Arduino平台的SIMON游戏克隆版制作指南

作为一名CodeProject的会员,一直没有机会发表文章,这让感到有些失望。一直在思考可以创作什么样的文章。在阅读了CodeProject上由jeffb42撰写的一些优秀文章后,问题得到了解决。大约两个月前,开始接触Arduino硬件平台,因为在寻找适合家用的几个项目。为了能够使用这个设备,首先需要了解它的能力。本文将探讨如何克服在Arduino平台上运行SIMON游戏克隆版的问题,这展示了处理主要应用程序循环约束和硬件平台上代码重用的方法。

对于那些不熟悉SIMON游戏的人来说,这是一个70年代的早期电子游戏,用户基本上需要向控制台重复一个序列以进入下一个级别。在游戏实现中,用户可以玩渐进模式,游戏将重新使用前一个级别作为下一个级别的开始,然后在后面添加一个随机步骤,或者随机模式,每个级别的游戏都会产生一个新的随机序列,每个步骤都比前一个长。

jeffb42的《Arduino入门》和《与LCD接口》文章提供了更多关于Arduino平台细节的信息,在网站上,有一些IO基础的各种示例,所以这里不需要重复。

在Arduino上构建这个游戏的挑战在于平台的工作原理。在源代码中,有三个区域可以放置代码:初始化区域、setup方法和loop方法。这些按照顺序执行。

// 初始化部分 // 库引用和变量/常量声明包含在这里 void setup() { // 这个方法只运行一次 // 硬件分配在这里完成,例如输入/输出引脚方向 } void loop() { // 这个方法将一直运行,直到断电或上传新程序到平台 }

挑战在于能够使代码以这样的方式运行,即游戏可以在两种模式中反复玩,而不会陷入无尽的循环。

使用代码

硬件需求的电路图包含在下载文件中,以及源代码。如果拥有必要的组件,可以构建游戏并将代码上传到Arduino使其工作。或者,如果刚开始接触这个平台或类似的东西,代码可能会为提供如何在自己的项目中实现类似目标的想法。

游戏结构

需要能够保持游戏代码在一个无尽的循环中运行,实现这一点的一种方法是使用标志。布尔变量,它们要么是True要么是False,是实现这一点的最简单方法之一。看看游戏是如何工作的,需要一些基本的标志。它们持有游戏的状态,即它是否已经开始,它是否已经被玩过,如果游戏现在结束了。还需要一些其他变量来跟踪生成的步骤序列是什么,以及用户当前处于哪个级别。这些需求以及硬件引脚分配在代码的初始化部分被识别:

#include <LiquidCrystal.h> // 使用接口引脚的数字初始化库 LiquidCrystal lcd(13, 12, 11, 10, 9, 8); int userInput = 0; // 从按钮获取用户输入的模拟引脚 int led1 = 2; // LED 1 int led2 = 3; // LED 2 int led3 = 4; // LED 3 int led4 = 5; // LED 4 int speaker = 6; // 扬声器 // 游戏统计 boolean started = false; // 游戏是否已经开始 boolean gameover = false; // 游戏是否结束 int level = 0; // 用户当前级别(得分 = 级别 -1) int gameMode = 0; // 正在使用的游戏模式 // 1 = 渐进 // 2 = 随机 boolean soundEnabled = true; // 声音是否启用 int lastLevelSeq[50]; // 用于渐进模式前一级别步骤的序列 // 游戏结束时也用于回放正确序列 // 没人能超过50级,对吧!

代码的下一部分是setup()方法。这是硬件引脚初始化的地方。

void setup() { pinMode(led1, OUTPUT); pinMode(led2, OUTPUT); pinMode(led3, OUTPUT); pinMode(led4, OUTPUT); pinMode(speaker, OUTPUT); // 设置LCD列和行数 lcd.begin(16, 2); delay(100); lcd.clear(); lcd.print("Welcome to"); lcd.setCursor(0, 1); lcd.print("Arduino SIMON"); // ...设置代码的其余部分被剪切。请参见下载。 }

接下来执行的是loop()方法,它被无尽地运行,因此需要跟踪游戏状态。为了使代码更短、更高效,使用了一些辅助方法来满足文章开头提到的重用要求。

// 用于确定用户按下的是哪个按钮 getButtonPressed() // 播放给定的声音 playTone(tone) // 这是实际的游戏级别,如果用户成功则返回布尔值true,如果用户失败则返回false doLevel(level) // 打开给定的LED,或者0以全部关闭 lightLed(led) // 这将使用其他辅助方法播放声音并点亮适当的LED playStep(step)

主loop()结构如下(所有细节都被剥离,只留下结构):

void loop() { while (started == false) { if (getButtonPressed() > 0) { // 这部分等待用户按下按钮开始游戏 } } while (gameMode == 0) { switch (getButtonPressed()) { case 1: // 用户按下按钮1选择渐进模式 break; case 2: // 用户按下按钮2选择随机模式 break; case 4: soundEnabled = (!soundEnabled); // 用户按下按钮4切换声音开关 break; } } while (gameover == false) { if (doLevel(level) == true) { // 用户玩了一个级别并且正确 } else { // 级别错误,设置游戏结束 } } if (gameover == true) { // 游戏现在结束了,回放正确的序列 } // 等待用户 while (gameover == true) { if (getButtonPressed() > 0) { // 等待用户按下按钮回到开始菜单 } } }

构建结构的最简单方法是在脑海中玩这个游戏,并在块中写下每个步骤所需的步骤;从这里,可以构建一个流程图,然后是代码结构。

感兴趣的点

鉴于可用的数字IO引脚数量有限,四个按钮实际上是通过一个电阻网络连接到一个模拟输入引脚上的。按下每个按钮都会改变模拟输入的值;从这个值,然后可以确定哪个按钮被按下,使用getButtonPressed()方法。此外,由于模拟信号的波动,可能会出现一些变化,必须使用合适的电阻尺寸以提供足够的输入值间隔:

/* 读取模拟输入并确定哪个按钮被按下 */ int getButtonPressed() { // 推按钮电阻矩阵值是什么 int userValue = 0; userValue = analogRead(userInput); int buttonPressed = 0; if (userValue > 850) { buttonPressed = 0; // 没有按钮被按下 } if (userValue < 850) { buttonPressed = 4; // 可能是按钮4,仍然需要检查其他按钮 } if (userValue < 800) { buttonPressed = 3; // 可能是按钮3,仍然需要检查其他按钮 } if (userValue < 700) { buttonPressed = 2; // 可能是按钮2,仍然需要检查最后一个按钮 } if (userValue < 600) { buttonPressed = 1; // 完成按钮检查 } return buttonPressed; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485