I am new to F# and am trying to develop a snake game so pardon me if this sounds stupid.
For now, this is the model for the game:
// value objects
type Position = int * int
type Block = { Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
type World = { Snake: Snake }
//
To make things simpler when moving the snake, I would like for each Direction
to have its own Position
, just like:
type Direction =
| North of Position (0, 1)
| South of Position (0, -1)
| West of Position (-1, 0)
| East of Position (0, 1)
so I can just apply it here:
let moveSnakeHead direction snake =
// easily move the snake's head
// tail[0].x += direction.x, tail[0].y += direction.y
However, it seems to me that it is not possible to do that of Position (x, y)
inside the discriminated union?
Could someone explain why? I am trying my best to learn types. And what would be the alternatives?
Abusing the answer from @TheQuickFrownFox I actually got it working the way I think you want. I think your data types are overly complex, but it is possible to create a snake game like this. Note the usage of reference types and mutables.
// value objects
type Position = int * int
type Block = { mutable Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
//
let directionToPostion = function
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
let moveSnakeHead (direction: Direction) (snake: Snake ref) =
// easily move the snake's head
let (dirX, dirY) = directionToPostion direction
let snakeHeadPos = (!snake).Tail.Blocks.[0].Position
(!snake).Tail.Blocks.[0].Position <- (dirX + fst snakeHeadPos, dirY + snd snakeHeadPos)
let blocks: Block list = [ {Position = (5,3)}; {Position = (4,2)} ]
let tail: Tail = { Blocks = blocks }
let snake = ref <| {Tail = tail}
printfn "%A" blocks
moveSnakeHead North snake
printfn "%A" blocks
F# is not a clean functional language, so you can use it like an object-oriented language with a bit of work, but it is not the preferred way. Optimally you would have a function which reads the snake (I recommend simply using the type type Snake = (int * int) list
, and outputs (maps) it into a new list containing the updated positions. This would be cleaner, easier to maintain, and more adherent to the design goals of F#.
I decided to come back and update my answer to contain which I think would be the canonical way of doing this in F#. I think you will find this cleaner and easier to read:
type Snake = (int * int) list
type Direction = North | South | East | West
let moveSnake snake dir =
if List.isEmpty snake then []
else
let h = List.head snake
match dir with
| North -> (fst h, snd h - 1) :: List.tail snake
| South -> (fst h, snd h + 1) :: List.tail snake
| East -> (fst h + 1, snd h) :: List.tail snake
| West -> (fst h - 1, snd h) :: List.tail snake
let snake = [(5,3); (1,2)]
printfn "%A" snake
printfn "%A" <| moveSnake snake North
If you really want, you can declare the snake variable mutable
, so that you can change the snake. But I recommend staying away from this and having your program strictly functional as far as possible.