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
Quick note:
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#.
Edit:
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.