一.游戏介绍
计组实验大作业要在板子上做一个小游戏,我们组要做节奏大师,先在命令行里做了一个,主界面只有4*20.
可以选歌,目前支持的有:1.两只老虎,
2.两只老虎无尽版。
开始游戏后
∗
*
∗号会从右边不断出现并往左边移动,在到达最左端时变为
#
\#
#号,这个时候需要对应地按下数字1,2,3,4键来命中它们。
如果成功命中,就会播放一声《两只老虎》里的旋律,游戏继续。
如果按了错误的键或者没有按键,游戏就会结束。
游戏有计分功能,击中一个音乐块算一分,还有记录最高分的功能,而且可以通关(除了无尽模式)。
讲真这应该大一而不是大三做
二.预备知识
做命令行小游戏需要什么。
1. 获取输入
做这种命令行小游戏首先得知道怎么获得用户输入,这里的获取输入显然指的不是用户输入一段字符再打回车输入,而是用户的每个操作都得立即接收。
windows平台 <conio.h>
int _kbhit() ,返回是否有按键在缓冲区中。
char _getch(),返回缓冲区中的第一个按键,如果缓冲区中没有就一直等待第一个输入并返回。
注意,getch前面有个下划线,不带下划线的版本我的gcc无法编译。
原理具体解释很麻烦,拿这个小程序玩一会就懂了。
#include <conio.h>
#include <iostream>
int main()
{
while (1)
if(_kbhit()) //如果有按键按下,则_kbhit()函数返回真
printf("input %d\n", _getch() );
}
清空缓冲区:while(_kbhit()) _getch();
2.播放声音
windows平台 <windows.h>
void Beep(int,int),播放一个音符,第一个参数表示音调,第二个参数表示时长。
同样不多解释,下面的程序玩一下就懂了。mnote是预设音调(标准音7个,最后一个是一个低音,都可以随便改),TwinTiger是两只老虎的谱子。
#include <stdio.h>
#include <windows.h>
#include <bits/stdc++.h>
using Song = std::vector<std::pair<int,int>>;
int main()
{
const int mnote[] = {0,440,495,550,587,660,733,825,325};
const Song TwinTiger =
{
{1,4},{2,4},{3,4},{1,4},
{1,4},{2,4},{3,4},{1,4},
{3,4},{4,4},{5,4},{0,4},
{3,4},{4,4},{5,4},{0,4},
{5,3},{6,1},{5,3},{4,1},{3,4},{1,4},
{5,3},{6,1},{5,3},{4,1},{3,4},{1,4},
{3,4},{8,4},{1,4},{0,4},
{3,4},{8,4},{1,4},{0,4},
};
for(auto note : TwinTiger)
{
Beep(mnote[note.first],note.second*100);
}
}
3.清屏与闪屏
调用system("cls")
清屏,这样有个坏处是会闪屏。
因为我的程序最终要写到板子上,就不去解决这个问题了。
想解决闪屏问题的同学可以搜索与缓冲区有关的知识,用双缓冲解决闪屏。
三.主要实现思路
首要目标是要实现音乐块从右边出现,逐个往左的效果。
1. 地图
首先要有R*C尺寸的地图,把 ∗ * ∗号和空格画在上边,再打印就可以把地图显示出来。打印文字同理。
2. 时间轴
记录游戏运行的时间,每过一个单位的时间就将所有的块往左移动一格,每隔一段时间就在右边新加入一个块。
3. 每行存储块产生的时间
本质上说,每行的关键要素就在于它有几个块,分别在什么位置。
但是位置是一个变量,不如存储这些块产生的时间,当需要打印地图的时候,与当前运行时间相减就可以得到存储位置。
4. 队列存储
按上面的方法,加入一个块就是在这一行的队尾加入一个元素:当前时间。
当最早的块脱离地图时,就将它出队即可。
5. 简化的方法
实际上不用显式地去做出队操作,每次从队尾遍历所有可以被显示的块,碰到一个不能显示的直接停止遍历,简单,方便。
6. 可行的优化
可以使用循环队列去降低空间复杂度,暂未实现。
7. 代码实现
/** 使用队列记录音乐块 */
int block[R+1][T], lim[R+1];
void push(int r)
{
block[r][lim[r]++] = gameRunTime;
}
需要显示在地图上时
for(int i=lim[r]-1; i>=0 && block[r][i]-gameRunTime+C>=1; --i)
{
gameMap[r][block[r][i]-gameRunTime+C] = '*';
if(gameMap[r][1]!=' ')
gameMap[r][1]='#';
}
四.其它细节
想清楚上述问题后,其它细节逐个加入就行:
1. 整体流程
1 欢迎界面
2 选关,进入游戏
3 块逐个出现,并往左移动
4 当有块在最左端时,检测按键
5 如果按键正确,继续游戏
6 否则,显示结束页面
7 返回1
2. 选关功能
因为只有两种模式,所以相当于按任意键切换到另一模式。
void chooseSongs(int now)
{
gameClass = now;
memset(gameMap,0,sizeof(gameMap));
prints(2,3,"Choose Songs:");
if(now==0)
prints(4,1,"← Twin Tigers →");
else
prints(4,1,"←TwinTigerEndless→");
while(_kbhit()) _getch();
if(_getch()==13)
return;
else
return chooseSongs(now^1);
}
3.检测按键
/* 按键检测 */
int checkFail()
{
while(_kbhit())
{
int row = _getch()-'0';
if(gameMap[row][1] != ' ')
{
playNote(score++);
gameMap[row][1] = ' ';
}
else
{
return 1;
}
}
for(int r=1;r<=R;++r)
{
if(gameMap[r][1]!=' ')
{
return 1;
}
}
return 0;
}
五.后记
源码和程序下载。
因为没有太大的技术含量,就不挂在github上了。
放在csdn上,如果有需要可以去下载,只需要两积分。
如果实在没有积分或者C币,可以留言,我看到了会发,但是不保证时间。
注意使用的时候一定要保持输入法在英文状态