├── .formatter.exs ├── .gitignore ├── .travis.yml ├── README.md ├── config └── config.exs ├── lib ├── spacelixir.ex └── spacelixir │ ├── io │ └── keyboard.ex │ ├── manager.ex │ ├── manager │ ├── meteor.ex │ ├── score.ex │ ├── shot.ex │ ├── spacecraft.ex │ └── time.ex │ ├── state.ex │ ├── ui.ex │ └── ui │ ├── meteor.ex │ ├── screen.ex │ ├── shot.ex │ └── spacecraft.ex ├── mix.exs ├── mix.lock ├── spacelixir └── test ├── spacelixir_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | spacelixir-*.tar 24 | 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | os: linux 4 | addons: 5 | apt: 6 | sources: 7 | - ubuntu-toolchain-r-test 8 | packages: 9 | - g++-6 10 | env: 11 | - MATRIX_EVAL="CC=gcc-4.9 && CXX=g++ 12 | before_install: 13 | - eval "${MATRIX_EVAL}" 14 | language: elixir 15 | elixir: 16 | - '1.8' 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spacelixir 2 | A simple game of spacecraft builded with elixir + ncurses. 3 | 4 | Demo 5 | ---- 6 |

7 | 8 |

9 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # third-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :spacelixir, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:spacelixir, :key) 18 | # 19 | # You can also configure a third-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /lib/spacelixir.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir do 2 | 3 | alias Spacelixir.UI 4 | alias Spacelixir.IO.Keyboard 5 | alias Spacelixir.State 6 | 7 | def run do 8 | UI.initialize() 9 | Keyboard.start_listen() 10 | schedule_next_tick() 11 | State.initial_state() 12 | |> loop() 13 | end 14 | 15 | def loop(state) do 16 | receive do 17 | {:ex_ncurses, :key, key} -> 18 | loop(Keyboard.handle_key(key, state)) 19 | 20 | :tick -> 21 | schedule_next_tick() 22 | state 23 | |> UI.draw() 24 | |> Spacelixir.Manager.update() 25 | |> loop() 26 | end 27 | end 28 | 29 | def schedule_next_tick do 30 | Process.send_after(self(), :tick, 33) 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/spacelixir/io/keyboard.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.IO.Keyboard do 2 | 3 | def start_listen do 4 | ExNcurses.listen() 5 | ExNcurses.noecho() 6 | ExNcurses.keypad() 7 | ExNcurses.curs_set(0) 8 | end 9 | 10 | def handle_key(?w, state), do: validate_spacecraft_position(state, state.spacecraft.x, state.spacecraft.y - 1) 11 | def handle_key(?a, state), do: validate_spacecraft_position(state, state.spacecraft.x - 1, state.spacecraft.y ) 12 | def handle_key(?s, state), do: validate_spacecraft_position(state, state.spacecraft.x, state.spacecraft.y + 1) 13 | def handle_key(?d, state), do: validate_spacecraft_position(state, state.spacecraft.x + 1, state.spacecraft.y ) 14 | def handle_key(?q, _), do: ExNcurses.endwin() 15 | def handle_key(?k, state) do 16 | ExNcurses.beep() 17 | %{state| shots: state.shots ++ [%{x: state.spacecraft.x+1, y: state.spacecraft.y}]} 18 | end 19 | def handle_key(_, state), do: state 20 | 21 | defp validate_spacecraft_position(state, x, y) do 22 | if x <= 38 && x >= 0 && y <= 39 && y >= 0 do 23 | %{state | spacecraft: %{x: x, y: y}} 24 | else 25 | state 26 | end 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/spacelixir/manager.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager do 2 | 3 | alias Spacelixir.State 4 | alias Spacelixir.Manager.Time 5 | alias Spacelixir.Manager.Meteor 6 | alias Spacelixir.Manager.Shot 7 | alias Spacelixir.Manager.Spacecraft 8 | 9 | def update(state) do 10 | state 11 | |> Time.update() 12 | |> Meteor.add() 13 | |> Shot.update() 14 | |> Meteor.destroy() 15 | |> Meteor.update() 16 | |> verify_if_is_game_over() 17 | end 18 | 19 | defp verify_if_is_game_over(state) do 20 | if Spacecraft.was_hit?(state) || Meteor.any_destroyed_planet?(state.meteors), do: State.initial_state(), else: state 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/spacelixir/manager/meteor.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager.Meteor do 2 | 3 | alias Spacelixir.Manager.Shot 4 | alias Spacelixir.Manager.Score 5 | 6 | def update(state) do 7 | if rem(state.time, 5) == 0 do 8 | %{state | meteors: generate_new_positions(state.meteors)} 9 | else 10 | state 11 | end 12 | end 13 | 14 | def add(state) do 15 | if rem(state.time, 20) == 0 do 16 | %{state | meteors: state.meteors ++ [%{x: :rand.uniform(37), y: 1}]} 17 | else 18 | state 19 | end 20 | end 21 | 22 | def destroy(state) do 23 | %{state | meteors: valids(state), shots: Shot.valids(state), score: Score.update(state)} 24 | end 25 | 26 | def valids(state) do 27 | state.meteors 28 | |> Enum.filter(fn meteor -> 29 | not Enum.any?(state.shots, fn shot -> 30 | was_destroyed?(meteor, shot) 31 | end) 32 | end) 33 | end 34 | 35 | def any_was_destroyed?(state) do 36 | state.meteors 37 | |> Enum.any?(fn meteor -> 38 | Enum.any?(state.shots, fn shot -> 39 | was_destroyed?(meteor, shot) 40 | end) 41 | end) 42 | end 43 | 44 | def any_destroyed_planet?(meteors) do 45 | meteors 46 | |> Enum.any?(fn meteor -> meteor.y >= 38 end) 47 | end 48 | 49 | def was_destroyed?(meteor, shot) do 50 | (meteor.x <= shot.x && shot.x <= meteor.x+2) && (meteor.y <= shot.y && shot.y <= meteor.y+1) 51 | end 52 | 53 | defp generate_new_positions(meteors) do 54 | meteors 55 | |> Enum.map(fn meteor -> %{x: meteor.x, y: meteor.y + 1} end) 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /lib/spacelixir/manager/score.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager.Score do 2 | 3 | alias Spacelixir.Manager.Meteor 4 | 5 | def update(state) do 6 | if Meteor.any_was_destroyed?(state), do: state.score + 1, else: state.score 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/spacelixir/manager/shot.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager.Shot do 2 | 3 | alias Spacelixir.Manager.Meteor 4 | 5 | def update(state) do 6 | %{state | shots: generate_new_positions(state.shots)} 7 | end 8 | 9 | def valids(state) do 10 | state.shots 11 | |> Enum.filter(fn shot -> 12 | not Enum.any?(state.meteors, fn meteor -> 13 | Meteor.was_destroyed?(meteor, shot) 14 | end) 15 | end) 16 | end 17 | 18 | defp generate_new_positions(shots) do 19 | shots 20 | |> Enum.map(fn shot -> %{x: shot.x, y: shot.y - 1} end) 21 | |> Enum.filter(fn shot -> shot.x >= 0 && shot.y >= 0 end) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /lib/spacelixir/manager/spacecraft.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager.Spacecraft do 2 | 3 | def was_hit?(state) do 4 | state.meteors 5 | |> Enum.any?(fn meteor -> was_destroyed?(meteor, state.spacecraft) end) 6 | end 7 | 8 | defp was_destroyed?(meteor, spacecraft) do 9 | (meteor.x <= spacecraft.x && spacecraft.x <= meteor.x+2) && (meteor.y <= spacecraft.y && spacecraft.y <= meteor.y+1) || 10 | (meteor.x <= spacecraft.x+2 && spacecraft.x+2 <= meteor.x+2) && (meteor.y <= spacecraft.y && spacecraft.y <= meteor.y+1) || 11 | (meteor.x <= spacecraft.x && spacecraft.x <= meteor.x+2) && (meteor.y <= spacecraft.y+1 && spacecraft.y+1 <= meteor.y+1) || 12 | (meteor.x <= spacecraft.x+2 && spacecraft.x <= meteor.x+2) && (meteor.y <= spacecraft.y+1 && spacecraft.y+1 <= meteor.y+1) 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/spacelixir/manager/time.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.Manager.Time do 2 | 3 | def update(state) do 4 | %{state | time: state.time + 1} 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/spacelixir/state.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.State do 2 | 3 | def initial_state do 4 | %{spacecraft: %{x: 20, y: 30}, shots: [], meteors: [], time: 0, score: 0} 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/spacelixir/ui.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.UI do 2 | 3 | alias Spacelixir.UI.Meteor 4 | alias Spacelixir.UI.Screen 5 | alias Spacelixir.UI.Shot 6 | alias Spacelixir.UI.Spacecraft 7 | 8 | def initialize do 9 | ExNcurses.initscr() 10 | end 11 | 12 | def draw(state) do 13 | ExNcurses.clear() 14 | Screen.draw_borders() 15 | Shot.draw(state.shots) 16 | Meteor.draw(state.meteors) 17 | Spacecraft.draw(state.spacecraft) 18 | Screen.draw_score(state.score) 19 | ExNcurses.refresh() 20 | state 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/spacelixir/ui/meteor.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.UI.Meteor do 2 | 3 | def draw(meteors) do 4 | Enum.each(meteors, fn meteor -> 5 | ExNcurses.mvaddstr(0 + meteor.y, 0 + meteor.x, "+") 6 | ExNcurses.mvaddstr(0 + meteor.y, 1 + meteor.x, "+") 7 | ExNcurses.mvaddstr(0 + meteor.y, 2 + meteor.x, "+") 8 | ExNcurses.mvaddstr(1 + meteor.y, 0 + meteor.x, "+") 9 | ExNcurses.mvaddstr(1 + meteor.y, 1 + meteor.x, "+") 10 | ExNcurses.mvaddstr(1 + meteor.y, 2 + meteor.x, "+") 11 | end) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/spacelixir/ui/screen.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.UI.Screen do 2 | 3 | def draw_borders() do 4 | (0..40) 5 | |> Enum.each(fn limit -> 6 | ExNcurses.mvaddstr(41, limit, "-") 7 | ExNcurses.mvaddstr(limit, 41, "|") 8 | end) 9 | end 10 | 11 | def draw_score(score) do 12 | ExNcurses.mvaddstr(5, 50, "SCORE") 13 | ExNcurses.mvaddstr(5, 57, to_string(score)) 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/spacelixir/ui/shot.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.UI.Shot do 2 | 3 | def draw(shots) do 4 | Enum.each(shots, fn shot -> ExNcurses.mvaddstr(shot.y, shot.x, ".") end) 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/spacelixir/ui/spacecraft.ex: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.UI.Spacecraft do 2 | 3 | def draw(spacecraft) do 4 | ExNcurses.mvaddstr(0 + spacecraft.y, 0 + spacecraft.x, "-") 5 | ExNcurses.mvaddstr(0 + spacecraft.y, 1 + spacecraft.x, "|") 6 | ExNcurses.mvaddstr(0 + spacecraft.y, 2 + spacecraft.x, "-") 7 | ExNcurses.mvaddstr(1 + spacecraft.y, 0 + spacecraft.x, "-") 8 | ExNcurses.mvaddstr(1 + spacecraft.y, 1 + spacecraft.x, "-") 9 | ExNcurses.mvaddstr(1 + spacecraft.y, 2 + spacecraft.x, "-") 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Spacelixir.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :spacelixir, 7 | version: "0.1.0", 8 | elixir: "~> 1.8", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | {:ex_ncurses, "~> 0.3"} 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, 3 | "ex_ncurses": {:hex, :ex_ncurses, "0.3.1", "1c6c480815c3863fe45cecf54e86dcaa6fa4484847d05c77a090e1ecb384536b", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, 4 | } 5 | -------------------------------------------------------------------------------- /spacelixir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juneira/Spacelixir/ca54caa73506de2a10b66acb858aae9ff7c17b49/spacelixir -------------------------------------------------------------------------------- /test/spacelixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpacelixirTest do 2 | use ExUnit.Case 3 | doctest Spacelixir 4 | 5 | test "there no has tests" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------