面临找工作之际,因为没有项目经验,所以从牛客网上找了这个项目来实践一下,并以此博客记录实践过程中遇到的问题和收获,并且希望我的文章能帮助到大家,如果有写的不好的地方,希望大家谅解,谢谢!
项目的第一步就是配置环境,虽然课程首页有简单的环境配置方法,但是因为方法太简短,导致我第一步花费了较长时间。
第一步是安装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;
}