技术标签: 专栏 - 项目实战开发 小游戏 c语言 2048 学习 - C/C++ 序一 技法
目录
Win10专业版x64 VS2015
这是2017年9或10月份写的 一个练手的,和上一篇Flappy Bird 是一起的, 留以后一个永久的回忆
==================================================================
==================================================================
游戏的规则很简单,你需要控制所有方块向同一个方向运动,两个相同数字的方块撞在一起之后合并成为他们的和,每次操作之后会在空白的方格处随机生成一个2或者4(生成2的概率要大一些),最终得到一个“2048”的方块就算胜利了。
1、方块移动和合并算法。
主要思想:把游戏数字面板抽象成4行4列的二维数组a[4][4],值为0的位置表示空方块,其他表示对应数字方块。把每一行同等对待,只研究一行的移动和合并算法,然后可以通过遍历行来实现所有行的移动合并算法。在一行中,用b[4]表示一行的一位数组,使用两个下标变量来遍历列项,这里使用j和k,其中j总在k的后面,用来寻找k项后面第一个不为0的数字,而k项用于表示当前待比较的项,总是和j项之间隔着若干个数字0,或者干脆紧挨着。不失一般性,考虑往左滑动时,初始事情况下j等于1,而k等于0,接着判断j项数字是否大于0,若是,则判断j项和k项数字的关系,分成3种情况处理,分别是P1: ,P2: b[k]==0和P3: b[k]!=0且b[k]!=b[j];若否,则j自加1,然后继续寻找k项后面第一个不为0的数字。其中P1,P2和P3分别对应如下:
P1:b[k]==b[j],则b[k] = 2 * b[k](说明两数合并了),且b[j] = 0(合并之后要将残留的j项值清零),接着k自加1,然后进行下一次循环。
P2:b[k]==0,则表示b[j]之前全是空格子,此时直接移动b[j]到k的位置,也就是b[k] = b[j],然后b[j] = 0(移动后将残留的j项值清零),接着k值不变,然后进行下一次循环。
P3:b[k]!=0且b[k]!=b[j],则表示两数不相等且都不为0,此时将两数靠在一起,也就是b[k+1] = b[j]。接着分两种小情况,若j!=k+1,则b[j] = 0(移动后将残留的j项值清零);若否,则表示两数原先就靠在一起,则不进行特殊处理(相当于未移动)。接着k自加1,然后进行下一次循环。
举一个P1的例子,流程表示如下:
一行内移动合并算法描述如下(此例为左移情况,其他方向与之类似,区别仅仅是遍历二维数组的行项和列项的方式)。
==================================================================
源码链接:https://pan.baidu.com/s/1mjBb6rQ 密码:olmj
#include "stdafx.h"
#include <time.h>
#include <conio.h>
#define FRAMERWHIDTH 20 //一个小的格子的宽度
#define FRAMERHIGHT 20 //一个小的格子的高度
int Bound[4][4]; //抽象为地图
int RandNum_nFalge; //是否添加一个新的随机数标志 1--->产生新的随机数 0--->不必产生新的随机数
int Gameover_nFlage; //是否游戏结束 1--->游戏失败结束 2---->游戏胜利结束 0--->继续正常(游戏未结束)
int Score; //游戏分数
//数组的移动 下标 k,j; 其中j为k后面的第一个不为0的数字
//左移动
void MoveLeft()
{
for (int i = 0; i < 4; i++) //一共有4行
{
for (int k = 0, j = 1; j < 4; j++) //每一行都是有4列(个数字)
{
if (Bound[i][j] > 0) //在一行中,只判k只有遇到的第一个非0的个数字 (j>0)
{
if(Bound[i][k] == Bound[i][j]) //情况一:k == j && j >0
{
Score += Bound[i][k++] *= 2;
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
else if (Bound[i][k] == 0) //情况二:k == 0 && j>0
{
Bound[i][k] = Bound[i][j];
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
else //情况三:k != j &&j >0
{
Bound[i][++k] = Bound[i][j];
if (k != j)
{
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
}
}
}
}
}
//右移动
void MoveRight()
{
for (int i = 0; i < 4; i++) //一共有4行
{
for (int k = 3, j = 2; j >= 0; j--) //每一行都是有4列(个数字)
{
if (Bound[i][j] > 0) //在一行中,只判k只有遇到的第一个非0的个数字 (j>0)
{
if (Bound[i][k] == Bound[i][j]) //情况一:k == j && j >0
{
Score += Bound[i][k--] *= 2;
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
else if (Bound[i][k] == 0) //情况二:k == 0 && j>0
{
Bound[i][k] = Bound[i][j];
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
else //情况三:k != j &&j >0
{
Bound[i][--k] = Bound[i][j];
if (k != j)
{
Bound[i][j] = 0;
RandNum_nFalge = 1;
}
}
}
}
}
}
//上移动
void MoveUp()
{
for (int i = 0; i < 4; i++) //一共有4列
{
for (int k = 0, j = 1; j < 4; j++) //每一列都是有4个数
{
if (Bound[j][i] > 0) //这个里面j为时刻变化的 数组行, i为每一轮变化一次的数组的列(这里面注意体会s数组的i和j的循环和数组里面的区别)
{
if (Bound[j][i] == Bound[k][i]) //情况一:k == j && j >0
{
Score += Bound[k++][i] *= 2;
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
else if (Bound[k][i] == 0) //情况二:k == 0 && j>0
{
Bound[k][i] = Bound[j][i];
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
else //情况三:k != j &&j >0
{
Bound[++k][i] = Bound[j][i];
if (k != j)
{
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
}
}
}
}
}
//下移动
void MoveDown()
{
for (int i = 0; i < 4; i++) //一共有4列
{
for (int k = 3, j = 2; j >= 0; j--) //每一列都是有4个数
{
if (Bound[j][i] > 0) //这个里面j为时刻变化的 数组行, i为每一轮变化一次的数组的列(这里面注意体会s数组的i和j的循环和数组里面的区别)
{
if (Bound[j][i] == Bound[k][i]) //情况一:k == j && j >0
{
Score += Bound[k--][i] *= 2;
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
else if (Bound[k][i] == 0) //情况二:k == 0 && j>0
{
Bound[k][i] = Bound[j][i];
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
else //情况三:k != j &&j >0
{
Bound[--k][i] = Bound[j][i];
if (k != j)
{
Bound[j][i] = 0;
RandNum_nFalge = 1;
}
}
}
}
}
}
//控制游戏的键盘输入
void KeyboardInput()
{
//char ch;
switch (_getch())
{
case 'w':
case 'W':
MoveUp();
break;
case 'a':
case 'A':
MoveLeft();
break;
case 's':
case 'S':
MoveDown();
break;
case 'd':
case 'D':
MoveRight();
break;
default:
break;
}
}
//绘画出一行数字
void ShowNum_a_Line(int i)
{
printf_s(" ┃ ┃ ┃ ┃ ┃\n");
printf_s(" ┃");
for (int j = 0; j < 4; j++)
{
if (0 != Bound[i][j])
{
printf_s(" %5d ┃", Bound[i][j]);
}
else
{
printf_s(" ┃", Bound[i][j]);
}
}
printf_s("\n");
printf_s(" ┃ ┃ ┃ ┃ ┃\n");
}
//检测空余的各自的个数
int nCountNullNum()
{
int n = 0;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (0 == Bound[i][j])
n++;
}
}
return n;
}
//生成随机数(该函数只赋值一个空格)
void RandNum()
{
srand((unsigned int)time(NULL));
int n = rand() % nCountNullNum();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (0 == n) //第n个为0的个子
{
Bound[i][j] = rand() % 3 ? 2 : 4; //数组随机生成的第n个为0的空格子,随机赋值的2的该路是4的2倍
goto a;
}
if (0 == Bound[i][j]) //数到第那n个位0的空格子
{
n--;
}
}
}
a:;
RandNum_nFalge = 0;
}
void ShowWindows()
{
printf_s("\n\n\n 游戏名字:2048 分数:%-6d 开发者:诗情画意\n", Score);
printf_s(" ------------------------------------------------------------------------------------------\n");
printf_s(" ┏━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓\n");
for (int i = 0; i < 4; i++)
{
if (i < 3)
{
ShowNum_a_Line(i);
printf_s(" ┣━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━━━┫\n");
}
if ( 3 == i)
{
ShowNum_a_Line(i);
printf_s(" ┗━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━━┛\n");
}
}
printf_s("\n\n W:↑ A:← S:↓ D:→\n");
}
//游戏开始之前随机初始化两个格子
void StartDate()
{
RandNum();
RandNum();
}
//游戏是否结束
void CheckGameOver()
{
int n1 = 0, n2 = 0;//横着、竖着两个两个不相等的 次数 的计数器
for (int i = 0; i < 4; i++) //横着横着2个进行比较
{
for (int j = 0; j < 3; j++)
{
if (Bound[i][j] != Bound[i][j + 1])
{
n1++; //n1最多只会比较12次
}
if (Bound[i][j] >= 2048) //单独的一个判断单个的Bound[][]是否大于2048,大于就说明游戏结束(胜利)
{
Gameover_nFlage = 2;
}
}
}
for (int i = 0; i < 4; i++) //竖着竖着2个进行比较
{
for (int j = 0; j < 3; j++)
{
if (Bound[j][i] != Bound[j + 1][i])
{
n2++; //n2最多只会比较12次
}
}
}
if (n1 == 12 && n2 == 12)
{
Gameover_nFlage = 1;//游戏结束(游戏失败)
}
}
//开始游戏循环
void StartGame()
{
system("title 2048"); //改控制台标题名称
system("color 0e"); //改控制台标题背景和内容的颜色
//游戏开始位置代码---------------------¥¥(一局游戏完整)
StartDate();
while (true)
{
ShowWindows();
KeyboardInput();
CheckGameOver();
if (1 == Gameover_nFlage) //判断游戏结束的两种方法(Gameover_nFlage ==1 或 ==2)
{
printf_s("游戏失败,GAME OVER!!!\n");
}
if (2 == Gameover_nFlage)
{
printf_s("游戏胜利,GAME SUCCESS!!!\n");
}
if (1 == RandNum_nFalge)
{
RandNum();
}
system("cls");
}
//游戏结束位置代码---------------------¥¥(一局游戏完整)
}
参考其他博客的链接:http://www.cnblogs.com/judgeyoung/p/3760515.html
其他:以后会不定期续写的,且上面得源码都是自己学习过程的一个又一个脚印,希望也可以给你们一些启发,也给自己一个回忆,么么哒φ(>ω<*)
文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr
文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc
文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8
文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束
文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求
文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname
文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立
文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码
文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词
文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限
文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定
文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland