各模块的实现之二-键盘输入
根据流程图可以把其分为以下几个模块:
- 1-根据 dir 的值对 grid 进行操作
- 2-键盘输入(本稿实现部分)
- 3-在随机位置添加数值
- 4-游戏结束检测
一、目的
简单来说就是我们要造一个“黑盒子”,让我们输入的键盘消息经过这个黑盒子,电脑就知道我们在2048游戏中按下了什么键,要知道,电脑不像人一样聪明,是不能直接识别的
二、前提知识
-
一个键,它有3种状态,它被按下(key_msg_down ),它被按着(key_msg_down),它抬起来了(key_msg_up)
//按键测试程序: #include "graphics.h" #include <iostream> using namespace std; int main() { initgraph(640,480,0); setcaption("按键测试"); setbkcolor(WHITE); setcolor(BLACK); setfont(22,0,"楷体"); int keyUPCount ,keyDownCount ,keyCharCount; keyUPCount = keyDownCount = keyCharCount = 0; for( ; is_run(); delay_fps(60)) { cleardevice(); while(kbmsg()) //kbmsg()判断是否有按键按下 { key_msg keyMsg = getkey(); //key_msg 是 EGE 中的按键消息结构体,有 3 个 unsiged int 成员: switch(keyMsg.msg) //getkey() 获取键盘输入值(键值) { //msg---消息类型, key---按键码, flag--辅助按键(判断有没有同时按下 Shift 或 Ctrl) case key_msg_down: //检测按键按下,长按会一直加 keyDownCount++; break; case key_msg_up: //检测按键抬起 keyUPCount++; break; case key_msg_char: //检查是否输入字符: 除了TAB,CAPSLOCK,SHIFT,CTRL,FN,ALT,小键盘数字,Num Lk,小键盘上下左右 keyCharCount++; break; } } xyprintf(50, 300, "down计数:%d up计数:%d char计数:%d", keyDownCount,keyUPCount, keyCharCount); } getch(); closegraph(); return 0; }
-
W,A,S,D按键的key值(加上小键盘的上下左右)
EGE 帮我们用宏定义好了:
键盘值 键值 W ‘W’ A ‘A’ S ‘S’ D ‘D’ 上 key_up 下 key_down 左 key_left 右 key_right
三、键盘控制四方向模板
while(kbmsg())
{
key_msg keyMsg = getkey();
if(keyMsg.msg == key_msg_down)
{
switch(keyMsg.key)
{
case 'W':case key_up : direction = 0; break;//上
case 'S':case key_down : direction = 1; break;//下
case 'A':case key_left : direction = 2; break;//左
case 'D':case key_right: direction = 3; break;//右
}
}
}
if(direction!=-1)
{
...
}
四、根据 direction 的值进行移动(上一篇的内容讲过)
-
屏幕的坐标轴原点(0, 0)在左上角,以向右为 x 轴,向下为 y 轴
-
现在的坐标是(x, y),移动逻辑如下
移动方向 direction值 运算 偏移值 向上 0 (x, y)+ (0,-1) (0,-1) 向下 1 (x, y)+ (0, 1) (0, 1) 向左 2 (x, y)+ (-1,0) (-1, 0) 向右 3 (x, y)+ (1, 0) (1, 0) -
定义偏移数组:
int dx = [0,0,-1,1]; int dy = [1,1,0,0];
-
得到 direction 后统一运算
x += dx[direction]; y += dy[direction];
移动方向和 direction的值对应关系可变:在本游戏中:
五、补全键盘输入到已写程序中
-
上篇程序实现概览:
-
图中蓝色圈出部分是我们指定 dir 的值,现在我们让程序根据键盘输入确定 dir 的值
-
我们要在 Move函数中加入判断,dir 的值无效则退出执行:
if(dir==-1) return; //或者: //if(dir<0||dir>3) return;
-
EGE 的规范框架:https://xege.org/manual/tutorial/21.htm
以下是框架简化:
//基础动画一:规范框架 #include <graphics.h> int main(void) { initgraph(640, 480); for ( ; is_run(); delay_fps(60) ) { // 清屏 cleardevice(); // todo: 逻辑更新(数据更新) // // todo: 图形更新 } getch(); closegraph(); return 0; }
-
加入框架后
#include <iostream> #include "graphics.h" using namespace std; //测试矩阵 int grid[4][4] = { {0,1,2,3}, {0,1,2,3}, {0,1,2,3}, {0,1,2,3} }; int EmptyBlock = 16; //空格数 //打印函数 void PrintGrid() { for(int i=0; i<4; i++) { for(int j=0; j<4; j++) cout << grid[i][j] << " "; cout << endl; } cout << endl; } //计算空格函数 int CalculateEmpty() { int cnt = 0; for(int i=0; i<4; i++) for(int j=0; j<4; j++) if(grid[i][j]==0) cnt++; return cnt; } //移动函数 int dir; // 0-左,1-上,2-右,3-下 static int x0[4] = {0, 0, 3, 0}; static int y0[4] = {0, 0, 0, 3}; static int firstOffset[4][2] = {{1,0},{0,1},{-1,0},{0,-1}}; static int secondOffset[4][2] = {{0,1},{1,0},{0,1} ,{1,0}}; void Move(int dir) { if(dir==-1) return; int tx, ty; int t1x, t1y; int t2x, t2y; for(int i=0; i<4; i++) { tx = x0[dir] + i*secondOffset[dir][0]; ty = y0[dir] + i*secondOffset[dir][1]; //cout << "(" << tx << ", " << ty << ")" << endl; t1x = tx; t1y = ty; t2x = tx + firstOffset[dir][0]; t2y = ty + firstOffset[dir][1]; for( ;t2x>=0&&t2x<=3&&t2y>=0&&t2y<=3; t2x+=firstOffset[dir][0],t2y+=firstOffset[dir][1]) { if(grid[t2y][t2x]!=0) { if(grid[t1y][t1x]==0) { grid[t1y][t1x] = grid[t2y][t2x]; grid[t2y][t2x] = 0; } else if(grid[t1y][t1x]==grid[t2y][t2x]) { grid[t1y][t1x]++; grid[t2y][t2x] = 0; t1x += firstOffset[dir][0]; t1y += firstOffset[dir][1]; } else if(t1x+firstOffset[dir][0]!=t2x||t1y+firstOffset[dir][1]!=t2y) { grid[t1y+firstOffset[dir][1]][t1x+firstOffset[dir][0]] = grid[t2y][t2x]; grid[t2y][t2x] = 0; t1x += firstOffset[dir][0]; t1y += firstOffset[dir][1]; } else { t1x += firstOffset[dir][0]; t1y += firstOffset[dir][1]; } } } } } int main() { initgraph(200, 200); cout << "dir = " << dir << endl; cout<< "EmptyBlock = " << CalculateEmpty() << endl; PrintGrid(); for ( ; is_run(); delay_fps(60) ) { dir = -1; cleardevice(); // todo: 逻辑更新(数据更新) //按键检测 while(kbmsg()) { key_msg keyMsg = getkey(); if(keyMsg.msg == key_msg_down) { switch(keyMsg.key) { case 'A':case key_left : dir = 0; break;//左 case 'W':case key_up : dir = 1; break;//上 case 'D':case key_right : dir = 2; break;//右 case 'S':case key_down : dir = 3; break;//下 } } } // todo: 图形更新 if(dir!=-1) { system("cls"); cout << "dir = " << dir << endl; switch(dir) { case 0: cout << "按下了 A/左 键" << endl; break;//左 case 1: cout << "按下了 W/上 键" << endl; break;//上 case 2: cout << "按下了 D/右 键" << endl; break;//右 case 3: cout << "按下了 S/下 键" << endl; break;//下 } Move(dir); cout<< "EmptyBlock = " << CalculateEmpty() << endl; PrintGrid(); } } getch(); closegraph(); return 0; }
运行会弹出两个窗口:控制台窗口 和 100*100(像素)EGE窗口
鼠标要点一下黑黑的EGE窗口(给控制权),再把输入法调为英文,就可以试试 WASD 和小键盘上下左右了
F11调试运行–》鼠标点EGE黑框,输入法改为英文—》键入
OK,本篇内容就到这里了,我们摆脱了直接给 dir 赋值,改用键盘输入
不过,在 GIF 中也看到,当数字合并后,并没有新的数字生生成,后面只有 3,4,5 三个数字移动,
下一篇我们就让它合并或移动后,根据条件生成新的数字
还有:数字的合并逻辑是:相同的合并加 1,如,1,1–》2; 2,2–》3; 4,4–》5,而不是 2,4,8 ,这与我们后面储存图像的逻辑有关,2^1 = 2; 2^2 = 4; 2^3 = 8;2^4 = 16……