用了大概三天时间用C语言加ncurses.h库在Linux下面写了一个简易的俄罗斯方块,还有很多地方可以完善,不过不打算写了。运行效果如下:
用方向键控制方块的移动,向上变换形状,ESC退出。
写之前参考了一本书上的方块等的数据结构定义(书名我真的忘了,好像叫《C语言项目实践……》)。
main.c 文件:
/*
***************************
* Copyright(C):
* Filename :
* Author :
* Version :
* Date :
* Description :
****************************
*/
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <ncurses.h>
#include "colors.h"
#include "tetris.h"
#include "shapes.h"
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))
/*---------------------CONSTS--------------------------*/
#define MYKEY_ESC 27
/* 每个Block在Y和X方向占用字符数 */
#define BLOCK_CHARS_Y 4
#define BLOCK_CHARS_X 8
/* Window左上角X Y 坐标 */
#define WINDOW_ORGN_X 10
#define WINDOW_ORGN_Y 5
/* Window在X和Y方向可以容纳的Block数 */
#define WINDOW_BLOCKS_Y 8
#define WINDOW_BLOCKS_X 5 /* 这个值应尽量是奇数,保证新的Block从中间出现 */
/* Window的字符宽度和长度 */
#define WINDOW_HEIGHT (WINDOW_BLOCKS_Y * BLOCK_CHARS_Y +2)
#define WINDOW_WIDTH (WINDOW_BLOCKS_X * BLOCK_CHARS_X +2)
/* 子窗口大小 要至少可以容纳一个方块大小*/
#define SUBWND_HEIGHT WINDOW_HEIGHT
#define SUBWND_WIDTH (BLOCK_CHARS_X*5/2)
/* 子窗口显示位置(左上角坐标) */
#define SUBWND_ORGN_X (WINDOW_ORGN_X+WINDOW_WIDTH)
#define SUBWND_ORGN_Y (WINDOW_ORGN_Y)
/* _board_table数组大小 */
#define BOARD_TABLE_X (WINDOW_BLOCKS_X*4)
#define BOARD_TABLE_Y (WINDOW_BLOCKS_Y*4)
/* 新的Block出现时的X Y坐标 */
#define BLOCK_INIT_X (1+BLOCK_CHARS_X*((WINDOW_BLOCKS_X)/2));
#define BLOCK_INIT_Y 1
/* 每消除一行增加的分数 */
#define SCORE_ADD_PERLINE 100
/* 每升一级需要的分数 */
#define SCORE_PERLEVEL 1000
/*--------- GLOBAL VARIABLES DECLARATION----------------*/
static int _block_x; /* 方块左上角x坐标 */
static int _block_y; /* 方块左上角y坐标 */
static int _block_index;
static int _block_next;
static int _table_shape_curr[4][4];
static int _table_shape_next[4][4];
static int _line_noneempty_min;
static int _flag_new; /* 新方块产生标志 */
static int _flag_done; /* 游戏结束标志 */
static int _time_down; /* 方块下降时间,ms */
static int _window_height;
static int _window_width;
static short _bg_color; /* 背景颜色 */
static short _fg_color; /* 前景颜色 */
static WINDOW *_pwindow;
static WINDOW *_subwnd;
static struct BOARD _board_table[ BOARD_TABLE_Y ][ BOARD_TABLE_X ];
static int _level_max; /* 最大等级 */
static int _level_cur; /* 当前等级 */
static unsigned int _score; /* 分数 */
static int _table_downtime[ ] = { /* 每个等级对应的下降时间,ms */
500,450,400,350,300,250,200,250,100,50,20
};
static const unsigned char _arrow_up[5][5] = {
{0,0,1,0,0},
{0,1,1,1,0},
{0,0,1,0,0},
{0,0,1,0,0},
{0,0,1,0,0},
};
static const unsigned char _arrow_right[5][5] = {
{0,0,0,0,0},
{0,0,0,1,0},
{1,1,1,1,1},
{0,0,0,1,0},
{0,0,0,0,0},
};
static const unsigned char _arrow_left[5][5] = {
{0,0,0,0,0},
{0,1,0,0,0},
{1,1,1,1,1},
{0,1,0,0,0},
{0,0,0,0,0},
};
static const unsigned char _arrow_down[5][5] = {
{0,0,1,0,0},
{0,0,1,0,0},
{0,0,1,0,0},
{0,1,1,1,0},
{0,0,1,0,0},
};
/*----------FUNCTION DECLARATION----------------------*/
void CalBlockShape(int index, int buf[4][4]);
void Change(void);
int CearFullLines(void);
int Collision(void);
WINDOW *CreateNewWindow(int height, int weight, int y, int x);
void CreateSquare(WINDOW *win, int y, int x);
void DrawArrow(const unsigned char arrow[5][5], int y, int x );
void DrawBackground(int l);
void DrawNext(WINDOW *win,int y,int x);
void DrawShape(WINDOW *win, int index, int table_shape[4][4], int tl_y, int tl_x);
void DrawSquare(WINDOW *win, int y, int x);
int HasFullLine(int start, int end, int *fullline);
void InitNcurses(void);
void Initial(void);
int LeftBorder(void);
int MaxRight(void);
int MinLeft(void);
void OnAlarm(int p);
void OnColor(WINDOW *win, int index);
void OffColor(WINDOW *win, int index);
void RandomBlock(void);
int RightBorder(void);
void ShowInfo(WINDOW *win);
void UpdateBackground(int y, int x);
/* main */
int main(int argc, char**argv){
int ch = 0;
int quit = 0;
struct itimerval tick;
int max_x, max_y;
Initial();
InitNcurses();
getmaxyx(stdscr,max_y,max_x);
if ( max_x < WINDOW_ORGN_X+WINDOW_WIDTH+SUBWND_WIDTH+5 ||
max_y < WINDOW_ORGN_Y+WINDOW_HEIGHT+2 ){
addstr("your screen is to small! press any key to quit!");
getch();
endwin();
exit(1);
}
curs_set(0); /* 隐藏光标 */
_pwindow = CreateNewWindow(WINDOW_HEIGHT,
WINDOW_WIDTH,
WINDOW_ORGN_Y,
WINDOW_ORGN_X
);
wattron(_pwindow,A_REVERSE); /* 反白显示 */
_subwnd = CreateNewWindow(SUBWND_HEIGHT,
SUBWND_WIDTH,
SUBWND_ORGN_Y,
SUBWND_ORGN_X
);
wattron(_subwnd,A_REVERSE); /* 反白显示 */
attron(COLOR_PAIR(7));
mvaddstr(0,1,"Press ESC to quit");
mvaddstr(1,1,"Press Up to change");
mvaddstr(2,1,"Press Arrows to move");
attroff(COLOR_PAIR(7));
refresh();
signal(SIGALRM,OnAlarm);
tick.it_value.tv_sec = _time_down/1000;
tick.it_value.tv_usec = 1000*(_time_down%1000);
tick.it_interval.tv_sec = _time_down/1000;
tick.it_interval.tv_usec = 1000*(_time_down%1000);
setitimer(ITIMER_REAL, &tick, NULL);
while( 1 ){ /* press ESC to quit*/
switch( ch=getch() ){
case KEY_LEFT : /* 左移 */
if ( !MinLeft() )
_block_x = _block_x-2; /* 注意这里不是减1 */
break;
case KEY_RIGHT : /* 右移 */
if ( !MaxRight() )
_block_x = _block_x+2; /* 注意这里不是加1 */
break;
case KEY_DOWN : /* 向下 */
while( !Collision() )
_block_y = _block_y+1;
break;
case KEY_UP : /* 变形 */
Change();
break;
case MYKEY_ESC : /* 退出 */
quit = 1;
break;
}
if ( quit != 0 ) /* 推出游戏 */
break;
werase(_pwindow); /* 用werase() 用wclear()屏幕会闪烁 */
DrawShape(_pwindow, _block_index, _table_shape_curr, _block_y, _block_x);
DrawBackground(_line_noneempty_min);
box(_pwindow,'|','-');
wrefresh(_pwindow);
}
delwin(_pwindow);
delwin(_subwnd);
endwin();
return 0;
};
void CalBlockShape(int index, int buf[4][4]){
int i;
unsigned int value;
value = (_shapes[index].box[0]<<8)|(_shapes[index].box[1]);
for (i=0; i<4; i++){
buf[0][i] = (value&(1U<<(15-i)))!=0 ? 1:0;
buf[1][i] = (value&(1U<<(11-i)))!=0 ? 1:0;
buf[2][i] = (value&(1U<<(7-i )))!=0 ? 1:0;
buf[3][i] = (value&(1U<<(3-i )))!=0 ? 1:0;
}
}
/*******************************************
* Change();
* 如果满足变形的条件则变换形状
* Input : NONE
* Output: NONE
* Return: 可以变形返回 1,不可以返回 0
*-------------------------------------------
* 备注:判断依据是变形之后是否有部分方块在
* 右边界之外(不可能从左边界出去)
*******************************************/
void Change(void){
int tmp_index;
/* 如果已经到底,不能变形 */
if ( Collision() )
return;
tmp_index = _block_index;
_block_index = _shapes[_block_index].next;
CalBlockShape(_block_index,_table_shape_curr);
/* 有可能发生碰撞 */
if ( _block_x+8 > WINDOW_WIDTH-2 ){
_block_x = _block_x-2;
if ( RightBorder() ){
_block_x = _block_x+2;
_block_index = tmp_index;
CalBlockShape(_block_index,_table_shape_curr);
return;
}
_block_x = _block_x+2;
return;
}
}
int ClearFullLines(void){
int i, j;
int end = BOARD_TABLE_Y-1;
int start = _line_noneempty_min;
int fullline;
int cnt=0;
while( HasFullLine(start,end,&fullline) ){
for ( i=fullline; i>=_line_noneempty_min && i>=1; i-- ){
for( j=0; j<BOARD_TABLE_X; j++ ){
_board_table[i][j] = _board_table[i-1][j];
}
}
_line_noneempty_min++; /* 注意这里不是减奥 */
end = fullline;
start = _line_noneempty_min;
cnt++;
}
return cnt; /* 返回清除的行数 */
}
int Collision(void){
int i, j, x,y;
x = (_block_x-1)/2;
y = (_block_y-1)/1;
for (i=0; i<4 && y<BOARD_TABLE_Y; i++ ){
for (j=0; j<4 && x<BOARD_TABLE_X; j++){
if (_table_shape_curr[i][j] != 0 &&
_board_table[y+1][x].value != 0
)
return 1;
x++;
}
if ( i<3 &&
_table_shape_curr[i+1][0] == 0 &&
_table_shape_curr[i+1][1] == 0 &&
_table_shape_curr[i+1][2] == 0 &&
_table_shape_curr[i+1][3] == 0
)
break;
if ( i == 3)
break;
x = (_block_x-1)/2;
y++;
}
if ( y+1 >= WINDOW_BLOCKS_Y*BLOCK_CHARS_Y ) /* 到底了 */
return 1;
return 0;
}
WINDOW *CreateNewWindow(int height, int weight, int y, int x){
WINDOW *pwin = newwin(height, weight, y, x);
box(pwin,'|','-');
wrefresh(pwin);
return pwin;
}
void DrawArrow(const unsigned char arrow[5][5], int y, int x ){
int i,j;
int x0 = x;
for ( i=0; i<5; i++ ){
for ( j=0; j<5; j++ ){
if ( arrow[i][j] )
mvaddch(y,x,'|');
x++;
}
x = x0;
y++;
}
}
void DrawBackground(int line){
int i, j;
int r_x, r_y;
r_x = 1;
r_y = line+1;
for ( i=line; i<BOARD_TABLE_Y; i++ ){
for ( j=0; j<BOARD_TABLE_X; j++ ){
if ( _board_table[i][j].value != 0 ){
OnColor(_pwindow, _board_table[i][j].color);
DrawSquare(_pwindow,r_y,r_x);
OffColor(_pwindow,_board_table[i][j].color);
}
r_x+=2;
}
r_x = 1;
r_y += 1;
}
}
void DrawSquare(WINDOW *win,int y, int x){
if ( win == NULL )
win = stdscr;
mvwaddch(win,y,x,'[');
mvwaddch(win,y,x+1,']');
}
void DrawShape(WINDOW *win, int index, int table_shape[4][4], int tl_y, int tl_x){
int i, j, y, x;
y = tl_y;
x = tl_x;
if ( win == NULL )
win = stdscr;
OnColor(win, _shapes[index].color_id);
for (i=0; i<4; i++){
for (j=0; j<4; j++){
if( table_shape[i][j] != 0 )
DrawSquare(win,y,x);
x+=2;
}
x = tl_x;
y++;
}
OffColor(win, _shapes[index].color_id);
}
int HasFullLine(int start, int end, int *fullline){
int i, j;
if ( start<0 || end>BOARD_TABLE_Y-1 )
return 0;
/* 从下往上找,一旦找到便退出 */
for ( i=end; i>=start; i-- ){
for ( j=0; j<BOARD_TABLE_X; j++ ){
if ( _board_table[i][j].value == 0 )
break;
}
if ( j == BOARD_TABLE_X ){
*fullline = i;
return 1;
}
}
return 0;
}
int LeftBorder(void){
if ( _block_x <= 1 ){
if ( _shapes[_block_index].box[0] & 0x88 ||
_shapes[_block_index].box[1] & 0x88
)
return 1;
else
return 0;
}
return 0;
}
int MaxRight(void){
int i,j;
int x,y;
if ( RightBorder() )
return 1;
y = (_block_y-1)/1;
for ( i=0; i<4 && y<BOARD_TABLE_Y && y>=0; i++ ){
x = (_block_x-1)/2+3;
for ( j=3; j>=0 && x+1<BOARD_TABLE_X && x+1>=0; j-- ){
if ( _table_shape_curr[i][j] != 0 ){
if ( _board_table[y][x+1].value != 0 )
return 1;
else
break;
}
x--;
}
y++;
}
return 0;
}
int MinLeft(void){
int i,j;
int x,y;
if ( LeftBorder() )
return 1;
x = (_block_x-1)/2;
y = (_block_y-1)/1;
for ( i=0; i<4 && y<BOARD_TABLE_Y && y>=0; i++ ){
for ( j=0; j<4 && x-1<BOARD_TABLE_X && x-1>=0; j++ ){
if ( _table_shape_curr[i][j] != 0 ){
if ( _board_table[y][x-1].value != 0 )
return 1;
else
break;
}
x++;
}
x = (_block_x-1)/2;
y++;
}
return 0;
}
void OnColor(WINDOW *win, int index){
if ( win == NULL )
win = stdscr;
switch( index ){
case 1 :
wattron(win,COLOR_PAIR(1));
break;
case 2 :
wattron(win,COLOR_PAIR(2));
break;
case 3 :
wattron(win,COLOR_PAIR(3));
break;
case 4 :
wattron(win,COLOR_PAIR(4));
break;
case 5 :
wattron(win,COLOR_PAIR(5));
break;
case 6 :
wattron(win,COLOR_PAIR(6));
break;
case 7 :
wattron(win,COLOR_PAIR(7));
break;
default:
break;
}
}
void OffColor(WINDOW *win, int index){
if ( win == NULL )
win = stdscr;
switch( index ){
case 1 :
wattroff(win,COLOR_PAIR(1));
break;
case 2 :
wattroff(win,COLOR_PAIR(2));
break;
case 3 :
wattroff(win,COLOR_PAIR(3));
break;
case 4 :
wattroff(win,COLOR_PAIR(4));
break;
case 5 :
wattroff(win,COLOR_PAIR(5));
break;
case 6 :
wattroff(win,COLOR_PAIR(6));
break;
case 7 :
wattroff(win,COLOR_PAIR(7));
break;
default:
break;
}
}
void OnAlarm(int p){
int lines = 0;
if ( _flag_done != 0 ){
return;
}
if ( _flag_new!=0 ){
_flag_new = 0;
RandomBlock();
ShowInfo(_subwnd);
}else if ( !Collision() ){
_block_y++;
}else{
if ( _block_y <= BLOCK_INIT_Y )
_flag_done = 1;
UpdateBackground(_block_y,_block_x);
_flag_new = 1;
}
werase(_pwindow);
DrawShape(_pwindow,_block_index, _table_shape_curr,_block_y,_block_x);
lines = ClearFullLines();
DrawBackground(_line_noneempty_min);
box(_pwindow,'|','-');
if ( lines ){
_score += lines*SCORE_ADD_PERLINE;
_level_cur = MIN(_score/SCORE_PERLEVEL,_level_max);
ShowInfo(_subwnd);
}
wrefresh(_pwindow);
if ( _flag_done != 0 ){
mvaddstr(WINDOW_ORGN_Y-1,WINDOW_ORGN_X,"Done!");
wrefresh(stdscr);
}
}
void RandomBlock(void){
_block_index = _block_next;
_block_y = BLOCK_INIT_Y;
_block_x = BLOCK_INIT_X;
CalBlockShape(_block_index,_table_shape_curr);
/* 随机产生下一个方块 */
srand(time(NULL));
_block_next = rand()%MAX_BOX;
}
int RightBorder(void){
int i;
int x = (WINDOW_WIDTH-1-_block_x)/2-1;
if ( _block_x+8 >= WINDOW_WIDTH-1 ){ /* +8是因为x方向每个小方块占两个字符间距 */
for (i=0;i<4;i++){
if (_table_shape_curr[i][x] != 0 )
return 1;
}
}
return 0;
}
void ShowInfo(WINDOW *win){
int x, y;
int i;
werase(win);
x = 1+SUBWND_WIDTH/2-BLOCK_CHARS_X/2;
y = 4;
DrawNext(win, y, x);
y += BLOCK_CHARS_Y+1;
for ( i=1; i<SUBWND_WIDTH-1; i++ ){
mvwaddch(win,y,i,'-');
}
wattroff(win,A_REVERSE); /* 关闭反白 */
y += 1;
mvwaddstr(win,y,x,"Next");
y = SUBWND_HEIGHT/2;
x = SUBWND_WIDTH/10;
mvwprintw(win,y,x,"%s: %d","Score",_score);
y += 2;
mvwprintw(win,y,x,"%s: %d","Level",_level_cur);
y += 4;
mvwaddstr(win,y,x,"Version: V1.0.0");
y += 1;
mvwaddstr(win,y,x,"Author : sduzh");
wattron(win,A_REVERSE); /* 打开反白 */
box(win,'|','-');
wrefresh(win);
}
void DrawNext(WINDOW *win, int y, int x){
CalBlockShape(_block_next, _table_shape_next);
DrawShape(win, _block_next, _table_shape_next,y, x);
}
void Initial(void){
int i,j;
srand(time(NULL));
_block_next = rand()%MAX_BOX;
_bg_color = COLOR_BLACK;
_fg_color = COLOR_WHITE;
_block_x = BLOCK_INIT_X;
_block_y = BLOCK_INIT_Y;
_flag_new = 1;
_flag_done = 0;
_window_height = WINDOW_BLOCKS_Y*BLOCK_CHARS_Y+2;
_window_width = WINDOW_BLOCKS_X*BLOCK_CHARS_X+2;
_line_noneempty_min = BOARD_TABLE_Y-1;
_score = 0;
_level_max = sizeof(_table_downtime)/sizeof(_table_downtime[0])-1;
_level_cur = 0;
_time_down = _table_downtime[_level_cur];
for (i=0; i<BOARD_TABLE_Y; i++ ){
for (j=1; j<BOARD_TABLE_X; j++){
_board_table[ i ][ j ].value = 0;
}
}
}
void InitNcurses(void){
initscr();
start_color();
init_pair(1, _tab_color[1], _bg_color);
init_pair(2, _tab_color[2], _bg_color);
init_pair(3, _tab_color[3], _bg_color);
init_pair(4, _tab_color[4], _bg_color);
init_pair(5, _tab_color[5], _bg_color);
init_pair(6, _tab_color[6], _bg_color);
init_pair(7, _tab_color[7], _bg_color);
cbreak();
nonl();
noecho();
intrflush(stdscr,FALSE);
keypad(stdscr,TRUE);
}
// x, y:发生碰撞时方块左上角x和y坐标
void UpdateBackground(int y, int x){
int i,j;
int row, line;
short color = _shapes[_block_index].color_id;
/* 先将字符坐标x,y转换为在_board_table数组中对应的位置 */
line = y-1;
row = (x-1)/2;
for ( i=0; (i<4) && (line+i<BOARD_TABLE_Y); i++ ){
for ( j=0; (j<4) && (row+j<BOARD_TABLE_X); j++ ){
_board_table[line+i][row+j].value |= _table_shape_curr[i][j];
if ( _table_shape_curr[i][j] != 0 )
_board_table[line+i][row+j].color = color;
}
}
_line_noneempty_min = MIN(line,_line_noneempty_min);
}
tetris.h文件:
#ifndef TEIRIS_H
#define TETRIS_H
/* Board */
struct BOARD{
int value;
int color;
};
/* Shape */
struct SHAPE{
/*一个字节8位,每4位表示游戏方块中的一行,例如:
box[0]=“0x88”,box[1]=“0xC0”表示:
1000
1000
1100
0000
*/
unsigned char box[2];
short color_id; /* 每个方块的颜色索引 */
int next; /* 下个方块的编号 */
};
#endif /* TETRIS */
shapes.c文件:
#include "tetris.h"
#include "shapes.h"
struct SHAPE _shapes[MAX_BOX] = {
/*
* 1 1 1 1 1 1 1
* 1 1 1 1 1 1
* 1 1 1
*/
{ {0x88,0xc0}, 1, 1 },
{ {0xe8,0x00}, 1, 2 },
{ {0xc4,0x40}, 1, 3 },
{ {0x2e,0x00}, 1, 0 },
/*
* 1 1 1 1 1 1 1
* 1 1 1 1 1 1
* 1 1 1
*/
{ {0x44,0xc0}, 2, 5 },
{ {0x8e,0x00}, 2, 6 },
{ {0xc8,0x80}, 2, 7 },
{ {0xe2,0x00}, 2, 4 },
/*
* 1 1 1
* 1 1 1 1
* 1
*/
{ {0x8c,0x40}, 3, 9 },
{ {0x6c,0x00}, 3, 8 },
/*
* 1 1 1
* 1 1 1 1
* 1
*/
{ {0x4c,0x80}, 4, 11 },
{ {0xc6,0x00}, 4, 10 },
/*
* 1 1 1 1 1 1
* 1 1 1 1 1 1 1 1
* 1 1
*/
{ {0x4e,0x00}, 5, 13 },
{ {0x8c,0x80}, 5, 14 },
{ {0xe4,0x00}, 5, 15 },
{ {0x4c,0x40}, 5, 12 },
/*
* 1 1 1 1 1
* 1
* 1
* 1
*/
{ {0x88,0x88}, 6, 17 },
{ {0xf0,0x00}, 6, 16 },
/*
* 1 1
* 1 1
*/
{ {0xcc,0x00}, 7, 18 },
};
shapes.h文件:
#ifndef TETRIS_SHAPES_H
#define TETRIS_SHAPES_H
//#include "tetris.h"
#define MAX_BOX 19
extern struct SHAPE _shapes[MAX_BOX];
#endif // TETRIS_SHAPES_H
colors.c文件:
#include "colors.h"
const short _tab_color[MAX_COLOR] = {
COLOR_BLACK,
COLOR_WHITE,
COLOR_CYAN,
COLOR_MAGENTA,
COLOR_YELLOW,
COLOR_BLUE,
COLOR_GREEN,
COLOR_RED,
};
colors.h文件:
#ifndef TETRIS_COLORS_H
#define TETRIS_COLORS_H
#include <ncurses.h>
#define MAX_COLOR 8
extern const short _tab_color[MAX_COLOR];
#endif /* TETRIS_COLORS_H */