Please watch the videos under Resources
before class.
Creating a Game
You objective is to recreate a simple 1-player game (Minesweeper) on a 9
x9
grid with 10
mines. If you have never played minesweeper before please play a round or two here before until you fully understand how it works:
Notice that sometimes when you hit a field with 0
mines adjacent it will open up large areas of the board until all borders are filled with numbers.
For reasons of simplicity you don't have to implement that feature and you can instead simply show a 0
or otherwise indicate that the field has been opened.
You also don't have to implement a timer that displays the seconds passed since opening the first field.
Here is how a winning game state and a "Game over" gamestate can look like:
For icons you can swoop the Flowbite Icon Library for something that resembles a mine.
To organize fields in form of a grid I highly recommend you to watch the "Tic Tac Toe" video from last Friday. Essentially we can use Tailwind's grid
system:
<div class"grid grid-cols-9">
<button class="size-4 ..." ...>...</button>
</div>
Since this is a bit more involved I recommend you what I always to when tackling challenging problems.
Create your modules blank and think about functions and what their inputs and outputs are before implementing them. A docstring is a great way to do that:
@doc """
Takes the id of a game and the new game state and updates it in the ETS table.
"""
def set_state(id, game) do
end
Finally to help you get started here are some recommendations on your games' schema fields:
embedded_schema do
field :mine_map, {:array, :boolean} # 81 entries, represents where mines are
field :open_map, {:array, :boolean} # 81 entries, represents what is opened
field :last_opened, :integer # index of last field opened
field :finished, :boolean # for convenience, not actually needed
end
Here is the ETS module code we used for the Tic Tac Toe game:
defmodule App.ETS do
use GenServer
alias App.Games.TicTacToe
require Logger
@name __MODULE__
def start_link(_) do
# Start the GenServer
GenServer.start_link(@name, [], name: @name)
end
@doc """
Create and populate in-memory data tables.
"""
def init(_) do
:ets.new(:tic_tac_toes, [:ordered_set, :named_table, :public, {:read_concurrency, true}])
{:ok, []}
end
def create_game do
game = TicTacToe.build_game()
:ets.insert(:tic_tac_toes, {game.id, game})
game
end
def join_game(game) do
game = TicTacToe.join_game(game)
:ets.insert(:tic_tac_toes, {game.id, game})
game
end
def update_game(game, attrs) do
game = TicTacToe.update_game(game, attrs)
:ets.insert(:tic_tac_toes, {game.id, game})
game
end
def get_game(id) do
case :ets.lookup(:tic_tac_toes, id) do
[{_slug, game}] -> game
_ -> nil
end
end
end
Don't forget to include your ETS module in the list of chilren in your application.ex
file:
children = [
...
# Start a worker by calling: App.Worker.start_link(arg)
# {App.Worker, arg},
App.ETS, # <-- add this
# Start to serve requests, typically the last entry
AppWeb.Endpoint
]
Here is further some helper code discussed in class:
@doc """
Returns a list of 89 booleans with 9 of them mines at random positions.
"""
defp create_random_mines do
List.duplicate(true, 9)
|> Kernel.++(List.duplicate(false, 72))
|> Enum.shuffle()
end
def adjacent_count(game, index) do
# use // and % to convert index to x and y coordinates:
# x
# y
# use x and y to check all 8 neighbors:
# example: Enum.at(game.mine_map, get_index(x-1, y)) --> true/false depending on mine
# sum up all mines, returns 0-8
end
defp get_index(x, y), do: y * 9 + x
As always, have a lot of fun!