This is part of the Elixir Life Beyond Apocalypse Zombie Game creation tutorial.
Refer to the introduction for more information.
We said we were going to make a commandline game.
So let's implement it in a commandline fashion!
First edit mix.exs so we can use escript. Escript is just Erlang Script which will create a self contained script file with our full project.
def project do [ app: :life_beyond_apocalypse, version: "0.1.0", elixir: "~> 1.5", start_permanent: Mix.env == :prod, deps: deps(), escript: escript(), ] end#.... defp escript do [main_module: LifeBeyondApocalypse.CLI] end
Now we can create a cli.ex file in our library.
Note the ~S sigil with the heredoc means that we don't want any interpolation/interpretation, or else you would get a weird looking tag
defmodule LifeBeyondApocalypse.CLI do
@tag ~S""" _ _ __ ____ _| | (_)/ _| ___ | __ ) ___ _ _ ___ _ __ __| || | | | |_ / _ \\ | _ \\ / _ \\ | | |/ _ \\| '_ \\ / _` || |___| | _| __/ | |_) | __/ |_| | (_) | | | | (_| ||_____|_|_| \\___| |____/ \\___|\\__, |\\___/|_| |_|\\__,_| |___/ _ _ / \\ _ __ ___ ___ __ _| |_ _ _ __ ___ ___ / _ \\ | '_ \\ / _ \\ / __/ _` | | | | | '_ \\/ __|/ _ \\ / ___ \\| |_) | (_) | (_| (_| | | |_| | |_) \\__ \\ __//_/ \\_\\ .__/ \\___/ \\___\\__,_|_|\\__, | .__/|___/\\___| |_| |___/|_|"""
We can run it directly with mix
mix run -e 'LifeBeyondApocalypse.CLI.main("")'
Or we could compile our script
mix escript.build
Compiling 5 files (.ex)
Generated life_beyond_apocalypse app
Generated escript life_beyond_apocalypse with MIX_ENV=dev
This will generate a file life_beyond_apocalypse which we can run.
./life_beyond_apocalypse
| | (_)/ _| ___ | __ ) ___ _ _ ___ _ __ __| || | | | |_ / _ \\ | _ \\ / _ \\ | | |/ _ \\| '_ \\ / _` || |___| | _| __/ | |_) | __/ |_| | (_) | | | | (_| ||_____|_|_| \\___| |____/ \\___|\\__, |\\___/|_| |_|\\__,_| |___/ _ _ / \\ _ __ ___ ___ __ _| |_ _ _ __ ___ ___ / _ \\ | '_ \\ / _ \\ / __/ _` | | | | | '_ \\/ __|/ _ \\ / ___ \\| |_) | (_) | (_| (_| | | |_| | |_) \\__ \\ __//_/ \\_\\ .__/ \\___/ \\___\\__,_|_|\\__, | .__/|___/\\___| |_| |___/|_|
You can copy the script to any machine that has Erlang installed and it will run.
Adding a way to read commands
What we'll do it create a module variable with a map of commands and their description.
We will develop a function to read the user input. And one to execute various commands based on the user input.
@commands %{"quit" => "Quits the game","help" => "?<topic>? - Shows help screen and help topics about various commands","move" => "<location> - Moves to location. Valid options are: (w)est, (e)ast, (s)outh, (n)orth ",}def main(_args) doIO.puts(@tag)name = read_text("What is your name dear adventurer?")IO.puts "Welcome to LifeBeyondApocalypse #{name}!"read_command("To get started type in a command, or help")enddefp read_text(text) doIO.gets("\\n#{text} > ")|> String.trimenddefp read_command(text \\\\ "") doIO.gets("\\n#{text} > ")|> String.trim|> String.downcase|> String.split(" ")|> execute_commandenddefp execute_command(["quit"]) doIO.puts "\\nThanks for playing Life Beyond Apocalypse. Have a nice day!"enddefp execute_command(["help"]) doprint_help_message()read_command()enddefp execute_command(_unknown) doIO.puts("\\nUnknown command. Try help <topic>.")print_help_message()read_command()enddefp print_help_message() doIO.puts("\\nLife Beyond Apocalypse supports the following commands:\\n")@commands|> Enum.map(fn({command, description}) -> IO.puts(" #{command} #{description}") end)end
Our read_command function removes the newline with String.trim, then puts everything in downcase and splits the command in an array and at last executes it.
This makes it possible for us to do pattern matching on the input.
Let's try to add the user structure somewhere and work our way to use the show and move commands we created in the last lesson.
@commands %{"quit" => "Quits the game","help" => "?<topic>? - Shows help screen and help topics about various commands","move" => "<location> - Moves to location. Valid options are: (w)est, (e)ast, (s)outh, (n)orth ","map" => "Shows the map with your current location colored in",}def main(_args) doIO.puts(@tag)name = read_text("What is your name dear adventurer?")IO.puts "Welcome to LifeBeyondApocalypse #{name}!"user = %User{name: name}read_command("To get started type in a command, or help", user)enddefp read_text(text) doIO.gets("\\n#{text} > ")|> String.trimenddefp read_command(text \\\\ "", user) doIO.gets("\\n#{text} > ")|> String.trim|> String.downcase|> String.split(" ")|> execute_command userenddefp execute_command(["quit"],_user) doIO.puts "\\nThanks for playing Life Beyond Apocalypse. Have a nice day!"enddefp execute_command(["help"],user) doprint_help_message()read_command(user)enddefp execute_command(["move" | location],user) doIO.puts "#{location}"GameMap.move(List.to_string(location), user )read_command(user)enddefp execute_command(["map"],user) doGameMap.show_map(user)read_command(user)enddefp execute_command(_unknown,user) doIO.puts("\\nUnknown command. Try help <topic>.")print_help_message()read_command(user)enddefp print_help_message() doIO.puts("\\nLife Beyond Apocalypse supports the following commands:\\n")@commands|> Enum.map(fn({command, description}) -> IO.puts(" #{command} - #{description}") end)end
Let's test it and see how everything behaves together.
[caption id="attachment_481" align="alignnone" width="956"] elixir life beyond apocalypse commandline example game[/caption]
We see that the colours aren't right yet and that we have some IO.puts commands still lying around from the debugging session.
Appart from that, everything seems to work just fine.
Download the full sourcecode for 0x02 here.
Conclusion
You can run any function as a starting point with mix run -e .
Whenever you need to create a simple script executable you can use escript.
There is one small problem, we always pass the user around. This makes our code ugly and complicated.
In the next tutorial we'll address this problem and refactor the code.