Play the Game

Let’s move on to the last module and learn to put the pieces together.

The board

The board has a dual role to play. It knows about and can address all the islands. It delegates function calls down to the islands individually or as a group. That makes a board both an orchestrator as well as an interface for actions that involve islands.

The actions that a board needs to handle include positioning islands, ensuring that all islands are positioned, and guessing coordinates. Let’s tackle them in order.

Players will be able to move their islands around the board until they declare them set. Each time they move them, the front-end of the application will pass down an atom key representing the type of the island. It also represents the row and the column of the starting coordinate.

Layers above this one will convert this data to an actual island and also pass both the key and the island into the board here. As long as the island matches an %Island{}, we know it’s valid. If it doesn’t match, this will raise a FunctionClauseError. This is appropriate because it means something went wrong with the island after it was created.

Develop the logic

In order to shorten the runtime check on an %Island{} struct, let’s add an alias at the top of the module file:

alias IslandsEngine.Island

If the island doesn’t overlap with any existing islands, we set it in the board map with the key we passed. Otherwise, we return {:error, :overlapping_island}:

def position_island(board, key, %Island{} = island) do
  case overlaps_existing_island?(board, key, island) do
    true -> {:error, :overlapping_island}
    false -> Map.put(board, key, island)
  end 
end

Get hands-on with 1200+ tech skills courses.