原创不易,请勿抄袭
作者联系方式 : QQ:993678929

文章目录

  • 0.游戏界面展示
  • 1. 编写思路
  • 2. 界面绘制
  • 3.数字的生成与合并
  • 4.键盘控制 / 游戏主循环
  • 5.游戏结束判定
  • #.完整代码

0.游戏界面展示



1. 编写思路

动手写代码之前,先设计好要做哪些部分。下面所述的是我个人的思路,如果有更好的思路欢迎探讨。

我们肯定能想到的是游戏界面的绘制,游戏的键盘控制,游戏逻辑。

游戏逻辑中包括数字的生成,合并,分数计算,以及游戏结束的判断。

棋盘是 4 x 4 的,且只需要显示数字,这可以用一个int数组来表示。

游戏过程中用户移动之后生成数字,需要注意这个次序

合并的顺序是按移动的逆方向来合并,而且一次移动只会合并一次。
比如有一行是4 2 2 2
那么向右划(移动)的结果是0 4 2 4
向左划的结果是4 4 2 0

不同的数字可以使用不同的颜色来显示方便辨识,提高玩家的游戏体验

后续还可以推出联网天梯排行榜


2. 界面绘制

我们可以专门写一个函数来绘制游戏界面,这样每次刷新的时候调用一次就行了。
首先定义一个4x4的“棋盘”来存放场面上的数字:

const int W = 4;
const int H = 4;
int board[H][W];  //H:高  W:宽   board[2][1] 表示第二行第二个

之前提到我们可以使用不同的颜色来显示不同的数字,以提高辨识度。

定义一个颜色数组,这样对于棋盘上的数字i,color[log2(i)-1],就能得到他的颜色代码:

const int color[] = {15,8,6,14, 4, 2,12 ,5,  13, 3,   10,  11,  1,   9, };
//分别对应 //2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384

比如数字8,取 log2(8) - 1 = 2 ,颜色代号是6,对应黄色

然后写上设置文本颜色的函数

void Set_Color(int color)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | color);
}

关于这个具体的解释可以参考我的另一篇博客 控制台输出彩色文本

接下来写display函数

void display()
{system("cls");  //调用cmd清屏for (int i = 0; i < H; i++){for (int j = 0; j < W; j++){if (board[i][j] == 0) printf("     ");else {Set_Color(color[ (int)log2(board[i][j])-1 ]); //根据数字设置颜色printf("%-5d", board[i][j]);Set_Color(7);}if (j < 3) putchar('|');}putchar('\n');for (int r = 0; r < 23; r++) putchar('-');putchar('\n');}printf("当前得分: %d",score);
}

很简单,只需要遍历一下4x4的board数组依次打印出里面的数字即可。
打印每个数字之前使用Set_Color(color[ (int)log2(board[i][j])-1 ]); 设置一下其专属颜色即可
printf("%-5d", board[i][j]); 表示左对齐的格式化输出,数字宽度为5,长度不足5的数字用空格在右边补齐,以此来保证我们用-|画的格子是整齐的
最后显示一下当前的得分,这个score的累加规则下一节会写到。


3.数字的生成与合并

这是2048最核心的部分。首先我们来考虑相对简单的数字生成。
这里我们只生成2,2048经典版是10%的概率生成4,90%的概率生成2
首先我们肯定得在空白的地方生成,也就是board数组中为0的位置
遍历一遍,如果是0就n++,统计有多少个空格子。统计完之后我们只需要生成一个1~n的随机数就可以决定在哪个空格子里生成2
这里也可以把空格子的位置记录下来,就不需要第二次遍历了,但是考虑到最多只有16个格子,我这里就直接遍历了:

void newNum()
{int flag = 0;int n = 0;for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)if (board[i][j] == 0)n++;srand((unsigned)time(NULL));if (n == 0) return;int end = rand() % n;int t = 0;for (int i = 0; i < 4 && !flag; i++)for (int j = 0; j < 4 && !flag; j++){if (board[i][j] == 0){if (t == end) {board[i][j] += 2;flag = 1;}else t++;}}
}

这里t是从0开始的,因为rand()%n返回的是0~n-1的数字,如果t从1开始那么第n个数会对不上。
因为可能出现场面上数字满了但是还没结束游戏(还有能合并的数字)的情况,所以一定得判断n是否等于0:if (n == 0) return;,不然对0取模会引发异常。

然后就是数字的合并了。
这里我为了省事引入dx和dy数组来表示纵向和横向的移动:

