Now that we have the data modeled, we can start with the bottom-most abstractions and build from there.

Some questions we'll need to answer:
- what legal moves does a piece have?
- what is the material balance of the game?
- is there a check or mate in this position?

In order to move, though, we'll need to be able to add integers to ranks and files:

> [!tip]- toEnum
> [[BoundedEnum]] provides `toEnum`, which happens to be a really quick way of solving the "into & out of" integers for these 2 datatypes.
> One difference is that [[BoundedEnum]]'s int representation is 0-based, but I think it makes more sense to have these be 1-based, so we subtract 1 before calling `toEnum` and add 1 after `fromEnum`.

```haskell
rankFromInt :: Int -> Maybe Rank
rankFromInt = toEnum <<< (_ - 1)

fileFromInt :: Int -> Maybe File
fileFromInt = toEnum <<< (_ - 1)

rankToInt :: Rank -> Int
rankToInt = (_ + 1) <<< fromEnum

fileToInt :: File -> Int
fileToInt = (_ + 1) <<< fromEnum

addRank :: Rank -> Int -> Maybe Rank
addRank a by = rankFromInt $ rankToInt a + by

addFile :: File -> Int -> Maybe File
addFile a by = fileFromInt $ fileToInt a + by
```

Now we can write a simple low-level `possibleMoves` function that yields all moves for a piece that keeps it within the bounds of the board (we'll check later if that is illegal for other reasons)
```haskell
possibleMoves :: Side -> Piece -> Pos -> Array Pos
possibleMoves Black Pawn (Pos r f)
  | r == R7 = [Pos R6 f, Pos R5 f]
  | otherwise =
      maybe [] pure
      $ (flip Pos f) <$> addRank r (-1)
possibleMoves White Pawn (Pos r f)
  | r == R2 = [Pos R3 f, Pos R4 f]
  | otherwise =
      maybe [] pure
      $ (flip Pos f) <$> addRank r 1
possibleMoves _ Knight (Pos r f) =
  Array.catMaybes
  [ Just Pos <*> addRank (-1) r <*> addFile (-2) f
  , Just Pos <*> addRank (-2) r <*> addFile (-1) f
  , Just Pos <*> addRank (1) r <*> addFile (-2) f
  , Just Pos <*> addRank (2) r <*> addFile (-1) f
  , Just Pos <*> addRank (-1) r <*> addFile (2) f
  , Just Pos <*> addRank (-2) r <*> addFile (1) f
  , Just Pos <*> addRank (1) r <*> addFile (2) f
  , Just Pos <*> addRank (2) r <*> addFile (1) f
  ]
possibleMoves _ King (Pos r f) =
  Array.catMaybes
  [ Just Pos <*> addRank (-1) r <*> addFile (-1) f
  , Just Pos <*> addRank 1 r <*> addFile (-1) f
  , Just Pos <*> addRank (-1) r <*> addFile 1 f
  , Just Pos <*> addRank 1 r <*> addFile 1 f
  , Just Pos <*> addRank 0 r <*> addFile 1 f
  , Just Pos <*> addRank 0 r <*> addFile (-1) f
  , Just Pos <*> addRank 1 r <*> addFile 0 f
  , Just Pos <*> addRank (-1) r <*> addFile 0 f
  ]
```