歧视工会内部的分配 快速注释:编辑:

问题描述

我是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,以便可以更改蛇。但是我建议不要这样做,并让您的程序尽可能严格地运行。