182 lines
5.7 KiB
Elixir
182 lines
5.7 KiB
Elixir
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
|