github地址:https://github.com/fanux/tetris
很多在windows下做的,用的图形图像的库,监听键盘的线程也是封装好的,大家在看我的程序时就会发现,所有东西都是最原始的,相比较而言更原始更原汁原味一点。
接下来我把正个程序一点一点拆开与大家分享。
1:cur.h文件(我们打印图像,自然要移动光标,大家是不是对移动光标很有兴趣?马上教你怎么实现!)#ifndef CUR_H_
#define CUR_H_
#include
using namespace std;
class Cur{
public:
void saveCur(); //保存光标位置
void moveCur(const int x,const int y); //移动光标位置到(x,y)坐标
void resumeCur(); //恢复光标位置
};
#endif
实现cur.cpp文件void Cur::moveCur(const int x,const int y)
{
int i;
for(i = 0; i
printf("\33[2C");
for(i = 0; i
printf("\33[1B");
}
void Cur::saveCur()
{
//save cur and move to destination
printf("\33[s");
}
void Cur::resumeCur()
{
printf("\33[u");
}
是不是移动光标比想象中的简单的多?这样封装起来一旦你的程序中想把光标移到哪就只需要搞一个Cur对象,然后就想在哪打印就在哪打印了。用起来很方便,而且还可以用在其它程序中。
2:cubePoint.h文件,屏幕上怎样打印一个小方块(各种颜色的)?然后我们就用小方块组成我们想要#ifndef CUBEPOINT_H_
#define CUBEPOINT_H_
#include
#include "cur.h"
using namespace std;
enum color{
CLEAR = 0,
BLACK = 30,
RED,
GREEN,
YELLOW,
BLUE,
PURPLE,
DEEP_GREEN,
WHITE
};
/*
* 每个点显然有个坐标值
* 每个点有一个颜色
*/
class CubePoint{
protected:
int color;
int x;
int y; //每个方格点有颜色属性,坐标属性
public:
CubePoint()
{
color = CLEAR;
x = 0;
y = 0;
}
CubePoint(int a,int b,int c)
{
color = a;
x = b;
y = c;
}
void setLocate(const int x,const int y){this->x = x;this->y = y;}
void setColor(const int color){this->color = color;}
int getColor(){return color;}
void getLocate(int&x,int &y){x = this->x;y = this->y;}
//打印点的方法
void printPoint();
};
#endif
实现cubePoint.cpp 我们同样是使用强大的printf函数打印各种各样的点,这里就需要移动光标了!void CubePoint::printPoint()
{
Cur cur;
cur.saveCur();
cur.moveCur(x,y);
switch(color)
{
case BLACK :printf("\033[40;30m \033[0m");break;
case RED :printf("\033[41;31m \033[0m");break;
case GREEN :printf("\033[42;32m \033[0m");break;
case YELLOW :printf("\033[43;33m \033[0m");break;
case BLUE :printf("\033[44;34m \033[0m");break;
case PURPLE :printf("\033[45;35m \033[0m");break;
case DEEP_GREEN:printf("\033[46;36m \033[0m");break;
case WHITE :printf("\033[47;37m \033[0m");break;
case CLEAR :printf("\033[8m ");
default:
break;
}
cur.resumeCur();
}
可能很多人没这样用过printf函数吧,赶紧去试试!最后一个CLEAR是擦出,也放到一起了,使用起来也方便~
3:图形工厂闪亮登场!graph.h我每种方块是用一个3X3的数组保存的(所以我的长条形状只有三格长度,不是为了做游戏而做游戏,咱的目的是实现功能,从而更好的了解c++对吧。)#include
using namespace std;
#include
#include "cubePoint.h"
#include
#define DOWN 0
#define LEFT 1
#define RIGHT 2
class Gbase{
protected:
int x;
int y; //a[0][0]的位置
int a[3][3];
public:
Gbase(){
int i,j;
x = 0;
y = 0;
for(j = 0; j
for(i = 0; i
a[i][j] = 0;
}
int move(int dir);
virtual int roll();
virtual void draw(){}
void setLocate(int a,int b){x = a;y = b;}
void getLocate(int* a,int* b){*a = x;*b = y;}
void printG(int color);
//获取数组首地址
void* getArray(){return (void*)a;}
};
class Zgraph : public Gbase{
public:
void draw(){
a[0][0] = 1;
a[0][1] = 1;
a[0][2] = 0;
a[1][0] = 0;
a[1][1] = 1;
a[1][2] = 1;
a[2][0] = 0;
a[2][1] = 0;
a[2][2] = 0;
}
};
class Tgraph : public Gbase{
public:
void draw(){
a[0][0] = 1;
a[0][1] = 1;
a[0][2] = 1;
a[1][0] = 0;
a[1][1] = 1;
a[1][2] = 0;
a[2][0] = 0;
a[2][1] = 0;
a[2][2] = 0;
}
};
class Ograph : public Gbase{
public:
void draw(){
a[0][0] = 1;
a[0][1] = 1;
a[0][2] = 0;
a[1][0] = 1;
a[1][1] = 1;
a[1][2] = 0;
a[2][0] = 0;
a[2][1] = 0;
a[2][2] = 0;
}
virtual int roll(){};
};
class Igraph : public Gbase{
public:
void draw(){
a[0][0] = 0;
a[0][1] = 1;
a[0][2] = 0;
a[1][0] = 0;
a[1][1] = 1;
a[1][2] = 0;
a[2][0] = 0;
a[2][1] = 1;
a[2][2] = 0;
}
};
class Lgraph : public Gbase{
public:
void draw(){
a[0][0] = 0;
a[0][1] = 1;
a[0][2] = 0;
a[1][0] = 0;
a[1][1] = 1;
a[1][2] = 0;
a[2][0] = 0;
a[2][1] = 1;
a[2][2] = 1;
}
};
class Context
{
private:
Gbase* gbase;
public:
~Context()
{
delete gbase;
}
Context(char cType)
{
switch(cType)
{
case 'Z':
gbase = new Zgraph();
break;
case 'T':
gbase = new Tgraph();
break;
case 'O':
gbase = new Ograph();
break;
case 'I':
gbase = new Igraph();
break;
case 'L':
gbase = new Lgraph();
break;
default:
printf("no %c type\n",cType);
break;
}
}
int move(int dir){return gbase->move(dir);}
int roll(){return gbase->roll();}
void draw(){gbase->draw();}
void setLocate(int a,int b){gbase->setLocate(a,b);}
void getLocate(int *a,int* b){gbase->getLocate(a,b);}
void* getArray(){gbase->getArray();}
void printG(int color){gbase->printG(color);}
};
这里用了一个图形的基类Gbase然后五个方块就五个子类(所以你自己想添加新的形状就去继承就可以了)外加一个Context类,这就是一个类似工厂的玩意儿,当然这里还使用到了策略模式。这样我们在处理逻辑的时候就可以将Context类作为接口,不需要关心什么基类子类了,用起来更方便。
实现很简单graph.cppvoid Gbase::printG(int color)
{
int i,j;
CubePoint p;
for(i = x; i
for(j = y; j
{
if(a[i - x][j - y] == 1)
{
p.setLocate(i,j);
p.setColor(color);
p.printPoint();
}
}
}
int Gbase::move(int dir)
{
switch(dir)
{
case DOWN:x++;break;
case LEFT:y--;break;
case RIGHT:y++;break;
default:
break;
}
return 0;
}
int Gbase::roll()
{
int i,j;
int b[3][3];
for(i = 0; i
for(j = 0; j
{
b[2-j][i] = a[i][j];
}
for(i = 0; i
for(j = 0; j
{
a[i][j] = b[i][j];
}
}
到此为止,准备工作基本完成了,现在就可以开始处理逻辑了,我们得把程序跑在面板上
4:game.h#include "graph.h"
class Game
{
private:
int m_penal[24][17];
Context* m_graph;
int x;
int y;//当前方块的位置,方块移动或者旋转成功后才可以设置这个值
private:
//恢复设置(方块会探索下一个位置是否合法,不合法需恢复面板)
bool recoverPenal();
//是否着陆(是否碰到下边)
bool isAttachBottom();
//是否碰到左边
bool isAttachLeft();
//是否碰到右边
bool isAttachRight();
//随机获取方块形状
char getShape();
//用方块数组给面板数组赋值
bool setPenal();
//方块动过后要把遗留面板信息擦除
bool erasePenal();
public:
Game();
//随机创建方块的方法
void createCube();
//移动的方法,移动的过程中对m_penal的改变
void move(int dir);
//旋转的方法。。。
void roll();
//方块停止
void stop();
//擦除满行
void erase();
//擦除完上面的图形整块坠落
void down(int level);
};
实现:要处理的逻辑还是比较多的,但是基本看代码就能看懂,我就直接贴上去了
game.cpp#define PENAL_SIZE (17*24*sizeof(int))
#define CUBE_SIZE (3*3*sizeof(int))
Game::Game()
{
m_graph = NULL;
x = 1;
y = 7;
CubePoint p;
int i;
memset((void*)m_penal,0,PENAL_SIZE);
for(i = 0; i
{
p.setLocate(i,0);
p.setColor(BLUE);
p.printPoint();
p.setLocate(i,16);
p.setColor(BLUE);
p.printPoint();
m_penal[i][0] = 1;
m_penal[i][16] = 1;
}
for(i = 0; i
{
p.setLocate(23,i);
p.setColor(BLUE);
p.printPoint();
p.setLocate(0,i);
p.setColor(RED);
p.printPoint();
m_penal[23][i] = 1;
m_penal[0][i] = 1;
}
/*测试面板值是否正常
for(i = 0; i
{
for(int j = 0; j
cout <
cout <
}
*/
fflush(stdout);
}
char Game::getShape()
{
Rand r;
char ch;
switch(r.randNum(1,6))
{
case 1:ch = 'Z';break;
case 2:ch = 'T';break;
case 3:ch = 'O';break;
case 4:ch = 'I';break;
case 5:ch = 'L';break;
default:
cout<
ch = '\0';
break;
}
return ch;
}
bool Game::erasePenal()
{
int i,j;
int b[3][3] = {0}; //获取方块数组
m_graph->printG(CLEAR);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
for(i = 0; i
for(j = 0; j
{
m_penal[i + x][j + y] -= b[i][j];
}
return true;
}
bool Game::recoverPenal()
{
int i,j;
int b[3][3] = {0}; //获取方块数组
memcpy(b,m_graph->getArray(),CUBE_SIZE);
for(i = x; i
for(j = y; j
{
m_penal[i][j] += b[i-x][j-y];
}
return true;
}
bool Game::setPenal()
{
int i,j;
int b[3][3] = {0}; //获取方块数组
m_graph->getLocate(&x,&y);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
/*测试取到方块数组是否正常
for(i = 0;i
{
for(j = 0; j
cout<
cout<
}
*/
for(i = x; i
for(j = y; j
{
m_penal[i][j] += b[i-x][j-y];
if(m_penal[i][j] > 1)
{
cout<
//加分数统计排行榜等
system("stty icanon echo");
exit(0);
}
}
return true;
}
void Game::createCube()
{
m_graph = new Context(getShape());
m_graph->draw();
m_graph->setLocate(1,7);
setPenal();
m_graph->printG(YELLOW);
/*
for(int i = 0; i
{
for(int j = 0; j
cout <
cout <
}
*/
}
void Game::move(int dir)
{
erasePenal();
switch(dir)
{
case DOWN:
if(false == isAttachBottom())
{
m_graph->move(DOWN);
setPenal();
m_graph->printG(YELLOW);
}
else
{
recoverPenal();
m_graph->printG(YELLOW);
erase();
stop();
}
break;
case LEFT:
if(false == isAttachLeft())
{
m_graph->move(LEFT);
setPenal();
m_graph->printG(YELLOW);
}
else
{
recoverPenal();
m_graph->printG(YELLOW);
}
break;
case RIGHT:
if(false == isAttachRight())
{
m_graph->move(RIGHT);
setPenal();
m_graph->printG(YELLOW);
}
else
{
recoverPenal();
m_graph->printG(YELLOW);
}
break;
default:
break;
}
}
void Game::roll()
{
//取出方块的值,先放到一个数组中
int i,j;
int flag = 0;
int b[3][3] = {0}; //获取方块数组
int temp[3][3] = {0};
m_graph->getLocate(&x,&y);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
erasePenal();
//旋转数组
for(i = 0; i
for(j = 0; j
{
temp[2-j][i] = b[i][j];
}
//判断旋转后是否会与面板重合
for(i = 0; i
{
for(j = 0; j
{
if (temp[i][j] == 1 && m_penal[x + i][y + j] == 1)
{
flag = 1;
break;
}
}
if(flag == 1)
break;
}
//如果不重合则旋转方块,设置面板的值
if(flag == 0)
{
m_graph->roll();
}
setPenal();
m_graph->printG(YELLOW);
}
void Game::stop()
{
delete m_graph;
createCube();
}
bool Game::isAttachBottom()
{
int i,j;
int cube_x,cube_y;
int b[3][3] = {0}; //获取方块数组
int flag = false;
m_graph->getLocate(&cube_x,&cube_y);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
for(i = 0; i
{
for(j = 0; j
{
if (b[i][j] == 1 && m_penal[i + cube_x + 1][j + cube_y] == 1)
{
flag = true;
break;
}
}
if (flag == true)
break;
}
return flag;
}
bool Game::isAttachLeft()
{
int i,j;
int cube_x,cube_y;
int b[3][3] = {0}; //获取方块数组
int flag = false;
m_graph->getLocate(&cube_x,&cube_y);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
for(i = 0; i
{
for(j = 0; j
{
if (b[i][j] == 1 && m_penal[i + cube_x][j + cube_y - 1] == 1)
{
flag = true;
break;
}
}
if (flag == true)
break;
}
return flag;
}
bool Game::isAttachRight()
{
int i,j;
int cube_x,cube_y;
int b[3][3] = {0}; //获取方块数组
int flag = false;
m_graph->getLocate(&cube_x,&cube_y);
memcpy(b,m_graph->getArray(),CUBE_SIZE);
for(i = 0; i
{
for(j = 0; j
{
if (b[i][j] == 1 && m_penal[i + cube_x][j + cube_y + 1] == 1)
{
flag = true;
break;
}
}
if (flag == true)
break;
}
return flag;
}
void Game::erase()
{
int i,j;
int flag = 0;
for(i = 22; i > 0; i--)
{
for(j = 1; j
{
if(m_penal[i][j] == 0)
{
flag = 1;
}
}
if(flag == 0)
{
//该行上面的图形整体坐落
down(i);
i++;
}
flag = 0;
}
}
void Game::down(int level)
{
int i,j;
int flag = 1;
for(i = level; i > 1; i--)
for(j = 1; j
{
m_penal[i][j] = m_penal[i - 1][j];
}
//刷新面板
CubePoint p;
for(i = 1; i
for(j = 1; j
{
if(m_penal[i][j] == 1)
{
p.setLocate(i,j);
p.setColor(YELLOW);
p.printPoint();
}
if(m_penal[i][j] == 0)
{
p.setLocate(i,j);
p.setColor(CLEAR);
p.printPoint();
}
}
}
5:main函数:void* listenKey(void *key)
{
char* key_;
key_ = (char*)key;
while(1)
{
system("stty -icanon -echo");
*key_ = getchar();
system("stty icanon echo");
}
}
int main()
{
pthread_t t1;
char key;
system("clear");
Game g;
g.createCube();
key = 's';
pthread_create(&t1,NULL,listenKey,(void*)(&key));
while(1)
{
fflush(stdout);
usleep(500000);
switch(key)
{
case 's':
g.move(DOWN);break;
case 'a':
g.move(LEFT);key = 's';break;
case 'd':
g.move(RIGHT);key = 's';break;
case 'w':
g.roll();key = 's';break;
default:
g.move(DOWN);break;
}
g.move(DOWN);
}
return 0;
}
这里要注意看我是怎么设置无输入缓存的!很多人无法做出来就是卡在这了(stty -icanon -echo)。