【C语言】游戏 2048
实现函数列表
游戏棋盘结构体
菜单选项枚举
查找可合并块偏移量结构体
menu()菜单函数
InitBoard()初始化棋盘函数
PrintBoard()显示棋盘函数
IsWin()判断输赢函数
IsRound()判断盘内块可否合并函数
AddBlock()随机增加方块函数
MergeBlock()合并方块函数
PlayAct()根据玩家行动调整方块函数
RecallBoard()回溯一步函数
Game()游戏实现函数
目前已知BUG
1.回溯后行动一步再回溯时,无法回溯到上一步棋盘状态。
2.行动时没有棋子改变位置也会随机增加块。
正在解决……
如果有小伙伴有好的思路,万望能分享!
实现思路
在游戏中玩家可控制棋盘中棋子向四个方向滑动,有相同的就合并。合并出2048方块即获胜。
首先这里有几个难点以及我的解决方法:
1.判断盘内块可否合并
一个块可以和上下左右四个方向合并,在做判断时,可以从数组第一个方块开始判断,只判断它他能否和它右边的方块合并,一行判断完再判断下一行,用递归来实现。
2.判断根据玩家指令方向上的方块是否可以合并
这里我为了减少代码量,构造了一个偏移量结构体,并定义了一个偏移量二维数组,数组的第一维是分别向四个方向判断的方向,数组的第二维则是这个方向上判断需要偏移的所有可能。
(这样就可以不需要写很多判断语句)
3.回溯上一次棋盘情况
因为2048这个游戏只能退回一步,且不能让玩家在游戏过程中频繁的确认是否反悔,所以我这里定义了两个和棋盘数组相同的数组,一个中间数组,一个回溯数组,玩家不选择反悔时,就先将当前棋盘的情况存入中间数组,在玩家进行下一次操作后,再将中间数组的数复制到回溯数组中去。也就是回溯数组中记录的棋盘状态永远比中间数组的慢一拍,整个流程如下图:
效果展示
头文件
#ifndef __2048_H__
#define __2048_H__
#define _CRT_SECURE_NO_WARNINGS 1
#define SIZE_BOARD 4 //游戏棋盘长宽大小
#define INIT_BLOCK 2 //游戏开始时棋盘中有数字的块
#include <stdio.h>
#include <time.h>
#include <conio.h>
#include <Windows.h>
enum MENU //菜单选项枚举
{
EXIT,
PLAY
};
typedef struct DISPLAY //游戏棋盘结构体
{
int score; //分数
int count; //棋盘中非零棋子的个数
int outscore; //回溯分数
int board[SIZE_BOARD][SIZE_BOARD]; //棋盘
int tmp[SIZE_BOARD][SIZE_BOARD]; //中间棋盘
int recall[SIZE_BOARD][SIZE_BOARD]; //中间棋盘
int flag[SIZE_BOARD][SIZE_BOARD]; //使一个方块在一回合内只合并一次,已合并过置1,反之
}DISPLAY, *PDISPLAY;
typedef struct POS //定义查找合并块的偏移量
{
int x;
int y;
}POS;
void menu(); //菜单
void InitBoard(PDISPLAY GameBoard); //初始化游戏棋盘
void PrintBoard(PDISPLAY GameBoard); //显示棋盘
int IsWin(PDISPLAY GameBoard); // 判断是输是赢
int IsRound(PDISPLAY GameBoard, int x, int y); //判断棋盘内的数周围有没有可以相加的数
void AddBlock(PDISPLAY GameBoard); //每步增加一个方块
void MergeBlock(PDISPLAY GameBoard, int flag, int uplimit, int i, int j); //合并方块
int PlayAct(PDISPLAY GameBoard); //根据玩家行动调整方块
void RecallBoard(PDISPLAY GameBoard, int Command); //回溯一步棋
void Game(); //游戏实现函数
#endif //__2048_H__
功能实现函数文件
#include "2048.h"
POS PosXY[4][SIZE_BOARD - 1] = { //定义默认棋盘大小下可能出现的偏移量 减少代码量
{ { -1, 0 }, { -2, 0 }, { -3, 0 } },
{ { 1, 0 }, { 2, 0 }, { 3, 0 } },
{ { 0, -1 }, { 0, -2 }, { 0, -3 } },
{ { 0, 1 }, { 0, 2 }, { 0, 3 } }
};
void menu() //菜单
{
printf("\t*************************\n");
printf("\t*** 1.开始游戏 ***\n");
printf("\t*** ***\n");
printf("\t*** 0.退出游戏 ***\n");
printf("\t*************************\n");
}
void InitBoard(PDISPLAY GameBoard) //初始化游戏棋盘
{
int count = INIT_BLOCK;
int i = 0;
int j = 0;
while (count--)
{
while (1)
{
int x = rand() % SIZE_BOARD;
int y = rand() % SIZE_BOARD;
if (GameBoard->board[x][y] == 0)
{
int tmp = rand() % 2;
GameBoard->board[x][y] = ((tmp) ? (2) : (4));
break;
}
}
}
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = 0; j < SIZE_BOARD; j++)
{
GameBoard->tmp[i][j] = GameBoard->board[i][j];
}
}
}
void PrintBoard(PDISPLAY GameBoard) //显示棋盘
{
int i = 0;
int j = 0;
printf("\n\t当前分数:%d", GameBoard->score);
printf("\n-----------------------------------\n\n");
printf("\t┏━━┳━━┳━━┳━━┓\n");
for (i = 0; i < SIZE_BOARD; i++)
{
printf("\t┃");
for (j = 0; j < SIZE_BOARD; j++)
{
if (GameBoard->board[i][j] == 0)
{
printf(" ┃");
continue;
}
printf("%4d┃", GameBoard->board[i][j]);
}
if (i < SIZE_BOARD - 1)
{
printf("\n\t┣━━╋━━╋━━╋━━┫\n");
}
}
printf("\n\t┗━━┻━━┻━━┻━━┛\n");
printf("\n\nW A S D 上 下 左 右\tP 反悔\n");
printf("\n\n-----------------------------------\n");
}
int IsWin(PDISPLAY GameBoard) // 判断是输是赢
{
int i = 0;
int j = 0;
GameBoard->count = 0;
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = 0; j < SIZE_BOARD; j++)
{
if (GameBoard->board[i][j] != 0)
GameBoard->count++;
else if (GameBoard->board[i][j] == 2048) //胜利条件2048
return 1;
}
}
if (GameBoard->count == SIZE_BOARD*SIZE_BOARD && IsRound(GameBoard, 0, 0) == 0)
{
return -1;
}
return 0;
}
int IsRound(PDISPLAY GameBoard, int x, int y) //判断棋盘内的数周围有没有可以相加的数
{
int ret = 0;
if (x == SIZE_BOARD - 1)
{
ret = 0;
}
else if (y == SIZE_BOARD - 1)
{
IsRound(GameBoard, x + 1, 0);
}
else if (GameBoard->board[x][y] == GameBoard->board[x][y + 1])
{
ret = 1;
}
else if (GameBoard->board[x][y] != GameBoard->board[x][y + 1])
{
ret += IsRound(GameBoard, x, y + 1);
}
return ret;
}
void AddBlock(PDISPLAY GameBoard) //每步增加一个方块
{
while (1)
{
int x = rand() % SIZE_BOARD;
int y = rand() % SIZE_BOARD;
if (GameBoard->board[x][y] == 0)
{
int tmp = rand() % 2;
GameBoard->board[x][y] = ((tmp) ? (2) : (4));
break;
}
}
}
void MergeBlock(PDISPLAY GameBoard, int flag, int uplimit, int i, int j) //合并方块
{
int k = 0;
if (GameBoard->board[i][j] == 0)
return;
for (k = 0; k < uplimit; k++) //uplimit 控制寻找附近可合并方块的循环次数
{
if (GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y] == 0)
{
if (k == uplimit - 1)
{
GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y] = GameBoard->board[i][j]; //移动方块
GameBoard->board[i][j] = 0;
break;
}
continue;
}
else if (GameBoard->board[i][j] == GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y] &&
GameBoard->flag[i + PosXY[flag][k].x][j + PosXY[flag][k].y] == 0)
{
GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y] *= 2;
GameBoard->board[i][j] = 0; //合并方块
GameBoard->score += GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y]; //分数增加
GameBoard->flag[i + PosXY[flag][k].x][j + PosXY[flag][k].y] = 1;
break;
}
else if (GameBoard->board[i][j] != GameBoard->board[i + PosXY[flag][k].x][j + PosXY[flag][k].y] ||
GameBoard->flag[i + PosXY[flag][k].x][j + PosXY[flag][k].y] == 1)
{
if (k == 0)
{
break;
}
GameBoard->board[i + PosXY[flag][k - 1].x][j + PosXY[flag][k - 1].y] = GameBoard->board[i][j]; //移动方块
GameBoard->board[i][j] = 0;
break;
}
}
}
int PlayAct(PDISPLAY GameBoard) //根据玩家行动调整方块
{
int i = 0;
int j = 0;
int ret = 0;
switch (_getch())
{
case 'w':
case 'W':
{
for (i = 1; i < SIZE_BOARD; i++)
{
for (j = 0; j < SIZE_BOARD; j++)
{
MergeBlock(GameBoard, 0, i, i, j);
}
}
}
break;
case 's':
case 'S':
{
for (i = SIZE_BOARD - 2; i >= 0; i--)
{
for (j = 0; j < SIZE_BOARD; j++)
{
MergeBlock(GameBoard, 1, SIZE_BOARD - i - 1, i, j);
}
}
}
break;
case 'a':
case 'A':
{
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = 1; j < SIZE_BOARD; j++)
{
MergeBlock(GameBoard, 2, j, i, j);
}
}
}
break;
case 'd':
case 'D':
{
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = SIZE_BOARD - 2; j >= 0; j--)
{
MergeBlock(GameBoard, 3, SIZE_BOARD - j - 1, i, j);
}
}
}
break;
case 'p':
case 'P': ret = 1; //悔一步棋
break;
default: ret = -1;
break;
}
return ret;
}
void RecallBoard(PDISPLAY GameBoard, int Command) //回溯一步棋
{
int i = 0;
int j = 0;
if (Command) //悔棋
{
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = 0; j < SIZE_BOARD; j++)
{
GameBoard->board[i][j] = GameBoard->recall[i][j];
}
}
GameBoard->score = GameBoard->outscore;
}
else //将上一步棋存放
{
for (i = 0; i < SIZE_BOARD; i++)
{
for (j = 0; j < SIZE_BOARD; j++)
{
GameBoard->recall[i][j] = GameBoard->tmp[i][j];
GameBoard->tmp[i][j] = GameBoard->board[i][j];
GameBoard->flag[i][j] = 0; //标志位置0,以便下一次使用
}
}
GameBoard->outscore = GameBoard->score;
}
}
void Game() //游戏实现函数
{
int count = 0;
int ret = 0; //玩家行动的返回值
DISPLAY GameBoard = { 0 }; //游戏棋盘
srand((unsigned)time(NULL));
InitBoard(&GameBoard); //初始化棋盘
while (1)
{
system("cls");
PrintBoard(&GameBoard); //打印棋盘
ret = PlayAct(&GameBoard);
if (ret == 1)
{
RecallBoard(&GameBoard, ret);
continue;
}
else if (ret == 0)
{
AddBlock(&GameBoard);
RecallBoard(&GameBoard, 0);
}
ret = IsWin(&GameBoard);
if (ret == 1) //判断输赢 没赢没输返回0,赢了返回1,输了返回-1
{
system("cls");
PrintBoard(&GameBoard); //打印棋盘
printf("\n你厉害,真的赢了!\n");
system("pause");
return;
}
else if (ret == -1)
{
system("cls");
PrintBoard(&GameBoard); //打印棋盘
printf("\n呵呵,再接再厉吧!小伙子!\n");
system("pause");
return;
}
}
}
主函数文件
#include "2048.h"
int main()
{
int input = 0;
do
{
menu();
printf("你想干嘛?:>");
scanf("%d", &input);
switch (input)
{
case PLAY: Game(), system("cls");
break;
case EXIT: printf("\n正在退出游戏\n");
break;
}
} while (input);
return 0;
}