问题描述
我是F#的新手,正在尝试开发蛇游戏,如果听起来很蠢,请原谅我。
现在,这是游戏的模型:
// 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 }
//
为了简化移动蛇的过程,我希望每个Direction
都有自己的Position
,就像:
type Direction =
| north of Position (0,1)
| South of Position (0,-1)
| West of Position (-1,0)
| East of Position (0,1)
所以我可以在这里应用它:
let moveSnakeHead direction snake =
// easily move the snake's head
// tail[0].x += direction.x,tail[0].y += direction.y
但是,在我看来,of Position (x,y)
不可能在受歧视的工会内部进行吗?
有人可以解释为什么吗?我正在努力学习类型。还有什么选择?
解决方法
请确保您清楚F#中的值和类型之间的区别。对于刚接触F#的人来说,这是一个常见的陷阱,尤其是在受歧视的工会周围。
1
是类型int
的值。
(1,1)
是类型(int * int)
的值。
定义DU类型时,每种情况都可以保存某种类型的数据:
type DUType =
| DUCase1 of int
因此,每个DU案例都可以包含任何 int
,而不仅仅是特定的int
。
您的代码type Position = int * int
中还有一个类型别名。这就是说您可以在任何地方写Position
,这与int * int
的含义相同。它实际上不是另一种类型。
因此,在您的代码中,您不能说DU案例必须始终包含某个值。您需要编写一个函数,该函数需要一个Direction
并返回一个Position
:
type Direction =
| North
| South
| West
| East
let directionToPostion direction : Position =
match direction with
| North -> (0,1)
| South -> (0,-1)
| West -> (-1,0)
| East -> (0,1)
您编写的任何F#代码通常始终处于3种“模式”:
- 值
- 类型
- 模式(与模式匹配一样)
尝试确保您在任何给定的时间都知道自己是其中三个。
,滥用@TheQuickFrownFox的答案实际上,它确实按照我想要的方式工作。我认为您的数据类型过于复杂,但是 可以创建这样的蛇游戏。注意引用类型和变量的用法。
// 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)
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#不是一种干净的函数式语言,因此您可以像使用面向对象的语言一样使用它,但需要一些工作,但这不是首选方法。理想情况下,您将拥有一个读取蛇的函数(我建议仅使用类型type Snake = (int * int) list
,然后将其输出(映射)到包含更新位置的新列表中。这将更清洁,更易于维护且更具粘性。达到F#的设计目标。
编辑:
我决定返回并更新我的答案,以包含我认为这是在F#中执行此操作的规范方法。我认为您会发现此工具更干净,更易于阅读:
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
如果确实需要,可以声明蛇变量mutable
,以便可以更改蛇。但是我建议不要这样做,并让您的程序尽可能严格地运行。