提示:源码资源可以点击下列地址:
源码链接: 2048小游戏资源
📕内容讲解
1、游戏登入界面
代码入口 main.c
代码部分:
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "initgame.h"
int array[4][4] = {0};
int main(){
init(array);
}
代码解释:
(一)预处理部分:
1、#include<stdio.h> 和 #include<stdlib.h> 作为最基本头文件不多做赘述
2、#include<string.h> 专门快速处理字符串操作的头文件
3、#include<curses.h> linux图形编程的头文件,可以完成终端的图形化设计,让界面更加好看
4、#include“initgame.h” 是自己设计的登入界面的函数,会在下文介绍到
知识点补充:#include<> 和 #include"" 的区别:【对找工作的同学们挺重要的】
1、引用的头文件不同:
前者:引用编译器的类库路径的头文件
后者:引用的是程序所在目录的头文件
2、用法不同(个人感觉这个非强制,但是还是养成习惯):
前者:引用包含标准头文件
后者:引用包含非标准头文件
3、调用文件的顺序不同:
前者:编译程序会先到标准函数库中调用文件
后者:编译程序会先从当前目录中调用文件
4、预处理程序的指示不同:
前者:指示预处理程序到预定义的缺省路径下寻找文件
后者:指示预处理程序先到当前目录下寻找文件,再到预定义的缺省路径下寻找文件
📕总结:尖括号的标准,去库路径寻找;双引号的非标准,在当前目录下寻找
(二)全局变量:
1、int array[4][4] = {0}; 由于我的代码设计的4x4大小的游戏界面所以定义的是4x4的二维数组
(三)函数部分:
int main(){
/*init函数是initgame.c内的函数,可以当作游戏的(API)接口,下文会有讲解 */
init(array);
}
游戏登入界面初始化 initgame
代码部分
h文件
#ifndef __INITGAME_H__
#define __INITGAME_H__
void initjudge1(int array[4][4]);
int init(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "initui.h"
void initjudge1(int array[4][4]){
char key = getch();
switch (key){
case 27:
clear();
refresh();
endwin();
break;
case '\n':
clear();
refresh();
gamestart(array);
break;
default:
attron(A_BLINK);
mvaddstr(LINES * 0.7, COLS * 0.40,"Please try again......");
attroff(A_BLINK);
initjudge1(array);
break;
}
}
int init(int array[4][4]){
initscr();
intrflush(stdscr,false);
cbreak();
keypad(stdscr,true);
box(stdscr,'+','+');
noecho();
curs_set(0);
attron(A_UNDERLINE);
mvaddstr(LINES * 0.3, COLS * 0.4, "Welcome To 2048 Game !!!");
attroff(A_UNDERLINE);
mvaddstr(LINES * 0.45, COLS * 0.45, "Start : (Enter)");
mvaddstr(LINES * 0.5, COLS * 0.46, "Exist : (Esc)");
mvaddstr(LINES * 0.6, COLS * 0.35,"Please input string Enter/Esc");
initjudge1(array);
return 0;
}
代码解释:
(一)预处理部分:
1、#include<stdio.h> 和 #include<stdlib.h> 作为最基本头文件不多做赘述
2、#include<string.h> 专门快速处理字符串操作的头文件
3、#include<curses.h> linux图形编程的头文件,可以完成终端的图形化设计,让界面更加好看
4、#include“initui.h” 是自己设计的游戏运行界面的函数,会在下文介绍到
(二)函数部分:
1、游戏登入界面文字初始化: int init(int array[4][4])
initscr(); // 此函数是curses编程的初始化,必须使用这个函数来启动curses模式
intrflush(stdscr,false); // 类似中断处理,但建议不需要打开
cbreak(); // 除DELETE或CTRL等一些特殊控制字元外能被读取
keypad(stdscr,true); // 可以使用方向键,将方向键转化为以KEY_开头的常量
box(stdscr,'+','+'); // 自动绘画方框,方便你设计边框边界
noecho(); // 按键输入时不实时显示在屏幕
curs_set(0); // 隐藏游标显示
/* 以上代码可以视作图形编程的初始化代码,如果弄不懂的可以不强求弄懂*/
attron(A_UNDERLINE); // 打开字体设置并将下文设置成自带下划线
mvaddstr(LINES * 0.3, COLS * 0.4, "Welcome To 2048 Game !!!"); // 在显示屏幕中显示文字 “Welcome To 2048 Game !!!”
attroff(A_UNDERLINE); // 关闭字体设置
mvaddstr(LINES * 0.45, COLS * 0.45, "Start : (Enter)"); // 在显示屏幕中显示文字 “Start : (Enter)”
mvaddstr(LINES * 0.5, COLS * 0.46, "Exist : (Esc)"); // 在显示屏幕中显示文字 “Exist : (Esc)”
mvaddstr(LINES * 0.6, COLS * 0.35,"Please input string Enter/Esc"); // 在显示屏幕中显示文字 “Please input string Enter/Esc”
initjudge1(array); // 判断按键函数,下文会有解释
------------------------------------------------------------------------------------------------------------------------------
2、游戏登入界面按键判断 void initjudge1(int array[4][4])
char key = getch(); // 获得按键信息
switch (key){ // 状态机判断运行
case 27: // 如果是按下Esc键
clear(); // 将整个屏幕清屏
refresh(); // 将整个屏幕清屏(与上面工作功能差不多)
endwin(); // 结束curse编程的函数
break;
case '\n': // 如果是按下Enter键
clear();
refresh();
gamestart(array); // 打开游戏运行界面,这个是在initui.c的一个函数,下文会有介绍。
break;
default: // 如果是按下除Esc键和Enter键以外的键
attron(A_BLINK); // 打开字体设置并将下文设置成自闪
mvaddstr(LINES * 0.7, COLS * 0.40,"Please try again......"); // 在显示屏幕中显示文字 “Please try again......”
attroff(A_BLINK); // 关闭字体设置
initjudge1(array); // 重新进行按键判断
break;
}
2、游戏运行界面
游戏运行界面初始化 initui
代码部分
h文件
#ifndef __INITUI_H__
#define __INITUI_H__
extern WINDOW * subwindow;
void initborder(WINDOW * subwindow);
void initarray(int array[4][4], WINDOW * subwindow);
int gamestart(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "keyjudge.h"
#include "gamejudge.h"
#include "randomint.h"
void initborder(WINDOW * subwindow){
wmove(subwindow, 4, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 8, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 12, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 16, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 1, 7);
wvline(subwindow, '|', 15);
wmove(subwindow, 1, 14);
wvline(subwindow, '|', 15);
wmove(subwindow, 1, 21);
wvline(subwindow, '|', 15);
}
void initarray(int array[4][4], WINDOW * subwindow){
for(int i = 1; i <= 4; i++){
for(int j = 1; j <= 4; j++){
if(array[i-1][j-1] != 0){
wmove(subwindow, (4 * i) - 2, (7 * j) - 5);
wprintw(subwindow, "%-5d", array[i-1][j-1]);
}
}
}
}
WINDOW *subwindow = NULL;
int oldarray[4][4] = {0};
void gamestart(int array[4][4]){
initscr();
cbreak();
noecho();
curs_set(0);
subwindow = subwin(stdscr, 24, 29, LINES * 0.07, COLS * 0.35);
initborder(subwindow);
box(subwindow,'|','-');
wmove(subwindow,17, 7);
wprintw(subwindow, "[W]:up [S]:down");
wmove(subwindow,19, 5);
wprintw(subwindow, "[L]:left [D]:right");
wmove(subwindow,22, 8);
wprintw(subwindow, "Exit : (Esc)");
RandomNum(array);
RandomNum(array);
initarray(array, subwindow);
while(1){
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++)
{
oldarray[i][j] = array[i][j];
}
}
key(array);
Gamejudge(array);
int flag = 0;
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++)
{
if(oldarray[i][j] != array[i][j]){
flag = 1;
}
}
}
if(flag){
RandomNum(array);
}
else{
}
clear();
wmove(subwindow,17, 7);
wprintw(subwindow, "[W]:up [S]:down");
wmove(subwindow,19, 5);
wprintw(subwindow, "[L]:left [D]:right");
wmove(subwindow,22, 8);
wprintw(subwindow, "Exit : (Esc)");
box(subwindow,'|','-');
initborder(subwindow);
initarray(array, subwindow);
}
}
代码解释:
(一)预处理部分:
1、#include<stdio.h> 和 #include<stdlib.h> 作为最基本头文件不多做赘述
2、#include<string.h> 专门快速处理字符串操作的头文件
3、#include<curses.h> linux图形编程的头文件,可以完成终端的图形化设计,让界面更加好看
4、#include“keyjudge.h” 、#include“gamejudge.h” 和#include“randomint.h” 分别是按键判断、游戏逻辑判断和随机生成“2”位置的函数,会在下文介绍到
(二)全局变量:
1、WINDOW *subwindow = NULL; 由上图可见,我是在原先的主窗口有创建了一个子窗口,此变量就是子窗口的变量
2、int oldarray[4][4] = {0}; 用来保存之前二维数组的缓存
(三)函数部分:
1、游戏边框ui代码::void initborder(WINDOW * subwindow)
wmove(subwindow, 4, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 8, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 12, 1);
whline(subwindow, '-', 27);
wmove(subwindow, 16, 1);
whline(subwindow, '-', 27);
/* 以上代码将方框划分出4四行*/
wmove(subwindow, 1, 7);
wvline(subwindow, '|', 15);
wmove(subwindow, 1, 14);
wvline(subwindow, '|', 15);
wmove(subwindow, 1, 21);
wvline(subwindow, '|', 15);
/* 以上代码将方框划分出4四列*/
-----------------------------------------------------------------------------------------------------
2、数字ui内容填写:void initarray(int array[4][4], WINDOW * subwindow)
/*将之前定义的二维数组进行遍历*/
for(int i = 1; i <= 4; i++){
for(int j = 1; j <= 4; j++){
if(array[i-1][j-1] != 0){ // 挑选出非零数值进行显示
wmove(subwindow, (4 * i) - 2, (7 * j) - 5); // 移动光标至对应位置
wprintw(subwindow, "%-5d", array[i-1][j-1]); // 将数值显示
}
}
}
-----------------------------------------------------------------------------------------------------
3、游戏运行界面总函数:void gamestart(int array[4][4])
initscr(); // curse编程的初始化
cbreak(); · // 除DELETE或CTRL等一些特殊控制字元外能被读取
noecho(); // 按键输入时不实时显示在屏幕
curs_set(0); // 隐藏游标
/* 以上可视为子窗口的初始化*/
subwindow = subwin(stdscr, 24, 29, LINES * 0.07, COLS * 0.35); // 创建子窗口大小
initborder(subwindow); // 初始化ui界面的边框
box(subwindow,'|','-'); // 快速绘画边界边框
wmove(subwindow,17, 7);
wprintw(subwindow, "[W]:up [S]:down");
wmove(subwindow,19, 5);
wprintw(subwindow, "[L]:left [D]:right");
wmove(subwindow,22, 8);
wprintw(subwindow, "Exit : (Esc)");
/* 以上为控制提示的ui设计 */
RandomNum(array);
RandomNum(array);
/* 随机给二维数组2个数字,此函数是位于randomint.c里(后面会有讲解) */
initarray(array, subwindow); //数字ui界面填写
while(1){
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++)
{
oldarray[i][j] = array[i][j];
}
}
/* 保存最新的二维数组 */
key(array); // 此函数是在keyjudge.c里的函数,用于方向判断和操作(后面会有讲解)
Gamejudge(array); // 此函数是在gamejudge.c里的函数,用于判断游戏是否结束(后面会有讲解)
int flag = 0; // 是否改变的标志位
/* 判断是否和之前的数组一样 */
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++)
{
if(oldarray[i][j] != array[i][j]){
flag = 1;
}
}
}
if(flag){ // 如果不同这随机在给一个数字
RandomNum(array);
}
else{ // 反之不进行任何操作
}
clear(); // 进行清屏刷新数据
/* 以下是将新界面进行呈现 */
wmove(subwindow,17, 7);
wprintw(subwindow, "[W]:up [S]:down");
wmove(subwindow,19, 5);
wprintw(subwindow, "[L]:left [D]:right");
wmove(subwindow,22, 8);
wprintw(subwindow, "Exit : (Esc)");
box(subwindow,'|','-');
initborder(subwindow);
initarray(array, subwindow);
}
随机生成数字“2” randomint
代码部分
h文件
#ifndef __RANDOMINT_H__
#define __RANDOMINT_H__
void Random(int array[4][4]);
int RandomNum(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
void Random(int array[4][4]){
int flag = 0;
int num[10] = {2,2,2,2,2,2,2,2,2,4};
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(array[i][j] == 0){flag++;}
}
}
if(flag == 0){
}
else{
flag = random() % flag;
int key = 1;
int k = 0;
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(key == 1){
if(array[i][j] == 0 && k != flag){
k++;
}
else if (array[i][j] == 0 && k == flag){
array[i][j] = num[random() % 10];
key = 0;
}
}
}
}
}
}
int RandomNum(int array[4][4]){
int i, j;
srandom(time(NULL));
Random(array);
}
代码解释:
(一)预处理部分:
1、#include<stdio.h> 和 #include<stdlib.h> 作为最基本头文件不多做赘述
2、#include<string.h> 专门快速处理字符串操作的头文件
3、#include<curses.h> linux图形编程的头文件,可以完成终端的图形化设计,让界面更加好看
4、#include<time.h> 专门处理时间查询的函数库
(二)函数部分:
1、二维数组生成随机数字“2” void Random(int array[4][4])
int flag = 0; // 判断是否有数字的标志位
int num[10] = {2,2,2,2,2,2,2,2,2,4}; // 这个是随机生成数字的“数字池”,随机生成的数字就是来源于此
/* 遍历数组,查找有多少个位置没有数据 */
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(array[i][j] == 0){flag++;}
}
}
if(flag == 0){ // 如果没有位置是空的就不作任何操作
}
else{ // 如果有位置是空进行随机数赋值
flag = random() % flag; // 在没有位置的地方随机选择位置赋值
int key = 1; // 是否赋值的标志位
int k = 0; // 由于判断是否到达我要赋值的位置的标志位
/* 开始遍历赋值 */
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(key == 1){ // 如果还没有赋值
if(array[i][j] == 0 && k != flag){ // 如果位置是空的但是没有到我想要赋值的位置
k++; // 位置加1
}
else if (array[i][j] == 0 && k == flag){ // 如果位置是空的且到达我想赋值的位置
array[i][j] = num[random() % 10]; // 从上述的数据池随机选择一个
key = 0; // 将是否赋值的标志位赋值0,让他不要赋值了
}
}
}
}
}
----------------------------------------------------------------------------------------------------------------------
2、随机生成总函数 int RandomNum(int array[4][4])
srandom(time(NULL)); // 生成随机种子
Random(array); // 二维数组随机生成数字“2”
判断游戏是否结束 gamejudge
代码部分
h文件
#ifndef __GAMEJUDGE_H__
#define __GAMEJUDGE_H__
void Gamejudge(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "win.h"
#include "lose.h"
void Gamejudge(int array[4][4]){
int i;
for (i = 0; i < 4; ++i) {
int j;
for (j = 0; j < 3; ++j) {
/* 横向和纵向比较挨着的两个元素是否相等,若有相等则游戏不结束 */
if (array[i][j] == array[i][j + 1] || array[j][i] == array[j + 1][i]) {
return;
}
}
}
for(int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(array[i][j] == 2048){
clear();
refresh();
endwin();
Win(array);
}
}
clear();
refresh();
endwin();
Lose(array);
}
}
代码解释:
(一)预处理部分:
1、#include<stdio.h> 和 #include<stdlib.h> 作为最基本头文件不多做赘述
2、#include<string.h> 专门快速处理字符串操作的头文件
3、#include<curses.h> linux图形编程的头文件,可以完成终端的图形化设计,让界面更加好看
4、#include"win.h"和#include"lose.h" 分别是游戏胜利界面和游戏失败界面的函数(下文会有讲解)
(二)函数部分:
1、游戏判断代码 void Gamejudge(int array[4][4])
int i;
for (i = 0; i < 4; ++i) {
int j;
for (j = 0; j < 3; ++j) {
/* 横向和纵向比较挨着的两个元素是否相等,若有相等则游戏不结束 */
if (array[i][j] == array[i][j + 1] || array[j][i] == array[j + 1][i]) {
return;
}
}
}
/* 判断每个元素与它的下面的元素和左边的元素是否相同,如果有相同的就退出判断,否则进行下一个判断 */
for(int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if(array[i][j] == 2048){
clear();
refresh();
endwin();
Win(array); // 此函数是win.c内的函数,下文会有介绍
}
}
/* 遍历数组,如果二维数组中有一个元素是2048,游戏胜利 */
clear();
refresh();
endwin();
Lose(array); // 此函数是lose.c内的函数,下文会有介绍
/* 反之游戏失败 */
}
3、游戏结束界面
由于结束界面和登入界面基本一致,所以大家可以类比游戏登入界面函数,以下只提供h文件和c文件
胜利界面 win
代码部分
h文件
#ifndef __WIN_H__
#define __WIN_H__
void initjudge3(int array[4][4]);
int Win(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "initui.h"
void initjudge3(int array[4][4]){
int newarray[4][4] = {0};
char key = getch();
switch (key){
case 27:
clear();
refresh();
endwin();
exit(0);
break;
case '\n':
clear();
refresh();
endwin();
gamestart(newarray);
break;
default:
attron(A_BLINK);
mvaddstr(LINES * 0.7, COLS * 0.40,"Please try again......");
attroff(A_BLINK);
initjudge3(array);
break;
}
endwin();
}
int Win(int array[4][4]){
initscr();
cbreak();
keypad(stdscr,true);
box(stdscr,'+','+');
noecho();
curs_set(0);
mvaddstr(LINES * 0.3, COLS * 0.47, "You Win !!!");
mvaddstr(LINES * 0.45, COLS * 0.45, "Again : (Enter)");
mvaddstr(LINES * 0.5, COLS * 0.46, "Exist : (Esc)");
mvaddstr(LINES * 0.6, COLS * 0.35,"Please input string Enter/Esc");
move(LINES - 1,COLS * 0.5);
initjudge3(array);
return 0;
}
失败界面 lose
代码部分
h文件
#ifndef __LOSE_H__
#define __LOSE_H__
void initjudge2(int array[4][4]);
int Lose(int array[4][4]);
#endif
c文件
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "initui.h"
void initjudge2(int array[4][4]){
int newarray[4][4] = {0};
char key = getch();
switch (key){
case 27:
clear();
refresh();
endwin();
exit(0);
break;
case '\n':
clear();
refresh();
endwin();
gamestart(newarray);
break;
default:
attron(A_BLINK);
mvaddstr(LINES * 0.7, COLS * 0.40,"Please try again......");
attroff(A_BLINK);
initjudge2(array);
break;
}
}
int Lose(int array[4][4]){
initscr();
cbreak();
keypad(stdscr,true);
box(stdscr,'+','+');
noecho();
curs_set(0);
mvaddstr(LINES * 0.3, COLS * 0.47, "You Lose !!!");
mvaddstr(LINES * 0.45, COLS * 0.45, "Again : (Enter)");
mvaddstr(LINES * 0.5, COLS * 0.46, "Exist : (Esc)");
mvaddstr(LINES * 0.6, COLS * 0.35,"Please input string Enter/Esc");
move(LINES - 1,COLS * 0.5);
initjudge2(array);
return 0;
}