Elixir - 0x02 Command Line Fun - LBA Game

// = Elixir - 0x02 Command Line Fun - LBA Game :id: 7e0d4239-2382-427d-9497-0767657a5fe0 :author: Andrei Clinciu :website: https://andreiclinciu.net/ :publish_at: 2018-02-22 18:24:00Z :heading_image: /images/blog/old_uploads/elixir-command-line-fun-825x510.jpg :description: \N :type: article :tags: :keywords: :toc: left :imagesdir: ../assets/

image::{heading_image}[] link:/blog/learning-elixir-and-otp-through-a-project-life-beyond-apocalypse/[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

[source,lang:default,decode:true]

@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” => “?? - Shows help screen and help topics about various commands”,“move” => “ - 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 .")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” => “?? - Shows help screen and help topics about various commands”,“move” => “ - 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 .")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.

{empty}[caption id=“attachment_481” align=“alignnone” width=“956”]image:/images/blog/old_uploads/screenshot_2018-01-26-202527.png[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.

https://andreiclinciu.net/wp-content/uploads/2018/02/0x02-Elixir-commandline.tar.gz[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.

 

Subscribe to my Newsletter

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