Skip to content

Instantly share code, notes, and snippets.

@DanCouper
Created September 29, 2015 12:27
Show Gist options
  • Save DanCouper/509feb71a2b8fa570654 to your computer and use it in GitHub Desktop.
Save DanCouper/509feb71a2b8fa570654 to your computer and use it in GitHub Desktop.
Grid for minesweeper-style games, in Elixir, using digraph
defmodule Minesweeper.GameGrid do
@moduledoc """
Represent a game grid as a unidirected graph. For example:
```
{0,0} ━ {1,0} ━ {2,0}
│ ╳ │ ╳ │
{0,1} ━ {1,1} ━ {2,1}
│ ╳ │ ╳ │
{0,2} ━ {1,2} ━ {2,2}
```
Using Digraph to model this entirely removes the bounding/navigation
problems inherent in the lists of lists approach, at the cost of increased
complexity, though this can [will] be mitigated by including an API to access,
query and modify the graph nodes.
Each vertex id is a tuple representing the {x,y} coordinates.
Each vertex label holds the state*.
DEV NOTES:
1. digraph has a relatively high start-up cost in terms of memory,
though subsequently, it is very fast.
2. Digraph uses ETS tables (3 linked tables),
and **the graph generated is mutable**.
3. The table generated stays around as long as the process is open. This
can cause issues, such as unexpected 'wrong' results due to not
clearing it down.
* TODO: what properties the state carries are dependent upon
implementation, this module currently generates a graph structure only,
with blank labels (`[]` for each vertex). The label should be defined in
a module, likely as a struct, with a defined `@behaviour`. The module should
also contain the core defaults? these are part of the core game structure.
Possibly, define a core `Grid` module & define behaviour, then generate [maybe?]
RectangularGrid, RectangularGridDiagonal, *&c* - each would pass a directional list.
Concept could be expanded, with careful refactoring, to include hexagonal
grids *&c*. This way, can focus on driving down the construction cost, offloading
it to processes etc.
* TODO: Grid could be 'owned' by a player, they effectively have a seleciton of
board types, which are reused for games that make use of the same directional structures.
"""
@default_opts [
width: 10,
height: 10,
directions: [{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1},{0, -1}, {-1, -1}],
grid_indexing: 1
]
@doc """
NOTE side effects
"""
def generate_unlabelled_graph(options) do
opts = Keyword.merge(options, @default_opts)
coordlist = generate_nodes(opts[:width], opts[:height])
:digraph.new()
|> add_vertices(coordlist)
|> add_edges(coordlist, opts[:width], opts[:height], opts[:directions])
end
@doc """
NOTE side effects
"""
def add_vertices(graph, coordlist) do
Enum.each(coordlist, fn coord -> :digraph.add_vertex(graph, coord) end)
# return the mutated graph:
graph
end
@doc """
NOTE side effects
"""
def add_edges(graph, coordlist, width, height, directions) do
coordlist
|> Enum.each(fn coord ->
generate_neighbours(coord, width, height, directions)
|> Enum.each(fn neighbour_coord ->
:digraph.add_edge(graph, coord, neighbour_coord)
end)
end)
# return the mutated graph:
graph
end
def generate_nodes(width, height) do
for x <- range(width), y <- range(height), do: {x, y}
end
def generate_neighbours({x,y}, width, height, directions) do
for {mod_x, mod_y} <- directions,
Enum.member?(range(width), x + mod_x), Enum.member?(range(height), y + mod_y) do
{x + mod_x, y + mod_y}
end
end
defp range(min \\ 1, max) do
modifier = min - 1
Range.new(min, max + modifier)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment