背景
昨天给自己放了个假,除了睡觉贼无聊,然后就开始找事情做了。
搜了一下c语言新手项目,看到2048就开始做了。
实现过程
1. 思路
用一维数组存储数字,每次操作通过对数组数字操作实现。
2. 主流程:
a. 按键检测;
b. 键值处理;
c. 成绩结算;
PS:有点废话。
3. 各函数介绍:
按键检测在main函数里面实现;
a.按键处理
1)通过传入的按键标志位,尝试移动数字。
有效移动,则生成新数字并判断数组是否未满;
未满,显示新数组;
数组满,结束游戏。
无效操作,则跳过。
2)清除标志位。
b.数字移动(上下左右)
以向左移动为例:
1)备份数组(用于判断移动是否有效);
2)对相邻相等项向左合并,例如数组a第一行为“2,0,2,4”,则将a[0] = a[0] + a[2],将a[2]清零,再接着跳过a[0]和a[2]中间数字的比较,直接判断a[3],因为一个数字最多进行一次合并。依次类推合并其他行;
3)拷贝数组的一行;
4)将原数组清零,并把拷贝的数组中非0元素左对齐放回圆数组;
5)比较移动后数组与移动前数组是否完全相等,完全相等则为无效移动,不等则为有效移动。
c.判断数组满
1)统计数组元素0的个数,同时备份数组。
2)原数组中没有0,则对备份数组进行上下左右的移动,若均为无效移动,说明数组已满。
d.随机生成新数字并放入数组空白位置
前面提到用一维数组,就是为了这里实现方便。
1)统计【原数组】中元素为0的下标,放到一个【临时数组】中;
2)随机选择【临时数组】中的一个元素,把【以该元素为下标的原数组元素】改为随机生成的2或4。
e.显示函数
源代码(代码有点乱)
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
#include <time.h>
#define true 1
#define false 0
void key_deal(char * state, unsigned int * buf);
char new_buf(unsigned int * p);
void game_show(unsigned int * p);
char key_left(unsigned int * p);
char key_right(unsigned int * p);
char key_up(unsigned int * p);
char key_down(unsigned int * p);
char buf_full(unsigned int * p);
int main(void) {
HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE); //获得标准输入设备句柄
INPUT_RECORD keyrec; //定义输入事件结构体
DWORD res; //定义返回记录
char state = 0, i;
unsigned int buf[16] = {
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
};
srand((unsigned)time(NULL)); //以程序运行时间为随机数种子
new_buf(buf);
game_show(buf);
while(state != -1) {
//按键扫描
ReadConsoleInput(handle_in, &keyrec, 1, &res); //读取输入事件
//如果当前事件是键盘事件
if (keyrec.EventType == KEY_EVENT) {
//当前事件是虚拟按键左方向键且当前状态是按下,不是释放
if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_LEFT
&& keyrec.Event.KeyEvent.bKeyDown == true) {
//printf("< ");
state = 1;
}
//当前事件是虚拟按键右方向键且当前状态是按下,不是释放
else if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT
&& keyrec.Event.KeyEvent.bKeyDown == true) {
//printf("> ");
state = 2;
}
//当前事件是虚拟按键上方向键且当前状态是按下,不是释放
else if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_UP
&& keyrec.Event.KeyEvent.bKeyDown == true) {
//printf("^ ");
state = 3;
}
//当前事件是虚拟按键下方向键且当前状态是按下,不是释放
else if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_DOWN
&& keyrec.Event.KeyEvent.bKeyDown == true) {
//printf("v ");
state = 4;
}
//当前事件是虚拟按键下 Q 键且当前状态是按下,不是释放
else if (keyrec.Event.KeyEvent.wVirtualKeyCode == 'Q' && keyrec.Event.KeyEvent.bKeyDown == true) {
//退出
state = -1;
}
}
key_deal(&state, buf); //按键处理
}
for(i=0; i<16; i++) {
if(buf[i] > buf[0])
buf[0] = buf[i];
}
printf("\n您最后得分是:%d\n", buf[0]);
printf("\n==============游戏结束==============\n\n");
system("pause");
return 0;
}
void key_deal(char *state, unsigned int * buf) {
if(*state!=0 && *state!=-1) {
char flag = 0;
switch(*state) {
case 1 :
if(key_left(buf))
flag = 1; //左
break;
case 2 :
if(key_right(buf))
flag = 1; //右
break;
case 3 :
if(key_up(buf))
flag = 1; //上
break;
case 4 :
if(key_down(buf))
flag = 1; //下
break;
default : printf("ERROR : 按键判断出错\n");
}
if(*state>0 && *state<5 && flag==0) {
new_buf(buf); //在空白处随机生成2或4
system("cls"); //清屏
game_show(buf); //显示处理后的结果
if(!buf_full(buf)) { //返回值为0说明数组满了,游戏结束
*state = -1;
return;
}
}
*state = 0; //清除按键标志位
}
}
//显示数组内容
void game_show(unsigned int * p) {
char i, j;
printf("===========【菜鸡版2048】===========\n\n");
printf("操作说明:\n");
printf("1. 方向键控制数字移动\n");
printf("2. 蹲下按 Q 退出游戏\n\n");
printf("祝您游戏愉快,遇到bug请忽略!\n\n");
printf("====================================\n\n");
for(i=0; i<16; i+=4) {
printf(" |");
for(j=i; j<i+4; j++) {
if(p[j] != 0) {
printf("%7d|", p[j]);
}
else
printf(" |"); //7个空格
}
printf("\n\n");
}
printf("====================================\n");
}
//左方向键处理函数,为有效操作返回0,否则返回1
char key_left(unsigned int * p) {
char i, j, n;
unsigned int buf[4];
unsigned int tmp[16];
char count = 0;
//备份数组
for(i=0; i<16; i++)
tmp[i] = p[i];
for(i=0; i<16; i+=4) {
//合并相邻的相等项
for(j=i; j<i+4; j++) {
for(n=1; n<(i+4)-j; n++) {
if(p[j+n]!=0) {
if(p[j]==p[j+n]) {
p[j] = p[j] + p[j+n];
p[j+n] = 0;
j = j + n;
break;
}
else
break;
}
}
}
count = 0;
//计算第i行有多少个0
for(n=0; n<4; n++) {
if(p[i+n] == 0)
count++;
buf[n] = p[i+n];
}
//如果i行不全为0
if(count < 4) {
//获得非零元素左对齐的数组
for(n=0, j=i; n<4; n++) {
if(buf[n] != 0) {
p[i+n] = 0; //把原数组中需要移动的元素所在位置清零
p[j++] = buf[n]; //将非零元素左对齐
}
}
}
}
//检查移动前移动后是否有变化,没变化说明不能移动
for(i=0, count=0; i<16; i++)
if(tmp[i] == p[i])
count++;
if(count == 16)
return 1;
else
return 0;
}
//右方向键处理函数,为有效操作返回0,否则返回1
char key_right(unsigned int * p) {
char i, j, n;
unsigned int buf[4];
unsigned int tmp[16];
char count = 0;
for(i=0; i<16; i++)
tmp[i] = p[i];
for(i=15; i>-1; i-=4) {
//合并相邻的相等项
for(j=i; j>i-4; j--) {
for(n=1; n<j-(i-4); n++) {
if(p[j-n]!=0) {
if(p[j]==p[j-n]) {
p[j] = p[j] + p[j-n];
p[j-n] = 0;
j = j - n;
break;
}
else
break;
}
}
}
count = 0;
//计算第i行有多少个0
for(n=0; n<4; n++) {
if(p[i-n] == 0)
count++;
buf[3-n] = p[i-n];
}
//如果i行不全为0
if(count < 4) {
//获得非零元素右对齐的数组
for(n=3, j=i; n>-1; n--) {
if(buf[n] != 0) {
p[i-(3-n)] = 0; //把原数组中需要移动的元素所在位置清零
p[j--] = buf[n]; //将非零元素右对齐
}
}
}
}
//检查移动前移动后是否有变化,没变化说明不能移动
for(i=0, count=0; i<16; i++)
if(tmp[i] == p[i])
count++;
if(count == 16)
return 1;
else
return 0;
}
//上方向键处理函数,为有效操作返回0,否则返回1
char key_up(unsigned int * p) {
char i, j, n;
unsigned int buf[4];
unsigned int tmp[16];
char count = 0;
for(i=0; i<16; i++)
tmp[i] = p[i];
for(i=0; i<4; i++) {
//合并相邻的相等项
for(j=i; j<i+13; j+=4) {
for(n=4; n<(i+13)-j; n+=4) {
if(p[j+n]!=0) {
if(p[j]==p[j+n]) {
p[j] = p[j] + p[j+n];
p[j+n] = 0;
j = j + n;
break;
}
else
break;
}
}
}
count = 0;
//计算第i行有多少个0
for(n=0; n<4; n++) {
if(p[i+(n*4)] == 0)
count++;
buf[n] = p[i+(n*4)];
}
//如果i行不全为0
if(count < 4) {
//获得非零元素顶对齐的数组
for(n=0, j=i; n<4; n++) {
if(buf[n] != 0) {
p[i+(n*4)] = 0; //把原数组中需要移动的元素所在位置清零
p[j] = buf[n]; //将非零元素顶对齐
j += 4;
}
}
}
}
//检查移动前移动后是否有变化,没变化说明不能移动
for(i=0, count=0; i<16; i++)
if(tmp[i] == p[i])
count++;
if(count == 16)
return 1;
else
return 0;
}
//上方向键处理函数,为有效操作返回0,否则返回1
char key_down(unsigned int * p) {
char i, j, n;
unsigned int buf[4];
unsigned int tmp[16];
char count = 0;
for(i=0; i<16; i++)
tmp[i] = p[i];
for(i=15; i>11; i--) {
//合并相邻的相等项
for(j=i; j>i-13; j-=4) {
for(n=4; n<j-(i-13); n+=4) {
if(p[j-n]!=0) {
if(p[j]==p[j-n]) {
p[j] = p[j] + p[j-n];
p[j-n] = 0;
j = j - n;
break;
}
else
break;
}
}
}
count = 0;
//计算第i行有多少个0
for(n=0; n<4; n++) {
if(p[i-(n*4)] == 0)
count++;
buf[3-n] = p[i-(n*4)];
}
//如果i行不全为0
if(count < 4) {
//获得非零元素底对齐的数组
for(n=3, j=i; n>-1; n--) {
if(buf[n] != 0) {
p[i-(12-(n*4))] = 0; //把原数组中需要移动的元素所在位置清零
p[j] = buf[n]; //将非零元素底对齐
j -= 4;
}
}
}
}
//检查移动前移动后是否有变化,没变化说明不能移动
for(i=0, count=0; i<16; i++)
if(tmp[i] == p[i])
count++;
//count = 16说明数组没变,移动无效
if(count == 16)
return 1;
else
return 0;
}
//产生新的数字,为有效操作返回0,否则返回1
char new_buf(unsigned int * p) {
unsigned int tmp[16];
char n=0, count=-1;
//将等于0的元素下标放在tmp中
for(n=0; n<16; n++) {
if(p[n] == 0)
tmp[++count] = n;
}
count++;
if(count != 0) {
//生成[0, count)的随机数,即在p随机选取一个0,将其随机改为2或4
p[tmp[rand()%count]] = (rand()%2) ? 2 : 4;
return 0;
}
else
return 1;
}
//数组是否满,满返回0,否则返回1
char buf_full(unsigned int * p) {
/*
* 1. 数组中没有0
* 2. 对数组进行上下左右移动都不变
*/
char count = 0, i;
unsigned int tmp[16];
for(i=0; i<16; i++) {
if(p[i] == 0)
count++;
tmp[i] = p[i];
}
if(count == 0) {
if(!key_left(tmp))
return 1; //向左为有效移动,数组没满
else if(!key_right(tmp))
return 1; //向右为有效移动,数组没满
else if(!key_up(tmp))
return 1; //向上为有效移动,数组没满
else if(!key_down(tmp))
return 1; //向下为有效移动,数组没满
else
return 0; //数组满了
}
return 1;
}
运行截图(皮了一下)