Elixir - 0x02 Command Line Fun - LBA Game

Elixir - 0x02 Command Line Fun - LBA Game

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) do
IO.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")
end

defp read_text(text) do
IO.gets("\n#{text} > ")
|> String.trim
end

defp read_command(text \\ "") do
IO.gets("\n#{text} > ")
|> String.trim
|> String.downcase
|> String.split(" ")
|> execute_command
end

defp execute_command(["quit"]) do
IO.puts "\nThanks for playing Life Beyond Apocalypse. Have a nice day!"
end
defp execute_command(["help"]) do
print_help_message()
read_command()
end

defp execute_command(_unknown) do
IO.puts("\nUnknown command. Try help <topic>.")
print_help_message()

read_command()
end

defp print_help_message() do
IO.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) do
IO.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)
end

defp read_text(text) do
IO.gets("\n#{text} > ")
|> String.trim
end

defp read_command(text \\ "", user) do
IO.gets("\n#{text} > ")
|> String.trim
|> String.downcase
|> String.split(" ")
|> execute_command user
end

defp execute_command(["quit"],_user) do
IO.puts "\nThanks for playing Life Beyond Apocalypse. Have a nice day!"
end
defp execute_command(["help"],user) do
print_help_message()
read_command(user)
end

defp execute_command(["move" | location],user) do
IO.puts "#{location}"
GameMap.move(List.to_string(location), user )
read_command(user)
end

defp execute_command(["map"],user) do
GameMap.show_map(user)
read_command(user)
end


defp execute_command(_unknown,user) do
IO.puts("\nUnknown command. Try help <topic>.")
print_help_message()

read_command(user)
end

defp print_help_message() do
IO.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.

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.

 

You might enjoy these similar articles:

Be the first to comment!

The comments section is closed.

Subscribe to my awesome newsletter!








What to expect: Ultimate Knowledge regarding Business Efficiency, Personalized Marketing Experience, Software Development and Cyber Security tips and tricks. 1-3 mails per month. Unsubscribe any time. See the privacy policy to learn how we take care of your information.