牛客网2048项目实践

面临找工作之际,因为没有项目经验,所以从牛客网上找了这个项目来实践一下,并以此博客记录实践过程中遇到的问题和收获,并且希望我的文章能帮助到大家,如果有写的不好的地方,希望大家谅解,谢谢!

项目的第一步就是配置环境,虽然课程首页有简单的环境配置方法,但是因为方法太简短,导致我第一步花费了较长时间。
第一步是安装MInGW,当你打开他所给的网站时,可以在页面最下面找到mingw-w64-install.exe,但是我不推荐使用这种方式安装,除非你的网速非常好。https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 。下载完mingw-w64-install.exe,运行它,会出现在这里插入图片描述
第二行选x86_64,其它的可以随意。然后点next,但是我点了next后下载速度很慢,并且多次出现下载错误,所以建议不要在线下载。
那么我们回到上面的网址,在网址上找到红框里面的离线下载包,64位机选择64位的安装包,32位的下载32位的安装包,我们可以看到每一个里面有4种安装包,可以随意选择。
注:如果没有跨平台编译需求,就选win32。如果有的话选posix。
dwarf、sjlj 的异常模型选择,推荐使用dwarf即所谓dw2,这个模型便于调试。不过出来的东西较sjlj的东西体量稍大些。
在这里插入图片描述
下载完后,解压到本地目录,(路径自己选) 。例如 D:\mgw,然后把D:\mgw\bin 加入到系统设置的路径里,打开命令行控制台输入g++,确认有这个命令以保证安装是成功:
在这里插入图片描述
但是有这个并不保证后面的pdcurses编译成功,我在这一步卡了很久,虽然有了图片中的结果,但是我的pdcurses库总是编译失败,因此我们输入: g++ -V,结果如下:
在这里插入图片描述
注意Configured with: 后面的单词很多很多,我一开始虽然也能显示这个界面,但是后面Configured with:的单词没有这么多,后面我下载新的mingw安装包后就能成功编译了,应该是我以前的版本不对,64位一定要下64的。
安装后的文件夹里应该有这几个文件,少了可能会导致后面编译失败:
在这里插入图片描述

接下来,编译pdcurses库,https://sourceforge.net/projects/pdcurses/files/pdcurses/3.6/pdcurs36.zip/download 下载pdcurses后解压到D:\pdcurs36目录,命令行控制台cd到 D:\pdcurs36\wincon目录,运行 mingw32-make 命令编译pdcurses库,编译成功后目录下有多个demo的exe文件以及一个pdcurses.a文件,这个文件是库文件。
在这里插入图片描述
在这里插入图片描述
环境搭好了之后我们创建一个2048文件夹,将pdcueses.a和 curses,h移动到2048文件夹中,然后创建一个后缀为cpp的文件。我的是2048step1.cpp。创建完后我们编译一下这个文件就可以生成一个exe文件。编译命令为: g++ 2048step1.cpp pdcurses.a -I . -o 1.exe(cpp文件的名字自己修改,exed的名字自己设定) 下面是效果图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当我第一次编写完代码时,虽然编译成功了,但是当我试玩我的游戏时,当我执行wasd即上下左右时,我发现我的游戏中数字的合并出现了问题,因为我并没有运行它给的moveLeft函数而是自己重写的moveLeft函数,我的思路时先将将数组中所有非零的元素左移,即这个时候只移动但是不合并,然后接着合并左移后的数组,循环这个过程就可以达到左移合并的目的了。我第一次写这个moveLeft函数时,在左移的步骤中,我首先用last变量记录为0的元素,然后将非0的元素移动到last位置上面去,但是当出现连续的0时,这个last保存的是最后的0的位置,所以出错了,后面我重写的时候是用循环扫描,这样就可以把非0元素放置到0元素位置上去。
假设:数组为:
2 0 2 2 第一步左移: 2 2 2 0 第二步合并: 4 2 0 0
0 0 2 2 2 2 0 0 4 0 0 0

