65.9K
CodeProject 正在变化。 阅读更多。
Home

F# 和 SDL.NET 实现康威生命游戏

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2011年2月4日

CPOL

5分钟阅读

viewsIcon

22654

downloadIcon

246

这是使用 F# 和 SDL.NET 实现康威生命游戏。

gameoflife.png

引言

概述

这是使用 F# 和 SDL.NET 实现的康威生命游戏。生命游戏是一个零玩家游戏,由英国数学家约翰·康威于 1970 年发明。生命游戏的规则很简单,但这些规则可能产生复杂的行为。

关于 SDL.NET

要构建此项目,需要 SDL.NET。SDL.NET 提供 C# 编写的 SDL 游戏库的 .NET 绑定。为了创建这个游戏,我从 http://cs-sdl.sourceforge.net/ 安装了 sdldotnet-6.1.1beta-sdk-setup.exe 版本。

如何运行生命游戏

规则

默认情况下,此程序使用康威生命游戏的规则:一个存活的细胞,如果邻居数量为两个或三个,则在下一代继续存活;一个死亡的细胞,如果邻居数量恰好为三个,则变为存活。也可以设置其他规则,可以通过命令行加载 .lif 文件到程序中来设置。

鼠标和键盘命令

鼠标命令

  • 左键单击 - 使细胞存活
  • 右键单击 - 使细胞死亡

键盘命令

  • 回车键 - 开始游戏
  • 暂停键 - 暂停游戏
  • 退格键 - 清除网格

从 .lif 文件加载数据

此模块以两种格式读取生命游戏文件:Life 1.05 和 1.06。程序中包含示例文件,位于 Data 文件夹中。要将数据加载到生命游戏网格中,请使用文件路径作为命令行参数运行程序,例如:GOL.exe C:\Life\Data\highlife.lif

生命游戏实现

项目结构

GOL 项目包含 4 个模块

  • GOL - 程序的入口点,负责图形显示、键盘和鼠标输入以及命令行参数
  • GOLGrid - 存储存活和死亡细胞网格的状态,并处理其在每一代的变化
  • ReadLife - 从 .lif 文件加载数据到游戏网格
  • General - 一系列通用的函数和项目中使用的活动模式

GOL

应用程序运行时会调用 go 函数。它获取命令行参数(如果有),并添加鼠标和键盘事件的处理程序。

let go() =
   let args = System.Environment.GetCommandLineArgs()
   if args.Length > 1 then readFromLifeFile args.[1]
   clearScreen()
   Events.KeyboardDown.Add(HandleInputDown)
   Events.MouseButtonDown.Add(HandleMouseClick)
   Events.Quit.Add(quit)
   Events.Tick.Add(update)
   Events.Run()

go()

HandleInputDown 函数使用模式匹配来选择按下特定键时要调用的函数。

   let HandleInputDown(args : KeyboardEventArgs) =  
    match args.Key with 
       | Key.Escape    ->Events.QuitApplication()
       | Key.Backspace -> clearGrid()  
       | Key.Return    -> runGame()
       | Key.Pause     -> pauseGame()
       | _ -> None |>ignore

HandleMouseClick 函数在鼠标主按钮(通常是左键)单击时设置细胞为存活状态,在游戏暂停或尚未开始时,使用其他鼠标按钮设置细胞为死亡状态。

   let HandleMouseClick(args : MouseButtonEventArgs) =
  if isRunning = false then
    let x = args.X
    let y = args.Y
    match args.Button with
    | MouseButton.PrimaryButton -> setCell (int y) (int x) true
                                   drawCell x y cellColour
    | _ -> setCell (int y) (int x) false
           drawCell x y Color.White
           drawGrid()

GOLGrid

生命游戏中的细胞可以是存活或死亡的。

type private State = 
 | DEAD  = 0
 | ALIVE = 1

默认情况下,一个细胞在其有 3 个存活邻居时出生,并在有 2 或 3 个存活邻居时继续存活。这些规则可以通过使用 ReadLife 模块加载 .lif 文件来更改。

let mutable private born = [3]    
let mutable private survive = [2;3]

let setRules birthList survivalList =
  born <- birthList
  survive <- survivalList

newValue 函数根据细胞的总存活邻居数量,返回网格中细胞的 ALIVE DEAD 值。它使用活动模式 IsMember 来检查总数是否在 bornsurvive 列表中。

   let private newValue row col = 
   let total = countNeighbours row col in 
      match total with
      | IsMember born when isDead row col     -> State.ALIVE
      | IsMember survive when isAlive row col -> State.ALIVE
      | _ -> State.DEAD

next 函数根据当前 grid 的值设置下一代 nextGrid 中细胞的值,然后更新 grid

