2048游戏系列---功能模块第二稿【键盘输入】

2048-具体功能实现二稿-键盘输入

各模块的实现之二-键盘输入

根据流程图可以把其分为以下几个模块:

  • 1-根据 dir 的值对 grid 进行操作
  • 2-键盘输入(本稿实现部分)
  • 3-在随机位置添加数值
  • 4-游戏结束检测
    2048游戏系列---功能模块第二稿【键盘输入】

一、目的

简单来说就是我们要造一个“黑盒子”,让我们输入的键盘消息经过这个黑盒子,电脑就知道我们在2048游戏中按下了什么键,要知道,电脑不像人一样聪明,是不能直接识别的

二、前提知识

  1. 一个键,它有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;
    }
    
  2. 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的值对应关系可变:在本游戏中:
    2048游戏系列---功能模块第二稿【键盘输入】

五、补全键盘输入到已写程序中

  • 上篇程序实现概览:
    2048游戏系列---功能模块第二稿【键盘输入】

  • 图中蓝色圈出部分是我们指定 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黑框,输入法改为英文—》键入

2048游戏系列---功能模块第二稿【键盘输入】


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……

2048游戏系列---功能模块第二稿【键盘输入】

上一篇:动态规划——马儿跳


下一篇:不到 150 行代码写一个 Python 版的贪吃蛇