总结:
项目难度不大,很容易上手,我一共花了一个下午和一个晚上,我是先把视频整个看完,然后再自己动手做,遇到问题再回看一下视频。 我觉得2048中比较重要的是移动合并函数的编写,因为游戏就是不断的移动并合并,只要正确编写了这个函数,游戏就基本没有其它问题了。
收获:
做完2048,我收获了很多东西,首先就是正如老师所说,编写程序时,可以通过宏定义定义一些变量,这样你修改程序时就只需要改变一小部分代码,并且程序的代码应该具有灵活性。比如我们要把游戏从4 x 4 变成5 x 5,只需要改变宏定义中的N的值,并且我的draw函数也是可以根据N动态变化的。
其次,在处理每一步的输入时,并不是写了4个处理函数,而是只写了一个moveLeft函数,然后通过调用moveLeft和rotate函数来实现其它方向的操作,这种思想非常棒,值得我们学习。
然后就是程序中以10%的概率产生4,因为rand()函数是以相等概率生成随机数的,这样我们就可以通过rand()函数以自己设定的概率生成某一个数字。比如:rand() % 10 == 1 ? 4 : 2 。 rand()%10生成0,1,2,3,4,5,6,7,8,9,等于1的概率为10%,所以生成4的概率为10%。 如果我们想以30%的概率生成4,那么可以这样:(rand()%10)%4==0?4:2。

完整的代码如下:

#include<iostream>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<curses.h>
using namespace std;
/*
2048小程序
2019/3/2
*/
//格子个数
#define N 4
//格子的字符长度
#define WIDTH 5
//胜利条件
#define TARGET 2048

//游戏状态
#define FAIL 0
#define WIN 1
#define NORMAL 2
#define QUIT 3


