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