作为一名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;
}