let next() =
   for row = 0 to (height-1) do
         for col = 0 to (width - 1) do
            nextGrid.[row,col] <- newValue row col
   for row = 0 to (height-1) do
         for col = 0 to (width - 1) do
            grid.[row,col] <- nextGrid.[row,col]

ReadLife

ReadLife 模块可以从两种格式加载数据到 GOL 程序中 - Life 1.05 和 1.06。有关这些格式的更多信息可以在 细胞自动机文件格式 中找到。

readFromLifeFile 函数将文件读取为 string 数组,如果文件未找到则抛出异常。然后它使用其中一个函数来加载数据,具体取决于格式。

let readFromLifeFile lifeFile =
  try
   let lifeData = File.ReadAllLines(lifeFile)
   match lifeData.[0] with 
   | "#Life 1.05" -> createLife1_05 lifeData |> ignore
   | "#Life 1.06" -> createLife1_06 lifeData.[1..]
   | _ -> None |> ignore
  with
  | :? System.IO.FileNotFoundException 
         -> printfn "System.IO.FileNotFoundException: 
                The file %s was not found" lifeFile
  | _                               -> printfn 
            "Exception occurred when trying to read %s" lifeFile

在读取文件的每一行时,createLife1_05 会为以 #N#R 开头的行设置游戏的生存和出生规则,为以 #P 开头的行设置中心偏移量,或者将该行传递给内部函数 setCellsLife 来设置网格的细胞死亡或存活。其他行,如以 #D 开头的描述行或空行,则被忽略。变量 XYcreateLife1_05setCellsLife 外部未使用,因此它们是使用可变引用单元的闭包。

   let private createLife1_05 (lifeData : string[]) = 
   let X = ref 0
   let Y = ref 0
   let setCellsLife (line : string) = 
      let startX = !X
      for ch in line do
        match ch with
        | '.' -> setDead !Y !X
        | '*' -> setAlive !Y !X
        | _   -> None |> ignore
        X := !X + 1
      Y := !Y + 1
      X := startX
   for line in lifeData do 
      match line with
      | StartsWith "#N"-> setRules [3] [2;3]
      | StartsWith "#R"-> let [| _ ; survival ; birth |] 
                        = line.Trim().Split([|' ';'/' |]) 
        setRules (stringToIntList birth)(stringToIntList survival)
      | StartsWith "#P"-> let [|_; x ; y|] = line.Trim().Split(' ') 
                                   X := Int32.Parse( x) + centreCol
                                   Y := Int32.Parse (y) + centreRow
      | StartsWith "."         
      | StartsWith "*"         -> setCellsLife line                              
      | _ -> None |> ignore

左键单击 gadget 并拖动以移动它。左键单击 gadget 的右下角并拖动以调整其大小。右键单击 gadget 以访问其属性。

General 模块包含通用的函数和活动模式。活动模式用于对函数调用的结果进行模式匹配。StartsWith 检查一个 string 是否以子字符串开头,并在 ReadLife 模块的 createLife1_05 中使用。

let (|StartsWith|_|) substr (str : string) = 
  if str.StartsWith substr then Some() else None

IsMember 检查一个项是否在一个列表中,并在 GOLGrid 模块的 newValue 函数中使用。

let (|IsMember|_|) list item =
    if (List.tryFind (fun x -> x = item) list) 
       = Some(item) then Some() else None

stringToIntList 函数使用管道 |> 来在给定数字 string 时返回整数列表。这在 createLife1_05 中用于获取整数列表以设置出生和生存规则。

let stringToIntList (str : string) =
  str.ToCharArray() |> Array.map (fun x -> x.ToString())
                    |> Array.map (fun x -> Int32.Parse(x))
                    |> Array.toList

示例

这些示例模式可以在 Data 文件夹中找到。

滑翔机 - glider.lif

滑翔机是一种对角移动的模式,需要四个世代才能恢复其原始形状。生命游戏的规则并未提及移动,但移动模式是这些规则的衍生物。

振荡器 - oscillators.lif

振荡器是停留在原地周期的模式。oscillators.lif 中的模式从左到右依次是闪烁器、脉冲器和十五边形。

R-五格马 - r-pentomino.lif

这最初是一个简单的五细胞模式,但会生成许多模式,最终稳定下来。滑翔机最早是在 R-五格马中发现的。

滑翔机枪 - 13glidercollision.lif

滑翔机枪生成源源不断的滑翔机,它是由 13 个滑翔机的碰撞产生的。

Highlife 复制器 - highlife.lif

HighLife 的规则与康威生命游戏略有不同:一个细胞如果有 2 或 3 个邻居则存活,如果有 3 或 6 个邻居则出生。HighLife 中的复制器模式会复制自身。

历史

  • 2011 年 2 月 4 日:初始帖子
© . All rights reserved.