Wondering what the 'best' or most idiomatic way of modelling a tic-tac-toe board would be.
As of right now I'm using nested tuples to represent a 3x3 grid:
pub type Board {
Board(rows: #(Row, Row, Row))
}
pub type Row {
Row(tiles: #(Tile, Tile, Tile))
}
pub type Tile {
Taken(Player)
Empty
}
pub type Player {
PlayerX
PlayerO
}
pub type Coordinate {
Coordinate(row: Int, col: Int)
}
But when interacting with the board I found that indexing into specific tiles is a little verbose:
pub fn mark_tile_as_taken(
board: Board,
coord: Coordinate,
player: Player,
) -> Board {
case coord.row {
0 -> {
let old_row = board.rows.0
let new_row = mark_tile_as_taken_in_row(old_row, coord.col, player)
Board(#(new_row, board.rows.1, board.rows.2))
}
1 -> {
let old_row = board.rows.1
let new_row = mark_tile_as_taken_in_row(old_row, coord.col, player)
Board(#(board.rows.0, new_row, board.rows.2))
}
2 -> {
let old_row = board.rows.2
let new_row = mark_tile_as_taken_in_row(old_row, coord.col, player)
Board(#(board.rows.0, board.rows.1, new_row))
}
_ -> panic
}
}
fn mark_tile_as_taken_in_row(row: Row, col: Int, player: Player) -> Row {
case col {
0 -> Row(#(Taken(player), row.tiles.1, row.tiles.2))
1 -> Row(#(row.tiles.0, Taken(player), row.tiles.2))
2 -> Row(#(row.tiles.0, row.tiles.1, Taken(player)))
_ -> panic
}
}
I can't help but feel that including more features, such as proper error-handling, or checking if a player has won will be quite verbose and difficult.
Are there any other tips on modelling my game state? Maybe I'm missing something obvious. Not especially an FP wizard.
Ty!