Haskell:使用GIF流进行游戏编程
注意:本文译自德语原文,曾作为卡尔斯鲁厄理工学院"编程范式"课程的作业,当时我负责辅导实践课程。
游戏介绍
贪吃蛇是一款电脑游戏,玩家需要操控蛇在游戏场地中移动。吃到食物会增加蛇的长度。当蛇撞到墙壁或自身时游戏结束。
任务目标
在这份作业中,你将使用Haskell实现贪吃蛇游戏。为此你需要使用gifstream仓库中的框架。游戏输出通过动态GIF流实现,可以在浏览器中观看。
技术基础
支持64种颜色,可表示为从(0,0,0)到(3,3,3)的Int元组:
|
|
GIF的单个帧定义为行列表,每行是RGB值列表:
|
|
框架提供server函数,在指定端口运行HTTP服务器:
|
|
编译与运行
Snake.hs文件包含编写贪吃蛇游戏的基础代码。编译并运行游戏(需要安装network和random Haskell包):
|
|
在浏览器中打开提供的地址。通过在终端中按下WASD键可以影响浏览器中的GIF。网络中的其他参与者也可以通过使用你的网络IP地址而不是127.0.0.1来观看GIF流。
还可以录制GIF流并稍后观看:
|
|
核心逻辑函数
Snake.hs中最重要的函数是logic:
|
|
logic函数为贪吃蛇游戏创建初始状态并将其传递给go函数。该函数使用getInput读取最后按下的键,然后生成新的游戏状态。显示的帧基于按下的键选择。最后sendFrame将新帧发送给所有连接的客户端。在此函数中,每个帧都通过scale进行缩放。对wait的调用会导致等待设置的延迟时间,默认为100毫秒。在函数末尾,它使用新生成的状态尾递归调用自身。
任务步骤
1. 打印游戏场地
使用当前状态生成图像并打印,而不是简单的单色图像。
编写fieldPositions列表,保存游戏场地在其对应位置的坐标:
|
|
场地大小保存在width和height中。对于3x4大小的场地,fieldPositions如下所示:
|
|
实现colorize函数,将图像的单个位置映射到颜色,以便通过let frame = map (map (colorize newSnake newFood)) fieldPositions创建新帧。根据该位置是蛇的一部分、食物还是背景,场地应着色不同:
|
|
2. 蛇的行为
接下来实现状态变化,以便游戏逻辑可以写为let newSnake = moveSnake snake food action:
|
|
蛇被定义为位置列表。根据传递的动作,新蛇获得新的头部。Action定义如下:
|
|
在尾部,最后一个元素被切断,除非蛇刚刚吃到食物。需要确保用户选择的动作是可能的。
编写validateAction函数,以便游戏逻辑可以通过let action = validateAction oldAction (charToAction input oldAction)进行扩展。为此,validateAction应仅在可能时返回新动作,否则返回旧动作。
3. 食物行为
接下来实现食物的状态变化,以便游戏逻辑可以通过newFood <- moveFood newSnake food进行扩展:
|
|
当蛇当前没有吃到食物时,可以直接返回食物的旧位置。否则应选择食物的新位置。避免食物出现在蛇的身体内部。
可以使用do语法生成x和y之间(包括)的随机数:r <- randomRIO (x,y)。因此需要导入System.Random模块。
4. 游戏结束
调整logic的结尾,以便使用newSnake检查新状态的有效性。在无效状态下,应通过调用initialstate >>= go重新启动游戏:
|
|
完整解决方案
完整的解决方案可在GitHub仓库中找到。
附加任务
使用GIF流输出编程另一个游戏,例如Pong、俄罗斯方块或康威生命游戏。