You didnāt list MapSets. They are especially handy when order doesnāt matter ā¦
ā¦ and here board locations are unique. So, for example, the board could be represented as a map containing three sets, one each for the fields owned by each player and a āfreeā set for the fields that arenāt owned yet.
In general the āoptimalā data structure really depends on the characteristics of the game and the nature of the analysis. Quite often representing a 2D game board as a 2D array is somewhat ābrute forceā.
# Represent board state as a map with sets as values
iex> state0 = %{
...> :free => MapSet.new([{1,1},{1,2},{1,3},{2,1},{2,2},{2,3},{3,1},{3,2},{3,3}]),
...> :x => MapSet.new,
...> :o => MapSet.new
...> }
%{free: #MapSet<[{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}]>,
o: #MapSet<[]>,
x: #MapSet<[]>}
# For move delete from free set and put into player set
iex> move = fn state, field, player ->
...> %{state |
...> :free => MapSet.delete(state.free, field),
...> player => MapSet.put(state[player], field) }
...> end
iex> state1 = move.(state0, {2,2}, :x)
%{free: #MapSet<[{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}, {3, 3}]>,
o: #MapSet<[]>,
x: #MapSet<[{2, 2}]>}
iex> state2 = move.(state1, {1,1}, :o)
%{free: #MapSet<[{1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}, {3, 3}]>,
o: #MapSet<[{1, 1}]>,
x: #MapSet<[{2, 2}]>}
iex> state3 = move.(state2, {1,2}, :x)
%{free: #MapSet<[{1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}, {3, 3}]>,
o: #MapSet<[{1, 1}]>,
x: #MapSet<[{1, 2}, {2, 2}]>}
iex> state4 = move.(state3, {3,2}, :o)
%{free: #MapSet<[{1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 3}]>,
o: #MapSet<[{1, 1}, {3, 2}]>,
x: #MapSet<[{1, 2}, {2, 2}]>}
iex> state5 = move.(state4, {3,1}, :x)
%{free: #MapSet<[{1, 3}, {2, 1}, {2, 3}, {3, 3}]>,
o: #MapSet<[{1, 1}, {3, 2}]>,
x: #MapSet<[{1, 2}, {2, 2}, {3, 1}]>}
iex> state6 = move.(state5, {2,3}, :o)
%{free: #MapSet<[{1, 3}, {2, 1}, {3, 3}]>,
o: #MapSet<[{1, 1}, {2, 3}, {3, 2}]>,
x: #MapSet<[{1, 2}, {2, 2}, {3, 1}]>}
iex> state7 = move.(state6, {1,3}, :x)
%{free: #MapSet<[{2, 1}, {3, 3}]>,
o: #MapSet<[{1, 1}, {2, 3}, {3, 2}]>,
x: #MapSet<[{1, 2}, {1, 3}, {2, 2}, {3, 1}]>}
# To determine win simply check if any winning field set
# is a subset of the player's field set
#
# Winning field sets can be generated dynamically
iex> rows = 1..3
iex> cols = 1..3
iex> max_row = Enum.max(rows)
iex> max_col = Enum.max(cols)
iex> diagonal_length = min(max_row, max_col)
iex> winning_lists =
...> [ (for i <- 1..diagonal_length, do: {i, max_col - i + 1}) |
...> [ (for i <- 1..diagonal_length, do: {i,i}) |
...> ((for r <- rows, do: for c <- cols, into: [], do: {r,c}) ++
...> for c <- cols, do: for r <- rows, into: [], do: {r,c})]]
[[{1, 3}, {2, 2}, {3, 1}], [{1, 1}, {2, 2}, {3, 3}], [{1, 1}, {1, 2}, {1, 3}],
[{2, 1}, {2, 2}, {2, 3}], [{3, 1}, {3, 2}, {3, 3}], [{1, 1}, {2, 1}, {3, 1}],
[{1, 2}, {2, 2}, {3, 2}], [{1, 3}, {2, 3}, {3, 3}]]
iex> winning_sets = Enum.map winning_lists, &MapSet.new/1
[#MapSet<[{1, 3}, {2, 2}, {3, 1}]>, #MapSet<[{1, 1}, {2, 2}, {3, 3}]>,
#MapSet<[{1, 1}, {1, 2}, {1, 3}]>, #MapSet<[{2, 1}, {2, 2}, {2, 3}]>,
#MapSet<[{3, 1}, {3, 2}, {3, 3}]>, #MapSet<[{1, 1}, {2, 1}, {3, 1}]>,
#MapSet<[{1, 2}, {2, 2}, {3, 2}]>, #MapSet<[{1, 3}, {2, 3}, {3, 3}]>]
iex> has_won = fn win_sets, state, player ->
...> Enum.any? win_sets, &(MapSet.subset? &1, state[player])
...> end
iex> has_won.(winning_sets, state7, :o)
false
iex> has_won.(winning_sets, state7, :x)
true