Files
ExMine/lib/ex_mine/game.ex
2019-01-13 15:24:32 -05:00

182 lines
5.8 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, wrap \\ :wrap) do
board = Cartographer.new_board(height, width, wrap)
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