Elixir - 0x02 Command Line Fun - LBA Game

/images/blog/old_uploads/elixir-command-line-fun-825x510.jpg
elixir command line fun 825x510

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 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.

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.

 

Subscribe to my Newsletter

Receive emails about Linux, Programming, Automation, Life tips & Tricks and information about projects I'm working on