使用软件:vscode
使用函数库:ncurses
ncurses
绘图库sudo apt-get install libncurses5-dev
要实现 2048 游戏目前有两个关键点:
其中第二点借助 ncurses
库可以较容易实现,但是第一点要稍微麻烦些。
第一点的实现思路是,我们创建一个与游戏地图相同维数的数组矩阵,通过数组矩阵来维护 2048 游戏中每个格子的数据与状态,从而玩家的移动操作都可以映射为对数组矩阵的操作。
添加需要的头文件和全局变量
#include
#include
#include
#include
#include
void init(void);//初始化
void draw_grid(void);//绘制窗格(游戏界面)
void draw_one(int x, int y);//绘制单个数字 传入数组的坐标
void play(void);//游戏运行
int cnt_one(int x, int y);
void cnt_value(int* new_x,int* new_y);
int game_over(void);//游戏结束
int board[4][4] = {0};//用来充当棋盘 4*4 16宫格 0表示空格子
int empty;//用来标记剩余空格子的数量(标记是否有相同的数字合并 有则+1)
int old_x, old_y;//用来标记避免在同一位置生成新数字
绘制5行横线,每行由21个-
组成;绘制5列竖线,每列由7个|
组成;
//绘制窗格
void draw_grid(void){
int m, n, x, y;
clear();//将整个屏幕清除
//绘制横线 m行 n列
for(m = 0; m < 9; m += 2){
for(n = 0; n < 21; n++){
move(m,n);//将光标移动至 m,n 的位置.
addch('-');//在当前光标位置输入单个字符,并将光标右移一位。
refresh();//刷新屏幕,使绘制即时生效
}
}
//绘制竖线
for (n = 0; n < 22; n += 5){
for(m = 1; m < 8; m++){
move(m,n);
addch('|');
refresh();
}
}
//绘制数组中的全部数字
for (x = 0; x < 4; x++){
for(y = 0; y < 4; y++)
draw_one(x, y);//绘制单个数字
}
}
//绘制单个数字
void draw_one(int x, int y){
int i, k, j, m=0;
char c[5] = {0x00};
i = board[x][y];
while(i > 0){
j = i % 10;//取出个位
c[m++] = j + '0';//将数字转化为字符型
i = i / 10;//去掉个位,剩下的继续循环 依次转化为字符型 c[5]倒序存入数字
}
m=0;
k=(y + 1) * 5 - 1;
while(c[m] != 0x00){
move(2*x+1, k);//移动光标到格子中
addch(c[m++]);//将字符取出画在屏幕上 并m++移到下一位字符
k--;//使画出的数字变为正序
}
}
//初始化
void init(void){
int x, y;
initscr();//开启 curses 模式
cbreak();//开启后, 除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取
noecho();//用来控制从键盘输入字元时不将字元显示在终端(窗口)上
curs_set(0);//将把光标设置为不可见
empty = 15;//初始化时剩余空格子的数量
srand(time(0));//随机数种子
x = rand() % 4;// 0 1 2 3
y = rand() % 4;// 0 1 2 3
board[x][y] = 2;//初始化随机在数组某处存入第一个数字2
draw_grid();//绘制窗格
}
然后来看看游戏运行的核心 play
函数。通过 W, S, A, D
和方向键来分别控制上、下、左、右方向的移动,按 Q
键可以退出界面。(先分析出一个方向的逻辑,其余的类似)
void play(void){
int x, y, i, new_x, new_y,temp;//x,y 表示 x行 y列 遍历数组使用;new_x,new_y表示新出现数字的坐标
int old_empty, move;//move 标记是否有元素移动过 有则标记为1,有移动必定有空格出现
while(1){
move = 0;
old_empty = empty;
//获取用户的按键 进行上下左右操作的判断
switch(getch()){
case 'a':
case 68://左移方向键
for(x = 0; x < 4; x++){
for(y = 0; y < 4;){
if(board[x][y] == 0){//持续判断数组中元素是否为0;即格子中是否为空
y++;
continue;
}
else{
for(i = y+1; i < 4; i++){
if(board[x][i] == 0)//数组中前一个元素不为0时;持续判断后面一个元素是否为0
continue;
else{
if(board[x][y] == board[x][i]){//如果一行左右两个都有元素是否相等(元素并不一定相邻)
board[x][y] += board[x][i];//把左右两个元素相加,赋值给左边的元素(因为按下左键)
board[x][i] = 0;//右边的元素清零
empty++;//空格子数量+1
break;
}
else
break;//结束这一行的判断
}
}
y = i;//判断到这行有两个元素,把y定位到右边的一个;再次循环判断这一行后面是否有第三个元素 ,再次进行上面操作
}
}
}//到此把四行中的左右相等的元素都进行相加 下面的循环将数据左对齐
for(x = 0; x < 4; x++){
for(y = 0; y < 4; y++){
if(board[x][y] == 0)//判断到元素为0则继续循环
continue;
else
for(i = y; (i > 0) && (board[x][i-1] == 0); i--){//当执行这层循环时,说明左边有空位,要把左边的数据移过去
board[x][i-1] = board[x][i];
board[x][i] = 0;
move = 1;
}
}
}
break;//处理完左键的操作,跳出switch
case 'd':
case 67://右移方向键
// case 77:
for(x = 0; x < 4; x++){
for(y = 3; y >= 0; ){//右移从右往左遍历
if(board[x][y] == 0){
y--;
continue;
}
else{
for(i = y - 1; i >= 0; i--){//检测到有非零元素,继续向左检测元素
if(board[x][i] == 0)
continue;
else if(board[x][y] == board[x][i]){
board[x][y] += board[x][i];//把元素相加放在右边
board[x][i] = 0;//左边的元素清零
empty++;
break;
}
else
break;
}
y = i;
}
}
}
for(x = 0; x < 4; x++){
for(y = 3; y >= 0; y--){
if(board[x][y] == 0)
continue;
else
for(i = y; (i < 3) && (board[x][i+1] == 0); i++){//数组元素右对齐存放
board[x][i+1] = board[x][i];
board[x][i] = 0;
move = 1;
}
}
}
break;
case 'w':
case 65://上移方向键
for(y = 0; y < 4; y++){//从第一列自上而下遍历数组
for(x = 0; x < 4; ){
if(board[x][y] == 0){
x++;
continue;
}
else{
for(i = x + 1; i < 4; i++){
if(board[i][y] == 0)
continue;
else if(board[x][y] == board[i][y]){//上下两个元素相同则相加
board[x][y] += board[i][y];
board[i][y] = 0;
empty++;
break;
}
else
break;
}
x = i;//将位置定位到下面的元素,遍历后面的元素
}
}
}
for(y = 0; y < 4; y++){
for(x = 0; x < 4; x++){
if(board[x][y] == 0)
continue;
else
for(i = x; (i > 0) && (board[i-1][y] == 0); i--){//当前位置有元素,并且上面一个元素为0,则位置上移
board[i-1][y] = board[i][y];
board[i][y] = 0;
move = 1;
}
}
}
break;
case 's':
case 66://下移方向键
for(y = 0; y < 4; y++){//从第一列开始自下而上遍历数组
for(x = 3; x >= 0; ){
if(board[x][y] == 0){
x--;
continue;
}
else{
for(i = x - 1; i >= 0; i--){
if(board[i][y] == 0)
continue;
else if(board[x][y] == board[i][y]){
board[x][y] += board[i][y];
board[i][y] = 0;
empty++;
break;
}
else
break;
}
x = i;
}
}
}
for(y = 0; y < 4; y++){
for(x = 3; x >= 0; x--){
if(board[x][y] == 0)
continue;
else
for(i = x; (i < 3) && (board[i+1][y] == 0); i++){//如果当前元素不为零,且下面的元素为零
board[i+1][y] = board[i][y];
board[i][y] = 0;
move = 1;
}
}
}
break;
case 'Q':
case 'q':
game_over();
break;
default:
continue;
break;
}
if(empty <= 0)//没有空格子,游戏结束
game_over();
if((empty != old_empty) || (move == 1)){//如果还有空格子
do{
new_x = rand() % 4;
new_y = rand() % 4;
}while(board[new_x][new_y] != 0);//找到数组中一个地方元素为0(格子为空)
cnt_value(&new_x, &new_y);//避免在同一位置重复出现新数字
do {
temp = rand() % 4;
}while(temp == 0 || temp == 2);//让temp为1或3
board[new_x][new_y] = temp + 1;//随机出现的2或4放入数组的空格子
empty--;//空格子数量-1
}
draw_grid();//数组更新后重新绘制窗格
}
}
//统计(x, y)对应的格子周围一圈的空格子的个数
int cnt_one(int x, int y){
int value = 0;
if(x - 1 >= 0)
board[x - 1][y] ? 0 : value++;//判断上边的格子是否为0 判断了2 3 4行
if(x + 1 < 4)
board[x + 1][y] ? 0 : value++;//判断下边的格子是否为0 判断了1 2 3行
if(y- 1 >= 0)
board[x][y - 1] ? 0 : value++;//判断左边的格子是否为0 判断了2 3 4列
if(y + 1 < 4)
board[x][y + 1] ? 0 : value++;//判断右边的格子是否为0 判断了1 2 3列
if(x - 1 >= 0 && y - 1 >= 0)
board[x - 1][y - 1] ? 0 : value++;//判断左上角
if(x - 1 >= 0 && y + 1 < 4)
board[x - 1][y +1] ? 0 : value++;//判断右上角
if(x + 1 < 4 && y - 1 >= 0)
board[x + 1][y - 1] ? 0 : value++;//判断左下角
if(x + 1 < 4 && y + 1 < 4)
board[x + 1][y + 1] ? 0 : value++;//判断右下角
return value;
}
//计算新出现的数字的位置与上次不在同一位置出现
void cnt_value(int* new_x,int* new_y){
int max_x, max_y, x, y, value;
int max = 0;
max = cnt_one(*new_x, *new_y);
for(x = 0; x < 4; x++){
for(y = 0; y < 4; y++){
if(!board[x][y]){//如果x,y对应的空格为空
value = cnt_one(x, y);//优先选取周围空格最多的空格展示新数字
if(value > max && old_x != x && old_y != y){//避免在同一位置反复出现新数字
*new_x = x;
*new_y = y;
old_x = x;
old_y = y;
}
}
}
}
//游戏结束
int game_over(void){
move(10, 5);
addstr("game over!");//显示字符串
refresh();//刷新生效
sleep(3);//暂停
endwin();//退出curses模式
exit(0);
}
main函数
int main(){
init();
play();
endwin();//关闭 curses 模式
return 0;
}
#编译 gcc 2048.c -o 2048 -lcurses
#执行 ./2048
ncurses
库使用参考文章:
Linux curses库使用
Linux下curses函数库
代码参考:
C 语言实现 2048 游戏