2048小游戏是用Linux C语言写的,运行在Ubuntu系统上,其中Ubuntu链接Ncuses库。首先,我们简单了解一下Ncurses。 Ncurses库是由原CURSES库发展而来的,这个库提供了C/C++对屏幕以及光标、鼠标访问的特性。使得C/C++简易写屏成为可能。
如果要调用NCURSES库中的函数,你必须在代码中加载ncurses.h文件,就是在C或C++程序中添加#include
例如: Hello World程序
#include
int main()
{
initscr(); //初始化,进入curses模式 ,用来清除屏幕("stdscr"窗口)上所有的字符,使屏幕变为空白,等待下一步处理
printw(“Hello World”); //在虚拟屏幕上打印Hello World 输出到”stdscr"的虚拟坐标(0,0)上
refresh(); //将虚拟屏幕上的内容写到显示器上,并刷新 (将缓冲区的内容输出到 屏幕上)printw+refresh()可以不断在虚拟屏幕上写入数据,而调用refresh()函数时让一切看起来似乎时一次完成的。因为refresh()函数至核查窗口和数据中变动的部分。
getch(); //等待用户输入
endwin(); //退出NCURSES模式 释放了curses子系统和相关数据结构的内存,才回到普通字符行模式,否则,结束后终端可能运转得不正常 如果你的程序不能正常地显示东西了,看看initscr()函数和endwin()函数是不是在不该被调用的地方调用了
}
#ifdef __INPUT_H
#define __INPUT_H
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
#define FIRST 10
#define NEXT 11
#define THEN 12
#define LAST 13
#define QUIT 100
#define UNKNOW 250
int get_user_input(void);
#endif
#include
#include "input.h"
extern WINDOW *w_2048; //声明一个外部变量,表示这个变量是其他文件(2048.c)中定义的
int get_user_input(void)
{
keypad(w_2048,TRUE);
int ch=wgetch(w_2048);
keypad(w_2048,FALSE);
if(ch=='q'||ch=='Q')
{
return QUIT;
}
else if(ch=='a'||ch==KEY_LEFT)
{
return LEFT;
}
else if(ch=='d'||ch==KEY_RIGHT)
{
return RIGHT;
}
else if(ch=='s'||ch==KEY_DOWN)
{
return DOWN;
}
else if(ch=='w'||ch==KEY_UP)
{
return UP;
}
else if(ch=='1')
{
return FIRST;
}
else if(ch=='2')
{
return NEXT;
}
else if(ch=='3')
{
return THEN;
}
else if(ch=='4')
{
return LAST;
}
else
{
return UNKNOW;
}
}
相关函数解释:
1.keypad()函数
这个函数允许使用功能键:F1,F2,方向键等。使用keypad(stdscr,TURE)就是显示设备 (stdscr)上使用这些功能。keypad(stdscr,FALSE);就是显示设备(stdscr)关闭这些功能。
2.wgetch()函数
wgetch(*WINDOWS)
这个函数从指定的窗口接收字符
#define __SCREEN_H
#define __SCREEN_H
//打开字符屏幕
void open_screen(void);
//关闭字符屏幕
void close_screen(void);
#include
//打开字符屏幕
void open_screen(void)
{
initscr();
cbreak();
noecho();
keypad(stdscr,TRUE);
}
//关闭字符屏幕
void close_screen(void)
{
endwin();
}
相关代码解释:
1. initscr()函数
初始化,进入curses模式 ,用来清除屏幕("stdscr"窗口)上所有的字符,使屏幕变为空白,等待下一步处理
2. cbreak()函数
通常情况下,用户输入的字符将被终端程序送入终端的缓冲区。但当用户有输入换行符时,终端程序将会终端,同时输出当前的缓冲区内容并启用新行的输出缓冲。后面的2048.c代码中, game_is_over()函数 直接改成printf(“game over”) ;程序结束后,会在终端输出game over。但是改成printf(“game over\n”),会显示在屏幕上。但是大多数需要用户输入单个字符并能显示在屏幕上。
cbreak()用来禁用行缓冲,这个函数所初始化的模式可以用来给程序传送控制字符,比如:挂起,中断或退出。这些控制字符被认为是终端驱动程序中的控制字符,因而将这些这符传给终端程序。
3.noecho()函数
这个函数控制用户输入的键盘回显。 noecho()函数可以不让控制字符出现在屏幕上。
#include
#include
#include
#include "screen.h"
#include "time.h"
int main()
{ //设置随机数zhongz
srandom(time(NULL));
//创建(初始化)我们的字符窗口屏幕
open_screen();
//进入2048的主循环
game_2048();
//关闭字符屏幕
close_screnen();
return 0;
}
#ifndef __2048_H__
#define __2048_H__
void game_2048(void);
#endif
#include
#include
#include
#include "input.h"
#include
//提示窗口相关参数
WINDOW *wel=NULL;
int wel_lines=2; //提示窗口的行数
int wel_columns=40; //提示窗口的列数
int wel_y0=0; //提示窗口左上顶点的y坐标
int wel_x0=20; //提示窗口右上顶点的x坐标
//游戏数字窗口
WINDOW *w_2048=NULL;
int w_2048_lines=4;
int w_2048_coluwns=24;
int w_2048_y0=3;
int w_2048_x0=20;
//得分窗口
WINDOW *w_score=NULL;
int w_score_lines=2;
int w_score_coluwns=24;
int w_score_y0=10;
int w_score_x0=20;
//结束窗口
WINDOW *w_over=NULL;
int w_over_lines=4;
int w_over_coluwns=24;
int w_over_y0=14;
int w_over_x0=20;
#define ITEM_NUM 4
int matrix_2048[ITEM_NUM][ITEM_NUM]=
{
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
};
//删除行
void Del(int num)
{
int i;
for(i=0;i<ITEM_NUM;i++)
{
if(matrix_2048[num][i]!=0)
matrix_2048[num][i]=0;
}
}
//获取0的个数
int get_zero_num()
{
int i,j;
int n=0;//计数
for(i=0;i<ITEM_NUM;i++)
{
for(j=0;j<ITEM_NUM;j++)
{
if(matrix_2048[i][j]==0)
n++;
}
}
return n;
}
//在空白位置上随机填充一个数字
void fill_a_number(void)
{
int zero_num=get_zero_num();
if(zero_num==0)
{
return;
}
int pos=random()%zero_num+1; //范围是【1~zero_num】
int n=0;//第几个0
int i,j;
for(i=0;i<ITEM_NUM;i++)
{
for(j=0;j<ITEM_NJM;j++)
{
if(matrix_2048[i][j]==0)
{
n++;
if(n==pos)
{
//要填充的位置找到了
int a[4]={2,2,4,8};
int index=random()%4;
matrix_2048[i][j]=a[index];
return ;
}
}
}
}
}
//初始化游戏界面需要用到的窗口
void init_windows()
{
start_color();
init_pair(1,COLOR_RED,COLOR_BLACK);
//1.创建一个提示(欢迎)字符
wel=newwin(wel_lines,wel_columns,wel_y0,wel_x0);
wmove(wel,0,0);
wattron(wel,COLOR_PAIR(1));
waddstr(wel,"welcome to game2048");
wmove(wel,1,3);
waddstr(wel,"Author:liyingzhi");
wrefresh(wel);
//2.创建一个游戏数字字符窗口
w_2048=newwin(w_2048_lines,w_2048_coluwns,w_2048_y0,w_2048_x0);
//3.显示最高得分
w_score=newwin(w_score_lines,w_score_coluwns,w_score_y0,w_score_x0);
//4.结束窗口
w_over=newwin(w_over_lines,w_over_coluwns,w_over_y0,w_over_x0);
}
void del_windows()
{
delwin(wel); //删除欢迎框
delwin(w_2048); //删除字符框
delwin(w_score); //删除得分框
}
//在非0位置随机删除一个数字
void delete_number(int del)
{
int zero_num=get_zero_num();
int i,j;
if(zero_num==0)
{
return;
}
for(i=0;i<ITEM_NUM;i++)
{
for(j=0;j<ITEM_NUM;j++)
{
if (del==j)
{
matrix_2048[i][j]=0;
}
}
}
}
//根据矩阵matrix_2048[][]的内容,在游戏中显示
void show_matrix(void)
{
int i,j;
int max=matrix_2048[0][0];//最高得分
for(i=0;i<ITEM_NUM;i++)
{
for(j=0;j<ITEM_NUM;j++)
{
//移动光标wmove
wmove(w_2048, i, j*6);
//添加要显示的字符串 int转换成char
char buf[6];
if(matrix_2048[i][j]==0)
{
sprintf(buf,"%5d",'_');
}
else
{
sprintf(buf,"%5d",matrix_2048[i][j]);
}
waddstr(w_2048,buf);
if(matrix_2048[i][j]>max)
{
max=matrix_2048[i][j];
}
}
}
//刷新窗口
wrefresh(w_2048);
//显示得分
wmove(w_score,0,6);
char buf1[24];
sprintf(buf1,"score:%5d",max);
waddstr(w_score,buf1);
//刷新窗口 wrefresh
wrefresh(w_score);
}
void move_rightr(void)
{
int i;
for(i=0;i<ITEM_NUM;i++)
{
int x=0;//辅助存储非0值进行比较
int m=ITEM_NUM-1;//指向这一行最后面(要被覆盖)的位置
int k=0;//哨兵,从右往左,找下一个非0元素a[i][k],和x进行比较
for(k=ITEM_NUM-1;k>=0;k--)
{
if(matrix_2048[i][k]!=0)
{
if(x==0)
{
x=matrix_2048[i][k];
matrix_2048[i][k]=0;
}
else
{
//找到两个非0值,进行比较
if(matrix_2048[i][k]==x)
{
//需要合并,后移
x=x+matrix_2048[i][k]; //合并的值
matrix_2048[i][k]=0; //前移后的数字为0
matrix_2048[i][m--]=x; //合并的值后移
x=0;
}
else
{
matrix_2048[i][m--]=x;
x=matrix_2048[i][k];
matrix_2048[i][k]=0;
}
}
}
}
matrix_2048[i][m--]=x;
}
void move_up(void)
{
int i;
for(i=0;i<ITEM_NUM;i++)
{
int x=0;//辅助存储非0值进行比较
int m=0;//指向这一行最前面(要被覆盖)的位置
int k=0;//哨兵,从上往下,找下一个非0元素 a[i][k],和x进行比较
for(k=0;k<ITEM_NUM;k++)
{
if(matrix_2048[k][i]!=0)
{
if(x==0)
{
x=matrix_2048[k][i];
matrix_2048[k][i]=0;
}
else
{
//找到两个非0值,进行比较
if(matrix_2048[k][i]==x)
{
//需要合并,前移
x=x+matrix_2048[k][i]; //合并的值
matrix_2048[k][i]=0; //前移后的数字为0
matrix_2048[m++][i]=x; //合并的值前移
x=0;//x已经合并前移了,x要重新清0
}
else
{
matrix_2048[m++][i]=x;
x=matrix_2048[k][i];
matrix_2048[k][i]=0;
}
}
}
}
matrix_2048[m++][i]=x;
}
void move_dowm(void)
{
int i;
for(i=0;i<ITEM_NUM;i++)
{
int x=0;//辅助存储非0值进行比较
int m=ITEM_NUM-1;//指向这一行最前面(要被覆盖)的位置
int k=0;//哨兵,从下往上,找下一个非0元素 a[i][k],和x进行比较
for(k=ITEM_NUM-1;k>=0;k--)
{
if(matrix_2048[k][i]!=0)
{
if(x==0)
{
x=matrix_2048[k][i];
matrix_2048[k][i]=0;
}
else
{
//找到两个非0值,进行比较
if(matrix_2048[k][i]==x)
{
//需要合并,前移
x=x+matrix_2048[k][i]; //合并的值
matrix_2048[k][i]=0; //前移后的数字为0
matrix_2048[m--][i]=x; //合并的值前移
x=0;//x已经合并前移了,x要重新清0
}
else
{
matrix_2048[m--][i]=x;
x=matrix_2048[k][i];
matrix_2048[k][i]=0;
}
}
}
}
matrix_2048[m--][i]=x;
}
}
void move_left(void)
{
int i;
for(i=0;i<ITEM_NUM;i++)
{
int x=0;//辅助存储非0值进行比较
int m=0;//指向这一行最前面(要被覆盖)的位置
int k=0;//哨兵,从左至右,找下一个非0元素 a[i][k],和x进行比较
for(k=0;k<ITEM_NUM;k++)
{
if(matrix_2048[i][k]!=0)
{
if(x==0)
{
x=matrix_2048[i][k];
matrix_2048[i][k]=0;
}
else
{
//找到两个非0值,进行比较
if(matrix_2048[i][k]==x)
{
//需要合并,前移
x=x+matrix_2048[i][k]; //合并的值
matrix_2048[i][k]=0; //前移后的数字为0
matrix_2048[i][m++]=x; //合并的值前移
x=0;//x已经合并前移了,x要重新清0
}
else
{
matrix_2048[i][m++]=x;
x=matrix_2048[i][k];
matrix_2048[i][k]=0;
}
}
}
}
matrix_2048[i][m++]=x;
}
}
//完整代码在网址中
函数分析
1、删除函数Del: 参数num是个整型数字,num等于1时,删除第一行;num等于2时,删除第二行。
每一行会有ITEM_NUM个数字,所以用一个for循环,把一行的数字删了
2、get_zero_num()函数:遍历整个二维数组,返回0的个数
3、fill_a_number()函数:首先用get_zero_num()判断是否有空位,random()%zero_num的范围是0~zero_num-1 pos=random()%zero_num+1的范围是1到zero_num pos就可以随机取其中空白的一个位置 int a[4]数组存放要填充的数字
4、init_windows():初始化窗口
newwin()函数的作用是分配内存给窗口相关的数据结构,返回一个指向窗口的结构指针,失败返回NULL。
delwin()函数可以删除一个窗口,并且释放存储窗口数据结构的内存和信息。
第一个参数:窗口的行数 第二个参数:窗口的列数 第三个参数:窗口左上顶点的行坐标
第四个参数:窗口左上顶点的列坐标 成功返回OK,失败返回ERR
mvwin()函数:第一个参数:要移动的窗口的指针 第二个参数:要移动位置左上顶点的行坐标 第三个参数:要移动位置左上顶点的列坐标 成功返回OK,失败返回ERR
5.show_matrix()
max存放最高分数,sprintf()函数把格式化数据写入字符串 第一个参数为要写入的字符串,第二个为格式化字符串,第三 个为变量。sprintf()最常见的应 用之一莫过于把整数打印到字符串中,如:sprintf(s, “%d”, 123); //把整数123打印成一个字符串保存在s中 sprintf(s, “%8x”, 4567); //小写16进制,宽度占8个位置,右对齐 sprintf()会根据参数format 字符串来转换并格式化数据,然后将结果。复制到参数str 所指的字符串数组,直到出现字符串结束(’\0’)为止。
waddstr(w_2048,buf);在w_2048这个窗口输出字符buf
6.move_left()
二维数组行遍历,按下左移键或W,整行往左移,填补空位置(0)。如果相邻位置值相同,要合并并且前移(使用后置++)
x用来存储比较的第一个非0值 m指向这一行最前面(要被覆盖)的位置 k为哨兵,从左至右找下一个非0值
if(x==0) x保存第一个非0值,如果一行只有一个非0值或者是第奇数个0,就执行 matrix_2048[i][m++]。但是m++是后置++, 是赋值后++。如果不止一个0,那么两个值比较,如果相等,x保存合并值,前移后的matrix_2048[i][k]变为0。合并的值前移。如果不相同,首先移动第一个x,利用循环外的matrix_2048[i][m++]移动第二个x
7.move_right()
二维数组行遍历。与move_left不同的是,从右往左遍历,整体往右移,所以第一个遍历的非0数,放到最右边(下标最大值)所以m- -
8.move_up()
二维数组列遍历。所以i和k要交换位置,从上往下遍历,第一个遍历的非0数,放在最上面(下标最小),所以m++
9.move_dowm()
二位数组列遍历。与move_dowm()不同的是,从下往上遍历,第一个遍历的非0数,放在最下面(下标最大),所以m- -
10.change_matrix
判断按下方向键,是否二维数组是否有改变,把matrix_2048备份到matrix_2048_bak,如果有改变,返回1,没有改变,返回0
完整代码地址
链接:https://pan.baidu.com/s/1BvlnS57pMeShkYlHf-YvzA
提取码:s4lb