const int dx[4] = { -1,1,0,0 };  //依次对应上下左右
const int dy[4] = {  0,0,-1,1 };

我们可以观察一下2048合并的规律:
如果你按下右键,所有的数字向右移动,然后按从右往左的顺序合并,且一次移动不会合并多次(不会出现4 2 2移动一次就变成8)
可见我们需要反向遍历:如果是向右移动,那么从最右边开始往左找,对每个格子重复以下操作:
如果当前数字不为0,且右边没有数字可以合并但是还有空位,则移动到空位。
如果当前数字不为0,且右边有数字可以合并,则合并(当前格子置为0,合并的格子翻倍),这里做完要继续往左边走并且把上一次合并的位置设置为底部,即不能再次合并了
按照以上逻辑我们很容易写出控制合并的函数:

void move(int dir)
{int x=0,y=0;if (dy[dir]) //水平移动{for (int i = 0; i < H; i++) //遍历每一行{y = (dy[dir] == 1) ? 3 : 0;int j = y;int top = y;while (abs(j - y) < 3) {j -= dy[dir];if (board[i][top] == 0 &&board[i][j]!=0){board[i][top] = board[i][j];board[i][j] = 0;}else if (board[i][top]!=0 && board[i][j]==board[i][top]){board[i][top] *= 2;score += board[i][top];board[i][j] = 0;}else if (board[i][top] * board[i][j] != 0 && board[i][top] != board[i][j]){top -= dy[dir];if (j != top){board[i][top] = board[i][j];board[i][j] = 0;}}}}}else if (dx[dir]) //垂直移动{for (int j = 0; j < W; j++) //遍历每一列{x = (dx[dir] == 1) ? 3 : 0;int i = x;int top = x;while (abs(i - x) < 3) {i -= dx[dir];if (board[top][j] == 0 && board[i][j] != 0){board[top][j] = board[i][j];board[i][j] = 0;}else if (board[top][j] != 0 && board[i][j] == board[top][j]){board[top][j] *= 2;score += board[top][j];board[i][j] = 0;}else if (board[top][j] * board[i][j] != 0 && board[top][j] != board[i][j]){top -= dx[dir];if (i != top){board[top][j] = board[i][j];board[i][j] = 0;}}}}}}

传入的参数dir是读取键盘按键识别来的,下一节会提到。
y = (dy[dir] == 1) ? 3 : 0; 是找到遍历的起点,如果是向右移动则是找最右边也就是3,向左移动是0
top变量记录当前的底部,来方便数字的移动,并且能防止多次合并。
因为我使用的是dx和dy,所以垂直和水平方向的移动需要分别写出来。
也可以用一个二维数组代替dx和dy来表示四个方向的单位位移,这样就只需要写一遍。
score存储的是当前得分,积分规则是2+2=4,则加4分,加上合并出的数字的值


4.键盘控制 / 游戏主循环

因为2048不需要快速而频繁的键盘操作,我们使用_getch函数即可(如果需要快速低延迟的按键读取,则可以使用GetAsyncKeyState函数,可以参考#部分完整代码中注释掉的宏函数)
为了方便我定义了一个keymap数组来储存四个方向键对应的值:

const int keymap[4] = {72,80,75,77}; //依次为上下左右

按照游戏的流程:生成数字->显示->玩家键盘按键->移动,合并数字为一个周期,不停的重复直到判定游戏结束为止即可

void play()
{newNum();display();while (true){int ch = _getch();for (int i = 0; i < 4; i++)if (ch == keymap[i]) {move(i); if(judge())return;newNum();display();}Sleep(10);     //防止连按}
}

注意这里判断游戏是否结束应该在生成数字前判断


5.游戏结束判定

只要场面上没有空格子或者能合并的格子(相邻格子数字相等), 那么遍历找这两种情况就行了,如果没找到就是游戏结束了。

bool judge()   //0:ok  1:gameover
{for (int i = 0; i < H; i++)for (int j = 0; j < W; j++){if (board[i][j] == 0)return false;else if(i<3 && j<3)if (board[i][j] == board[i + 1][j] || board[i][j]==board[i][j+1])return false;}return true;
}

#.完整代码

#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>//#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)const int W = 4;
const int H = 4;
int board[H][W];  //H:高  W:宽   board[2][1] 表示第二行第二个
int score;
const int keymap[4] = {72,80,75,77}; //依次为上下左右
const int dx[4] = { -1,1,0,0 };  //依次对应上下左右
const int dy[4] = {  0,0,-1,1 };//2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384  (超过这个数字的从头开始)
const int color[] = {15,8,6,14, 4, 2,12 ,5,  13, 3,   10,  11,  1,   9, };/*  颜色说明:* 0 = 黑色       8 = 灰色* 1 = 蓝色       9 = 淡蓝色* 2 = 绿色       10 = 淡绿色* 3 = 浅绿色     11 = 淡浅绿色* 4 = 红色       12 = 淡红色* 5 = 紫色       13 = 淡紫色* 6 = 黄色       14 = 淡黄色* 7 = 白色       15 = 亮白色*/
bool judge()   //0:ok  1:gameover
{for (int i = 0; i < H; i++)for (int j = 0; j < W; j++){if (board[i][j] == 0)return false;else if(i<3 && j<3)if (board[i][j] == board[i + 1][j] || board[i][j]==board[i][j+1])return false;}return true;
}void Set_Color(int color)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | color);
}void display()
{system("cls");for (int i = 0; i < H; i++){for (int j = 0; j < W; j++){if (board[i][j] == 0) printf("     ");else {Set_Color(color[ (int)log2(board[i][j])-1 ]); //根据数字设置颜色printf("%-5d", board[i][j]);Set_Color(7);}if (j < 3) putchar('|');}putchar('\n');for (int r = 0; r < 23; r++) putchar('-');putchar('\n');}printf("当前得分: %d",score);
}void move(int dir)
{int x=0,y=0;if (dy[dir]) //水平移动{for (int i = 0; i < H; i++) //遍历每一行{y = (dy[dir] == 1) ? 3 : 0;int j = y;int top = y;while (abs(j - y) < 3) {j -= dy[dir];if (board[i][top] == 0 &&board[i][j]!=0){board[i][top] = board[i][j];board[i][j] = 0;}else if (board[i][top]!=0 && board[i][j]==board[i][top]){board[i][top] *= 2;score += board[i][top];board[i][j] = 0;}else if (board[i][top] * board[i][j] != 0 && board[i][top] != board[i][j]){top -= dy[dir];if (j != top){board[i][top] = board[i][j];board[i][j] = 0;}}}}}else if (dx[dir]) //垂直移动{for (int j = 0; j < W; j++) //遍历每一列{x = (dx[dir] == 1) ? 3 : 0;int i = x;int top = x;while (abs(i - x) < 3) {i -= dx[dir];if (board[top][j] == 0 && board[i][j] != 0){board[top][j] = board[i][j];board[i][j] = 0;}else if (board[top][j] != 0 && board[i][j] == board[top][j]){board[top][j] *= 2;score += board[top][j];board[i][j] = 0;}else if (board[top][j] * board[i][j] != 0 && board[top][j] != board[i][j]){top -= dx[dir];if (i != top){board[top][j] = board[i][j];board[i][j] = 0;}}}}}}void newNum()
{int flag = 0;int n = 0;for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)if (board[i][j] == 0)n++;srand((unsigned)time(NULL));if (n == 0) return;int end = rand() % n;int t = 0;for (int i = 0; i < 4 && !flag; i++)for (int j = 0; j < 4 && !flag; j++){if (board[i][j] == 0){if (t == end) {board[i][j] += 2;flag = 1;}else t++;}}
}void play()
{newNum();display();while (true){int ch = _getch();for (int i = 0; i < 4; i++)if (ch == keymap[i]) {move(i);if (judge())return;newNum();display();}Sleep(10);     //防止连按}}void init()
{system("mode con:cols=24 lines=10");memset(board, 0, sizeof(board));score = 0;
}int main()
{init();play();system("cls");printf("\n\n\tGAME OVER!");printf("\n\n\tSCORE:%d", score);getchar();
}

手把手教你编写C++控制台小游戏 : 2048相关推荐

  1. python入门小游戏之跳一跳_从零基础开始,用python手把手教你玩跳一跳小游戏,直接打出高分...

    这对于很多人来说,可能是已经过时的游戏,但是对于python入门来说,却是一个非常值得学习的项目. TONOW收集了很多有关python入门的项目案例,包含了相应的教程和源码,哪怕你从未接触编程,也通 ...

  2. python 玩公众号游戏_从零基础开始,用python手把手教你玩跳一跳小游戏,直接打出高分...

    这对于很多人来说,可能是已经过时的游戏,但是对于python入门来说,却是一个非常值得学习的项目. 我们收集了很多有关python入门的项目案例,包含了相应的教程和源码,哪怕你从未接触编程,也通过这些 ...

  3. pixi 小游戏_手把手教你制作一款小游戏【超好玩!】

    想必大家小时候都听说过或玩过4399小游戏(这句话好像暴露了年龄),在当时电脑不算很普及,且没有那么多网游.大型单机游戏的时代,数量众多且种类丰富的网页小游戏使我们的童年增添了非常多的乐趣.诸如黄金矿 ...

  4. 拾起童年的回忆 - 手把手教你制作飞机大战小游戏

    拾起童年的回忆 最记得小学时,每逢放学便会打开电视机,接上红白机/小霸王,插上一张游戏卡带,魂斗罗.超级玛丽.冒险岛.足球小将,拳王.飞机大战.雪人兄弟--,这些游戏到现在还是如数家珍,一张游戏卡可以 ...

  5. C++ · 手把手教你写一个扫雷小游戏

    Hello,大家好,我是余同学.这两个月真是太忙了,无暇给大家更新文章- 暑假不是写了个扫雷小游戏吗(Link)?考虑到很多同学对代码没有透彻的理解,那么,这篇文章,我们来详细分析一下代码. 我们分为 ...

  6. 如何用python开发游戏_手把手教你用Python完成一个控制台小游戏-阿里云开发者社区...

    很多人想学Python程序设计或者已经了解过一点Python程序设计基础,却没办法开发出一个项目. 今天,通过演示一个简单的控制台小游戏制作,手把手教你如何用Python编写一个游戏程序,即便你是个新 ...

  7. 手把手教你编写游戏模拟器 - Chip8篇(1)

    转自 http://www.cnblogs.com/YiranXie/p/3439934.html 手把手教你编写游戏模拟器 - Chip8篇(1) 手把手教你编写游戏模拟器 - Chip8篇 翻译整 ...

  8. 译文1 手把手教你用cocos2d开发iphone游戏

    手把手教你用cocos2d开发iphone游戏-译文1 (2011-07-07 16:37:00) Learning Cocos2d – A Hands On Guide to Building iO ...

  9. 写字机上位机c语言,易懂 | 手把手教你编写你的第一个上位机

    一.前言 大家好,我是ZhengN,本次来教大家编写一个基于QT的简单的上位机. 学习一个新的东西我们都从最基础地实例开始,比如学习C语言我们会从编写一个hello程序开始.学习嵌入式我们从点灯开始. ...

最新文章

  1. oss对象存储服务操作
  2. C语言如何根据数据类型来处理不同的数据?
  3. 使用ueditor小结
  4. 真执着 卡巴斯基和Palo Alto找到了BlackEnergy和ExPetr的相似代码
  5. Web 安全与 Rational AppScan 入门
  6. Eclipse/STS 在线安装阿里java代码规约插件
  7. 游戏服务器框架 Leaf/go
  8. java日志分级概要
  9. 计算机组成原理课程(唐朔飞)最全笔记
  10. 从随机生成九宫格至随机生成数独游戏再至用回溯法实现数独的解
  11. java环境安装1359错误_iserver java api接口频发错误
  12. /usr/bin与/usr/local/bin/区别总结
  13. 闲鱼商品理解数据分析平台——龙宫
  14. VMware虚拟机中Windows11无法连接网络
  15. Java、JSP教师信息管理系统
  16. 用Python-opencv快速实现人脸识别功能(从零开始教你)(复制粘贴即可用)
  17. num_workers
  18. bzoj1038: [ZJOI2008]瞭望塔
  19. Win7电脑系统录屏功能使用教程分享
  20. linux下安装codeblocks及写完程序之后编译成功但无法运行的原因

热门文章

  1. 微服务架构原理与开发实战
  2. 02-MyBatis-CRUD-配置文件、参数封装、mybatisX插件、动态sql, 简单的用注解开发
  3. [转帖]SPU、SKU、ID,它们都是什么意思,三者又有什么区别和联系呢?
  4. 谷歌版 ChatGPT 首秀就翻车...
  5. 青萍之末 光伏“抢屋顶”风潮来袭
  6. 基于DM642 RAW采集格式的视频驱动开发及应用
  7. 软件工程概论总结及其对建民欧巴的评价和建议
  8. 【Java导出zip格式压缩包】
  9. 计算机学院表白情书,【计算机学院】大学生的别样表白情书——计算机学院成功举办南华大学第一届代码情书大赛...
  10. 【CF Div. 1 + Div. 2】A. Divide and Multiply、B. William the Vigilant