控制台俄罗斯方块
这是一个控制台项目,一切设计都要从字符角度考虑。
这次项目我终于敢放下顾虑,大胆解决问题了,其价值足以我写一篇博客来记录
俄罗斯方块不是什么很难的东西,也没人教过我怎么去构建这个项目。说明这个项目是真的简单。
这个项目构思不是一瞬间完成的,是先有总的思路,然后考虑某个部分的内容,再接着去做其细节、写成代码,常常因为出现前面考虑不到而去修改前面的东西的情况。看来这种修改是稀松的,不用担心去修改前面的代码,
这种修改是一种进步,不用担心不用怕。
那咱就挑点重点写写吧
阅前提示
建议先编译看看效果然后再来读博客。代码链接
(还有很多需要优化的地方……)
代码实现
总框架
void gameStart()
1 | 初始化 |
然后想到这个死循环有停下俩的时候,那就是:游戏失败或者是玩家退出,所以又加上了gameOver
和gameExit
这俩控制变量,然后在循环之后加上了例如分数记录之类的东西
贴图实现
实际上我以前都觉得贴图应该存在一个二维数组里面,但这次我却明白贴图还有多种存储方式。比如我能把一个”山“样的下落物存储在一个一维数组里,把它的贴图当作一个矩形,记录他的宽度,然后一排一排地解析。1
2
3
4
5降维打击前:
#
###
降维打击之后:
# ### width:3 height:2
大概是我第一次在显示这个问题上”活脑筋“,其价值足以我写一篇博客来记录
双缓冲渲染
我把双缓冲渲染改成了三个部分:绘制(输出画面)、写入Buffer、Buffer更新
以前我会顾虑:到底在哪里使用双缓冲?
我突然就敢放下这个顾虑:”只在游戏部分使用不就行了,其他地方根本就不需要双缓冲。“
绘制(输出画面)
void render()
比较用于绘制的Buffer(renderBuffer[DRAWING]
)和显示在屏幕上的信息(renderBuffer[SHOWING]
),然后到相应的位置去输出不同的部分。用gotoxy()
可以解决调用system('cls')
带来的闪屏问题。
写入Buffer
void renderDraw()
这次把所有的绘制都封装到了一个函数renderDraw()
里,以前的时候我总是会顾虑”逻辑运算完成了,在哪里绘制啊??????“。现在我通过gameStart()函数里的总流程,实现了贴图绘制的一个封装,就不用担心”这里是一个绘制,那里是一个绘制“导致混乱的问题、避免在这个问题上继续纠结了。renderDraw()
会在(dropX,dropY)解析下落物的贴图,并写入renderBuffer[DRAWING]
,然后把那些已经固定下来的方块一一对应绘制进buffer
Buffer更新
void renderFresh()
比较两个buffer[],然后把旧的推进新的里去。
选择选项
int makeChoice(char**, int[, char*])
函数
如果直接把做选择这个事情封闭起来,那么其它部分代码就不好实现,所以需要让makeChoice()有一个返回值,便于信息传递,而不是简简单单的去显示个动画。
首先,肯定要有个变量choice
来记录选择……
做选择时会有多个选项,每个选项前都要有其被选中的标志,按下回车键确定,因此采用了分行输出每个选项,每个选项前留出空白,在空白区域用->
表示这是当前选中的选项,以->
的左右移动为其动画效果美化纯文字的界面。
所以首先要显示每个选项并留出空白1
2for (int i = 0; i < _amo; ++i)
printf(" %s\n", _choice[i]);
然后就是考虑动效了。既然->
要上下移动和左右移动——尽管不会同时进行——那么我们就得去确定它的移动坐标变化范围。显然该用WindowsAPI获取选项输出后的光标位置。1
2
3HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO pos;
GetConsoleScreenBufferInfo(h, &pos);
简单把->
输出到相应位置十分简单,这里就不讲了。我们直接考虑动效的几个步骤:
- 清空旧的绘制图案
- 绘制图案
清空旧的绘制图案,有如下情况
- 用户改变了选中,旧的
->
要清除 ->
移动了,原来位置上的不能再显示了
考虑到控制台覆盖先前字符的特性,->
自动移动可以由新的图案直接输出覆盖,而用户改变选中,则需要我们把光标放到相应位置并输出足够的空格。当前动画播放到哪一帧,也需要去记录,就用ani
吧。
而且,不管用户是否按键,我们都要去更新->
的动画。所以我们不能阻塞进程并获取按键信息,conio.h
的kbhit()
以及我写的伪非阻塞SleepunblockedSleep()
就派上用场了。1
2
3
4
5
6
7
8
9
10
11if kbhit() == 1:
光标移动
覆盖旧->(无论按什么键什么都去覆盖一下,是为了代neng码tou简ge便lan
判断按键
按w:改变choice
按s:改变choice
按Enter:return choice
光标移动
在旧->上直接覆盖当前的帧的图案
unblockedSleep(); // 这样能保证延时时间或对用户按键”随叫随到“的相应
ani++
后来在Esc暂停菜单里我用makeChoice()
来实现菜单选项,就又在里面加了个选择后”隐藏“菜单的功能,于是又在按下Enter键后的代码里加上了,让makeChoice
自己清除自己的的代码
一开始考虑的设计是如>Start!
的样式,但是测试之后发现不如->Start!
好康,于是就改成了这个