forking from https://gitea.daggertrout.com/mcdoh/ExMineArchive
This commit is contained in:
18
lib/ex_mine/application.ex
Normal file
18
lib/ex_mine/application.ex
Normal file
@@ -0,0 +1,18 @@
|
||||
defmodule ExMine.Application do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(ExMine.Server, [])
|
||||
]
|
||||
|
||||
options = [
|
||||
name: ExMine.Supervisor,
|
||||
strategy: :simple_one_for_one,
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, options)
|
||||
end
|
||||
end
|
||||
181
lib/ex_mine/game.ex
Normal file
181
lib/ex_mine/game.ex
Normal file
@@ -0,0 +1,181 @@
|
||||
defmodule ExMine.Game do
|
||||
defstruct(
|
||||
board: nil,
|
||||
flags: 0, # how many times a user has flagged or unflagged
|
||||
mine_density: 0.20625,
|
||||
start_time: 0,
|
||||
state: :initializing,
|
||||
|
||||
# tracks moves
|
||||
# used to return only updated tiles to user
|
||||
# start at -1 as the first 'update' is to report the board to the user
|
||||
update: -1
|
||||
)
|
||||
|
||||
alias ExMine.Tile
|
||||
|
||||
@report_to_user [:play_time, :state, :update]
|
||||
|
||||
def new_game(height, width) do
|
||||
board = Cartographer.new_board(height, width)
|
||||
tiles = for x <- 0..(width - 1), y <- 0..(height - 1), do: {x, y}
|
||||
|
||||
Enum.reduce(tiles, %ExMine.Game{board: board}, fn({x, y}, game) -> new_tile(game, x, y) end)
|
||||
end
|
||||
|
||||
def get_height(game), do: Cartographer.height(game.board)
|
||||
|
||||
def get_width(game), do: Cartographer.width(game.board)
|
||||
|
||||
def get_board(game) do
|
||||
tiles = game.board.tiles
|
||||
|> Enum.filter(fn({_key, tile}) -> tile.update == game.update end)
|
||||
|> Enum.map(fn({{x, y}, tile}) -> {"#{ x },#{ y }", scrub_tile(tile)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
game = Map.put(game, :update, game.update + 1)
|
||||
|
||||
{game, %{tiles: tiles, height: game.board.height, width: game.board.width}}
|
||||
end
|
||||
|
||||
def get_tile(game, x, y) do
|
||||
Cartographer.get(game.board, x, y)
|
||||
|> scrub_tile()
|
||||
end
|
||||
|
||||
def get_state(game = %{state: :lost}), do: {game, Map.take(game, @report_to_user)}
|
||||
def get_state(game = %{state: :won}), do: {game, Map.take(game, @report_to_user)}
|
||||
def get_state(game) do
|
||||
won = game.board.tiles
|
||||
|> Enum.all?(fn({_key, tile}) -> tile.unveiled or tile.mine end)
|
||||
|
||||
game = if won do
|
||||
Map.put(game, :state, :won)
|
||||
else
|
||||
game
|
||||
end
|
||||
|
||||
game = Map.put(game, :play_time, Time.diff(Time.utc_now(), game.start_time, :microsecond))
|
||||
|
||||
{game, Map.take(game, @report_to_user)}
|
||||
end
|
||||
|
||||
def make_move(game, x, y) do
|
||||
unveil(game, x, y, Cartographer.get(game.board, x, y))
|
||||
end
|
||||
|
||||
def toggle_flag(game, x, y) do
|
||||
toggle_flag(game, x, y, Cartographer.get(game.board, x, y))
|
||||
end
|
||||
|
||||
##############################################################################################
|
||||
|
||||
defp new_tile(game, x, y) do
|
||||
board = Cartographer.set(game.board, x, y, Tile.new_tile(game, x, y))
|
||||
|
||||
Map.put(game, :board, board)
|
||||
end
|
||||
|
||||
|
||||
defp scrub_tile(tile = %{unveiled: true}), do: Map.drop(tile, [:update])
|
||||
defp scrub_tile(tile), do: Map.drop(tile, [:mine, :update])
|
||||
|
||||
|
||||
defp unveil(game, _x, _y, _tile = nil), do: game
|
||||
defp unveil(game, _x, _y, _tile = %{flag: true}), do: game
|
||||
defp unveil(game, _x, _y, _tile = %{unveiled: true}), do: game
|
||||
|
||||
defp unveil(game = %{state: :initializing}, x, y, tile) do
|
||||
# make sure the opening move neighbors zero mines
|
||||
board = game.board
|
||||
|> Cartographer.get(x, y, 1)
|
||||
|> Enum.reduce(%{}, fn({{x, y}, tile}, nerfed) -> Map.put(nerfed, {x, y}, Map.put(tile, :mine, false)) end)
|
||||
|> Enum.reduce(game.board, fn({{x, y}, tile}, board) -> Cartographer.set(board, x, y, tile) end)
|
||||
|
||||
game = game
|
||||
|> Map.put(:board, board)
|
||||
|> Map.put(:start_time, Time.utc_now())
|
||||
|> Map.put(:state, :playing)
|
||||
|
||||
unveil(game, x, y, Cartographer.get(game.board, x, y))
|
||||
end
|
||||
|
||||
defp unveil(game, x, y, tile) do
|
||||
tile = tile
|
||||
|> Map.put(:update, game.update)
|
||||
|> Map.put(:unveiled, true)
|
||||
|> Map.put(:color, Map.put(tile.color, :alpha, tile.color.alpha / 10))
|
||||
|
||||
check_tile(game, x, y, tile)
|
||||
end
|
||||
|
||||
|
||||
defp check_tile(game, x, y, tile = %{mine: true}), do: update_tile(game, x, y, tile)
|
||||
defp check_tile(game, x, y, tile = %{neighboring_mines: neighboring_mines}) when not is_nil(neighboring_mines), do: update_tile(game, x, y, tile)
|
||||
defp check_tile(game, x, y, tile) do
|
||||
neighboring_mines = game.board
|
||||
|> Cartographer.neighbors(x, y)
|
||||
|> Enum.count(fn({_xy, tile}) -> tile.mine end)
|
||||
|
||||
tile = tile
|
||||
|> Map.put(:update, game.update)
|
||||
|> Map.put(:neighboring_mines, neighboring_mines)
|
||||
|
||||
update_tile(game, x, y, tile)
|
||||
end
|
||||
|
||||
|
||||
defp check_neighbors(game, x, y) do
|
||||
_check_neighbors(game, x, y, Cartographer.get(game.board, x, y))
|
||||
end
|
||||
|
||||
defp _check_neighbors(game, _x, _y, _tile = %{mine: true}), do: game
|
||||
defp _check_neighbors(game, _x, _y, _tile = %{neighboring_mines: neighboring_mines}) when neighboring_mines != 0, do: game
|
||||
defp _check_neighbors(game, x, y, _tile) do
|
||||
game.board
|
||||
|> Cartographer.neighbors(x, y)
|
||||
|> Enum.filter(fn({{_x, _y}, tile}) -> not tile.mine end)
|
||||
|> Enum.filter(fn({{_x, _y}, tile}) -> is_nil(tile.neighboring_mines) end)
|
||||
|> Enum.reduce(game, fn({{x, y}, tile}, game) -> check_tile(game, x, y, tile) end)
|
||||
end
|
||||
|
||||
|
||||
defp update_tile(game, x, y, tile = %{mine: true, unveiled: true}) do
|
||||
game
|
||||
|> Map.put(:play_time, Time.diff(Time.utc_now(), game.start_time, :microsecond))
|
||||
|> Map.put(:state, :lost)
|
||||
|> _update_tile(x, y, tile)
|
||||
end
|
||||
|
||||
defp update_tile(game, x, y, tile = %{mine: false, neighboring_mines: neighboring_mines, unveiled: false}) when not is_nil(neighboring_mines) do
|
||||
tile = tile
|
||||
|> Map.put(:update, game.update)
|
||||
|> Map.put(:unveiled, true)
|
||||
|> Map.put(:color, Map.put(tile.color, :alpha, tile.color.alpha / 10))
|
||||
|
||||
_update_tile(game, x, y, tile)
|
||||
end
|
||||
|
||||
defp update_tile(game, x, y, tile) do
|
||||
_update_tile(game, x, y, tile)
|
||||
end
|
||||
|
||||
defp _update_tile(game, x, y, tile) do
|
||||
board = Cartographer.set(game.board, x, y, tile)
|
||||
|
||||
game
|
||||
|> Map.put(:board, board)
|
||||
|> check_neighbors(x, y)
|
||||
end
|
||||
|
||||
|
||||
defp toggle_flag(game, _x, _y, _tile = %{unveiled: true}), do: game
|
||||
defp toggle_flag(game, x, y, tile) do
|
||||
tile = Map.put(tile, :flag, not tile.flag)
|
||||
board = Cartographer.set(game.board, x, y, tile)
|
||||
|
||||
game
|
||||
|> Map.put(:flags, game.flags + 1)
|
||||
|> Map.put(:board, board)
|
||||
end
|
||||
end
|
||||
30
lib/ex_mine/parse_string.ex
Normal file
30
lib/ex_mine/parse_string.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule ParseString do
|
||||
def parse(str) when is_binary(str)do
|
||||
case str |> Code.string_to_quoted do
|
||||
{:ok, terms} -> {:ok, _parse(terms)}
|
||||
{:error, _} -> {:invalid_terms}
|
||||
end
|
||||
end
|
||||
|
||||
# atomic terms
|
||||
defp _parse(term) when is_atom(term), do: term
|
||||
defp _parse(term) when is_integer(term), do: term
|
||||
defp _parse(term) when is_float(term), do: term
|
||||
defp _parse(term) when is_binary(term), do: term
|
||||
|
||||
defp _parse([]), do: []
|
||||
defp _parse([h|t]), do: [_parse(h) | _parse(t)]
|
||||
|
||||
defp _parse({a, b}), do: {_parse(a), _parse(b)}
|
||||
defp _parse({:"{}", _place, terms}) do
|
||||
terms
|
||||
|> Enum.map(&_parse/1)
|
||||
|> List.to_tuple
|
||||
end
|
||||
|
||||
defp _parse({:"%{}", _place, terms}) do
|
||||
for {k, v} <- terms, into: %{}, do: {_parse(k), _parse(v)}
|
||||
end
|
||||
|
||||
defp _parse({_term_type, _place, terms}), do: terms # to ignore functions and operators
|
||||
end
|
||||
51
lib/ex_mine/server.ex
Normal file
51
lib/ex_mine/server.ex
Normal file
@@ -0,0 +1,51 @@
|
||||
defmodule ExMine.Server do
|
||||
use GenServer
|
||||
|
||||
alias ExMine.Game
|
||||
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args)
|
||||
end
|
||||
|
||||
def init(%{height: height, width: width}) do
|
||||
{:ok, Game.new_game(height, width)}
|
||||
end
|
||||
|
||||
def handle_call({:get_height}, _from, game) do
|
||||
{:reply, Game.get_height(game), game}
|
||||
end
|
||||
|
||||
def handle_call({:get_width}, _from, game) do
|
||||
{:reply, Game.get_width(game), game}
|
||||
end
|
||||
|
||||
def handle_call({:get_board}, _from, game) do
|
||||
{game, board} = Game.get_board(game)
|
||||
{:reply, board, game}
|
||||
end
|
||||
|
||||
def handle_call({:get_tile, x, y}, _from, game) do
|
||||
tile = Game.get_tile(game, x, y)
|
||||
|
||||
{:reply, %{x: x, y: y, tile: tile}, game}
|
||||
end
|
||||
|
||||
def handle_call({:get_state}, _from, game) do
|
||||
{game, state} = Game.get_state(game)
|
||||
{:reply, state, game}
|
||||
end
|
||||
|
||||
def handle_call({:make_move, x, y}, _from, game) do
|
||||
game = Game.make_move(game, x, y)
|
||||
{game, board} = Game.get_board(game)
|
||||
|
||||
{:reply, board, game}
|
||||
end
|
||||
|
||||
def handle_call({:toggle_flag, x, y}, _from, game) do
|
||||
game = Game.toggle_flag(game, x, y)
|
||||
tile = Game.get_tile(game, x, y)
|
||||
|
||||
{:reply, %{x: x, y: y, tile: tile}, game}
|
||||
end
|
||||
end
|
||||
35
lib/ex_mine/tile.ex
Normal file
35
lib/ex_mine/tile.ex
Normal file
@@ -0,0 +1,35 @@
|
||||
defmodule ExMine.Tile do
|
||||
|
||||
defstruct(
|
||||
color: %{
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
|
||||
alpha: 1.0,
|
||||
},
|
||||
|
||||
mine: nil,
|
||||
flag: false,
|
||||
scale: 1.0,
|
||||
unveiled: false,
|
||||
update: -1,
|
||||
neighboring_mines: nil
|
||||
)
|
||||
|
||||
def new_tile(game, _x, _y) do
|
||||
%__MODULE__{
|
||||
color: %{
|
||||
red: :rand.uniform(128) + 128,
|
||||
green: :rand.uniform(128) + 128,
|
||||
blue: :rand.uniform(128) + 128,
|
||||
|
||||
alpha: (:rand.uniform(20) + 75) / 100,
|
||||
},
|
||||
|
||||
mine: :rand.uniform() <= game.mine_density,
|
||||
|
||||
scale: (:rand.uniform(20) + 70) / 100,
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user