class Game2048 {
private:
	//存储当前的所有数字
	int data[N][N];
	//保存当前的状态
	int status;
public:
	//构造函数,初始化数字矩阵和游戏状态
	Game2048():status(NORMAL) {
		//设置一个测试的数字矩阵
		setTestData();
	}
	//得到当前的游戏状态
	int getStatus() {
		return status;
	}
	//设置一个测试的数字矩阵
	void setTestData() {
		for (int i = 0; i < N;i++) {
			for (int j = 0; j < N;j++) {
				data[i][j] = 0;
			}
		}
		bool fa=randNew();
		bool fb=randNew();
	}
	//对每一个输入进行处理
	void processInput() {
		char ch = getch();
		//将大写字母转为小写字母
		if (ch>='A'&&ch<='Z') {   
			ch += 32;
		}
		if (status == NORMAL) {
			bool updated = false;
			//左移
			if (ch == 'a') {
				updated = moveLeft();
			}
			//上移
			else if (ch=='w') {
				rotate();
				updated = moveLeft();
				rotate();
				rotate();
				rotate();
			}
			//下移
			else if (ch == 's') {
				rotate();
				rotate();
				rotate();
				updated = moveLeft();
				rotate();
			
			}
			//右移
			else {
				rotate();	
				rotate();
				updated = moveLeft();
				rotate();
				rotate();
			}
			//判断当前状态是否更新
			if (updated == true) {
				randNew();
				if(!judegeStatus()){
					status = FAIL;
				}
			}

		}
		if (ch=='Q'||ch=='q') {
			status = QUIT;
		}
		else if (ch=='R'||ch=='r') {
			restart();
		}

		
	}
	//判断矩阵中是否还有能合并的数
	bool coundMerge() {
		for (int i = 0; i < N;i++) {
			for (int j = 0; j < N - 1;j++) {
				if ( (data[i][j]==0&&data[i][j+1]!=0)|| (data[i][j] == data[i][j + 1] && data[i][j] != 0 )   ) {
					return true;
				}
			}
		}
		return false;
	}
	//处理左移
	bool moveLeft() {
		//循环先左移再合并的操作达到左移整个矩阵的目的
		int tmp[N][N];
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				tmp[i][j] = data[i][j];
			}
		}
		while (coundMerge()) {
			//先逐行将非零的数左移,不合并
			for (int i = 0; i < N; i++) {
				int last = -1;
				for (int j = 0; j < N; j++) {
					
					for (int k = 0; k < j;k++) {
						if (data[i][k]==0&&data[i][j]!=0) {
							data[i][k] = data[i][j];
							data[i][j] = 0;
							break;
						}
					}
					
					/*if (data[i][j] == 0) {
						last = j;
					}
					else {
						if (last != -1) {
							data[i][last] = data[i][j];
							data[i][j] = 0;
							last = j;
						}
					}*/
				}
			}
			//逐行合并
			for (int i = 0; i < N; i++) {
				for (int j = 0; j < N - 1;j++) {
					if (data[i][j] == data[i][j + 1] && data[i][j] != 0) {
						data[i][j] *= 2;
						data[i][j + 1] = 0;
					}
				}
			}
		}
		bool flag = false;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (tmp[i][j] != data[i][j]) { return true; }
			}
		}
		return false;

	}


	//画出2048的框图
	void draw() {
		//清理当前屏幕;
		clear();
		//先画出周围的边框
		for (int i = 0; i < 2 * N + 1; i++) {
			for (int j = 0; j < WIDTH*N + 1; j++) {
				if (i % 2 == 1) {
					if (j%WIDTH == 0) {
						drawItem(i, j, '|');
					}
				}
				else {
					drawItem(i, j, '*');
				}
			}
		}
		//在界面中画出数组中的数字
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (data[i][j] != 0) {
					drawNumber(i * 2 + 1, (j + 1)*WIDTH - 1, data[i][j]);
				}
			}
		}
		//输出操作提示
		mvprintw(2*N+4,N*WIDTH/2,"W(UP),S(DOWN),A(LEFT),D(RIGHT),R(RESTART),Q(QUIT)");
		//判断当前游戏状态
		if (status==WIN) {    
			mvprintw(N, 5 * N / 2 - 1, " YOU WIN,PRESS R TO CONTINUE ");
		}
		else if (status ==FAIL) {
			mvprintw(N, 5 * N / 2 - 1, " YOU FAIL,PRESS R TO CONTINUE ");
		}

	}


	//重新开始
	void restart() {
		//将数组中的数字全部置为0
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				data[i][j] =0;
			}
		}
		randNew();
		randNew();
		status=NORMAL;
	}
	//逆时针旋转矩阵
	void rotate() {
		//tmp临时数组存储旋转后的矩阵
		int tmp[N][N]={0};
//		for (int i = 0; i < N;i++) {
//			for (int j = 0; j < N;j++) {
//				tmp[N-i-1][i] = data[i][j];  
//			}
//		}
//		for (int i = 0; i < N; i++) {
//			for (int j = 0; j < N; j++) {
//				 data[i][j]= tmp[i][j] ;
//			}
//		}
		for (int i = 0; i < N; ++i) {
            for (int j = 0; j < N; ++j) {
                tmp[i][j] = data[j][N - 1 - i];
            }
        }
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < N; ++j) {
                data[i][j] = tmp[i][j];
            }
        }

	}
	//在方格中标出数字
	void drawNumber(int row,int col,int num) {
		while (num>0) {
			drawItem(row,col,num%10+'0');
			num = num / 10;
			col--;
		}
	}
	//在方格中画出字符
	void drawItem(int row,int col,char c) {
		move(row,col);
		addch(c);
	}
	//设置测试数字矩阵
	void settestdata() {
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				data[i][j] = rand() % 1000;
			}
		}
	}

	//判断游戏状态
	bool judegeStatus() {
		for (int i = 0; i < N-1;i++) {
			for (int j = 0; j < N-1;j++) { 
				//有0或者有相同的则游戏可以继续
				if ((data[i][j] * data[i][j + 1] == 0) || (data[i][j] == data[i][j + 1])) { return true; }
				if ((data[i][j] * data[i+1][j] == 0) || (data[i+1][j] == data[i][j])) { return true; }
			}
		}
		return false;
	}
	//产生新的数
	bool randNew() {
		vector<int> tmp;
		//记录空位
		for (int i = 0; i < N;i++) {
			for (int j = 0; j < N;j++) {
				if (data[i][j]==0) {
					tmp.push_back(i*N+j);
				}
			}
		}
		if (tmp.size()==0) {
			return false;
		}
		//随机选择空位
		int val = tmp[rand() % tmp.size()];
		//给空位赋值,可以通过修改mod来修改数据产生的概率
		data[val / N][val % N] = rand() % 10 == 1 ? 4 : 2;
		return true;
	}


};

//初始化
void initialize() {
	// ncurses初始化
	initscr();
	// 按键不需要输入回车直接交互
	cbreak();
	// 按键不显示
	noecho();
	// 隐藏光标
	curs_set(0);
	// 随机数
	srand(time(NULL));
}
//关闭
void shutdown() {
	// ncurses清理
	endwin();
}
int main(){
	initialize();

	Game2048 game;
	do {
		game.draw();
		game.processInput();
	} while (game.getStatus()!=QUIT);

	shutdown();
	return 0;
}



猜你喜欢

转载自blog.csdn.net/ywh15387127537/article/details/88071171