C Windows控制台字符版本俄罗斯方块

C Windows控制台字符版本俄罗斯方块

图片

//一个可以工作在Windows控制台字符界面下的俄罗斯方块

//工作在非图形模式,无需其他库依赖,单个C文件代码即可运行

//支持最高纪录,并且对于纪录进行了加密

//By wrule 2015年12月14日20:53:57

//控制方式 WSAD 键对应旋转,下,左,右

//需要注意的是在进行游戏之前需要按下 Ctrl + 空格 取消输入法,否则无法正确操作

#define _CRT_SECURE_NO_WARNINGS

#include

#include

#include

#include

#define SQUARE_BLANK 0

#define SQUARE_TETRIS 1

#define SQUARE_WALL 2

//俄罗斯方块图案类型,不同旋转情况下的可能个体

typedef struct pattern pattern;

struct pattern {

const int w;

const int h;

const int data[4][4];

};

//俄罗斯方块类型,可以完整描述一个俄罗斯方块的所有旋转

typedef struct tetris tetris;

struct tetris {

const int len;

const pattern * pattern_list;

};

//用于定位的俄罗斯方块类型,使用两个 id 作为索引

typedef struct itetris itetris;

struct itetris {

int id1;

int id2;

};

//可以完整描述俄罗斯方块当前状态以及操作的类型,为最终类型

typedef struct ntetris ntetris;

struct ntetris {

int x1;

int y1;

itetris its1;

int x2;

int y2;

itetris its2;

};

//以下为俄罗斯方块数据的前向声明

const tetris tetris_list[];

const size_t TETRIS_LIST_LEN;

//历史最高分玩家姓名

char hs_name[9] = “暂无”;

//历史最高分玩家得分

int hs_score = 0;

//当前得分

int now_score = 0;

//地图

int map[20][10];

//游戏初始节奏

unsigned int rhythm = 1000;

//Windows 控制台显示坐标定位

void gotoxy(int x, int y) {

COORD coord = { x, y };

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),

coord);

}

//错误抛出并且结束程序运行

void error() {

gotoxy(0, 0);

printf(“error”);

_getch();

exit(EXIT_FAILURE);

}

//绘制基本方块

void draw_square(int x, int y, int v) {

gotoxy(x * 2, y);

switch (v) {

case SQUARE_BLANK:{

  printf(”  “);

}break;

case SQUARE_TETRIS:{

  printf(“□”);

}break;

case SQUARE_WALL:{

 printf(“■”);

}break;

default:{

error();

}break;

}

}

//绘制界面框架

void draw_frame() {

int x, y;

for (y = 0; y < 22; ++y) { for (x = 0; x < 12; ++x) { if (y == 0 || y == 21 || x == 0 || x == 11) { draw_square(x, y, SQUARE_WALL); } } } for (y = 0; y < 6; ++y) { for (x = 13; x < 19; ++x) { if (y == 0 || y == 5 || x == 13 || x == 18) { draw_square(x, y, SQUARE_WALL); } } } gotoxy(28, 8); printf("最高纪录"); gotoxy(28, 14); printf("当前得分"); gotoxy(28, 21); printf("by wrule"); } //绘制俄罗斯方块 void draw_tetris(const itetris * pits, int x, int y, int v) { int px; int py; int ox; int oy; const pattern * ptrn = &(tetris_list[pits->id1].

pattern_list[pits->id2]);

int w = ptrn->w;

int h = ptrn->h;

for (py = 0; py < h; ++py) { for (px = 0; px < w; ++px) { if (ptrn->data[py][px]) {

ox = x + px;

oy = y + py;

if ((ox > -1 &&

ox < 10 && oy > -1 &&

oy < 20) || (ox > 12 &&

ox < 17 && oy > -1 &&

oy < 4)) { draw_square(ox + 1, oy + 1, v); } } } } } //在界面上绘制出下一个即将出现的俄罗斯方块 void draw_next_tetris(const itetris * pits) { const pattern * ptrn = &(tetris_list[pits->id1].

pattern_list[pits->id2]);

int w = ptrn->w;

int h = ptrn->h;

int bx;

int by;

int px = (4 – w) / 2;

int py = (4 – h) / 2;

if (h % 2) {

++py;

}

for (by = 1; by < 5; ++by) { for (bx = 14; bx < 18; ++bx) { draw_square(bx, by, SQUARE_BLANK); } } draw_tetris(pits, 13 + px, py, SQUARE_TETRIS); } //随机取得一个俄罗斯方块 void get_rnd_tetris(itetris * pits) { pits->id1 = rand() % TETRIS_LIST_LEN;

pits->id2 = rand() % tetris_list[pits->id1].len;

}

//应用针对于俄罗斯方块的操作

void use_op(ntetris * pnts) {

draw_tetris(&(pnts->its1),

pnts->x1,

pnts->y1,

SQUARE_BLANK);

pnts->x1 = pnts->x2;

pnts->y1 = pnts->y2;

pnts->its1 = pnts->its2;

draw_tetris(&(pnts->its1),

pnts->x1,

pnts->y1,

SQUARE_TETRIS);

}

//俄罗斯方块自然下落

void general_fall(ntetris * pnts) {

pnts->x2 = pnts->x1;

pnts->y2 = pnts->y1 + 1;

pnts->its2 = pnts->its1;

}

//用户手动左移俄罗斯方块

void hand_left(ntetris * pnts) {

pnts->x2 = pnts->x1 – 1;

pnts->y2 = pnts->y1;

pnts->its2 = pnts->its1;

}

//用户手动右移俄罗斯方块

void hand_right(ntetris * pnts) {

pnts->x2 = pnts->x1 + 1;

pnts->y2 = pnts->y1;

pnts->its2 = pnts->its1;

}

#define TURN_LEFT -1

#define TURN_RIGHT 1

//用户手动旋转俄罗斯方块

void hand_turn(ntetris * pnts, int dir) {

int px, py;

const pattern * ptrn1;

int w1, h1;

const pattern * ptrn2;

int w2, h2;

int len = tetris_list[pnts->its1.id1].len;

pnts->its2 = pnts->its1;

pnts->its2.id2 += dir;

if (pnts->its2.id2 >= len) {

pnts->its2.id2 = 0;

}

else if (pnts->its2.id2 <= -1) { pnts->its2.id2 = len – 1;

}

ptrn1 = &(tetris_list[pnts->its1.id1].

pattern_list[pnts->its1.id2]);

ptrn2 = &(tetris_list[pnts->its2.id1].

pattern_list[pnts->its2.id2]);

w1 = ptrn1->w;

h1 = ptrn1->h;

w2 = ptrn2->w;

h2 = ptrn2->h;

px = (w1 – w2) / 2;

py = (h1 – h2) / 2;

pnts->x2 = pnts->x1 + px;

pnts->y2 = pnts->y1 + py;

}

//向地图之中放置一个俄罗斯方块

void put_tetris(ntetris * pnts, itetris * pits) {

const pattern * ptrn = &(tetris_list[pits->id1].

pattern_list[pits->id2]);

int w = ptrn->w;

int h = ptrn->h;

pnts->x2 = (10 – w) / 2;

pnts->y2 = -h;

pnts->its2 = *pits;

pnts->x1 = pnts->x2;

pnts->y1 = pnts->y2;

pnts->its1 = pnts->its2;

use_op(pnts);

}

//初始化地图数据

void init_map() {

int x, y;

for (y = 0; y < 20; ++y) { for (x = 0; x < 10; ++x) { map[y][x] = 0; } } } //判断针对于俄罗斯方块的操作是否非法 int op_isilegal(ntetris * pnts) { int x = pnts->x2;

int y = pnts->y2;

const pattern * ptrn = &(tetris_list[pnts->its2.id1].

pattern_list[pnts->its2.id2]);

int w = ptrn->w;

int h = ptrn->h;

int px, py;

int ox, oy;

int ret = 0;

for (py = 0; py < h; ++py) { for (px = 0; px < w; ++px) { if (ptrn->data[py][px]) {

ox = x + px;

oy = y + py;

if (ox < 0 || ox > 9 ||

oy > 19) {

ret = 1;

goto pret;

}

else {

if (oy > -1) {

if (map[oy][ox]) {

ret = 1;

goto pret;

}

}

}

}

}

}

pret:

return ret;

}

//俄罗斯方块被放置之后写入地图数据

void write_tetris(ntetris * pnts) {

int x = pnts->x1;

int y = pnts->y1;

const pattern * ptrn = &(tetris_list[pnts->its1.id1].

pattern_list[pnts->its1.id2]);

int w = ptrn->w;

int h = ptrn->h;

int px, py;

int ox, oy;

for (py = 0; py < h; ++py) { for (px = 0; px < w; ++px) { if (ptrn->data[py][px]) {

ox = x + px;

oy = y + py;

map[oy][ox] = 1;

}

}

}

}

//绘制地图

void draw_map() {

int x, y;

for (y = 0; y < 20; ++y) { for (x = 0; x < 10; ++x) { draw_square(x + 1, y + 1, map[y][x] ? SQUARE_TETRIS : SQUARE_BLANK); } } } //试图消行 int try_clear() { int score = 0; int i; int x, y; int flag; int clear = 0; int list[20]; int tmap[20][10]; //记录应当被消除的列表 for (y = 0; y < 20; ++y) { flag = 1; for (x = 0; x < 10; ++x) { if (0 == map[y][x]) { flag = 0; break; } } list[y] = flag; } //记录消除标志和消除得分 for (i = 0; i < 20; ++i) { if (1 == list[i]) { score++; clear = 1; } } if (clear) { //显示消除动画 for (i = 0; i < 3; ++i) { for (x = 0; x < 10; ++x) { for (y = 0; y < 20; ++y) { if (list[y]) { draw_square(x + 1, y + 1, (i % 2) ? SQUARE_TETRIS : SQUARE_BLANK); } } } Sleep(250); } //初始化备份地图 for (y = 0; y < 20; ++y) { for (x = 0; x < 10; ++x) { tmap[y][x] = 0; } } //在备份地图上执行消除 i = 19; for (y = 19; y > -1; –y) {

if (0 == list[y]) {

for (x = 0; x < 10; ++x) { tmap[i][x] = map[y][x]; } --i; } } //把消除之后的数据回写入地图 for (y = 0; y < 20; ++y) { for (x = 0; x < 10; ++x) { map[y][x] = tmap[y][x]; } } //在界面上重绘地图 draw_map(); } rhythm -= score; if (rhythm < 50) { rhythm = 50; } return score; } //更新当前玩家分数 void update_now_score() { gotoxy(30, 16); printf("        "); gotoxy(30, 16); printf("%d", now_score); } //更新历史玩家数据 void update_hs_data() { int i; gotoxy(28, 10); printf("        "); gotoxy(28, 10); for (i = 0; i < 8 && hs_name[i]; ++i) { putchar(hs_name[i]); } gotoxy(30, 12); printf("        "); gotoxy(30, 12); printf("%d", hs_score); } #define NEXT_STEP 0 #define WRITE_WIN 1 #define GAME_OVER 2 //俄罗斯方块自然下落之后的处理过程 int after_fall(ntetris * pnts) { int pscore; //如果试图进行的操作不违法 if (op_isilegal(pnts) == 0) { use_op(pnts); return NEXT_STEP; } else { //如果垒满,游戏结束 if (pnts->y1 < 0) { return GAME_OVER; } else { //放置好俄罗斯方块,并且向地图写入数据 write_tetris(pnts); //试图消行并且获得得分 pscore = try_clear(); //如果得分非零,则更新当前玩家得分 if (pscore) { now_score += pscore; update_now_score(); } return WRITE_WIN; } } } //BKDR Hash 函数 unsigned int bkdr_hash(const char * str) { unsigned int seed = 131; unsigned int hash = 0; while (*str) { hash = hash * seed + (*str++); } return hash % 65536; } //读取历史记录数据 Hash 校验 void read_hs_data() { size_t i; size_t len; unsigned char buf[100]; unsigned int hash_num; char str[50]; char * pstr = NULL; FILE * fp = fopen("tetris.txt", "rb"); if (NULL != fp) { len = fread(buf, 1, 100, fp); for (i = 0; i < len; ++i) { buf[i] ^= 0xC6; } buf[i] = ''; hash_num = buf[0] + buf[1] * 256; if (bkdr_hash(&buf[2]) == hash_num) { sscanf(&buf[2], "%s", str); sscanf(str, "%d", &hs_score); pstr = &buf[strlen(str) + 3]; for (i = 0; i < 8 && pstr[i]; ++i) { hs_name[i] = pstr[i]; } hs_name[i] = ''; } else { strcpy(hs_name, "暂无"); hs_score = 0; } fclose(fp); } else { strcpy(hs_name, "暂无"); hs_score = 0; } } //写入历史记录数据 Hash 加密 void write_hs_data(char name[], int score) { size_t len; size_t i; unsigned char buf[100]; FILE * fp = fopen("tetris.txt", "wb"); unsigned int hash_num; if (NULL != fp) { sprintf(&buf[2], "%dn", score); len = strlen(&buf[2]); for (i = 0; i < 8 && name[i]; ++i) { (&buf[2])[len + i] = name[i]; } (&buf[2])[len + i] = ''; len = strlen(&buf[2]); hash_num = bkdr_hash(&buf[2]); buf[0] = hash_num % 256; buf[1] = hash_num / 256; for (i = 0; i < len + 2; ++i) { fputc(buf[i] ^ 0xC6, fp); } fclose(fp); } else { error(); } } //游戏结束相关处理 void game_over() { char name[100]; system("cls"); gotoxy(9, 2); printf("你的得分为: %d", now_score); if (now_score > hs_score) {

gotoxy(9, 4);

printf(“恭喜你刷新了记录”);

gotoxy(9, 6);

printf(“请输入你的大名: “);

scanf(“%s”, name);

write_hs_data(name, now_score);

gotoxy(9, 8);

printf(“记录成功刷新”);

}

else {

gotoxy(9, 4);

printf(“很遗憾你没有刷新记录”);

}

_getch();

}

//游戏主程序

int game_main() {

itetris next_its;

ntetris nts;

unsigned int otime;

unsigned int ntime;

int c;

int pause = 0;

int aflag;

//清空控制台屏幕

system(“cls”);

now_score = 0;

//初始化地图数据

init_map();

//获得第一个随机俄罗斯方块

get_rnd_tetris(&next_its);

//绘制界面框架

draw_frame();

//读取历史记录

read_hs_data();

//更新历史玩家数据

update_hs_data();

//更新当前玩家得分

update_now_score();

gotoxy(28, 19);

printf(“P键暂停”);

while (1) {

bk2:

//放置下一个俄罗斯方块进入当前场景

put_tetris(&nts, &next_its);

//获得下一个随机俄罗斯方块

get_rnd_tetris(&next_its);

//绘制下一个俄罗斯方块

draw_next_tetris(&next_its);

//Tick 定时器开始工作

otime = GetTickCount();

while (1) {

if (0 == pause) {

//Tick 定时器节奏触发俄罗斯方块自然下落

if ((ntime = GetTickCount()) – otime > rhythm) {

otime = ntime;

general_fall(&nts);

aflag = after_fall(&nts);

if (WRITE_WIN == aflag) {

break;

}

else if (GAME_OVER == aflag) {

game_over();

return 0;

}

}

}

//按键检测

if (_kbhit()) {

c = _getch();

if (0 == pause) {

switch (c) {

//旋转俄罗斯方块

case ‘w’:{

 hand_turn(&nts, TURN_RIGHT);

 //如果合法则应用操作,下同

 if (op_isilegal(&nts) == 0) {

 use_op(&nts);

 }

}break;

//手动下落

case ‘s’:{

 general_fall(&nts);

 aflag = after_fall(&nts);

 if (WRITE_WIN == aflag) {

 goto bk2;

 }

 else if (GAME_OVER == aflag) {

 game_over();

 return 0;

 }

}break;

//左移俄罗斯方块

case ‘a’:{

 hand_left(&nts);

 if (op_isilegal(&nts) == 0) {

 use_op(&nts);

 }

}break;

//右移俄罗斯方块

case ‘d’:{

 hand_right(&nts);

 if (op_isilegal(&nts) == 0) {

 use_op(&nts);

 }

}break;

}

}

//游戏暂停功能

if (‘p’ == c) {

if (pause) {

pause = 0;

gotoxy(28, 19);

printf(“P键暂停”);

}

else {

pause = 1;

gotoxy(28, 19);

printf(“P键继续”);

}

}

}

}

}

return 0;

}

//主函数

int main() {

//清空控制台屏幕

system(“cls”);

//在控制台标题上显示游戏标题

system(“title tetris”);

//设定控制台窗口的宽高

system(“mode con cols=38 lines=22”);

//产生随机数种子

srand((unsigned int)time(NULL));

while (1) {

//进入游戏主程序

game_main();

}

return 0;

}

//以下 const pattern 类型为七种俄罗斯方块各自旋转图案

const pattern pattern_list_line[] = {

{

1, 4,

{

1, 0, 0, 0,

1, 0, 0, 0,

1, 0, 0, 0,

1, 0, 0, 0

}

},

{

4, 1,

{

1, 1, 1, 1,

0, 0, 0, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_l[] = {

{

2, 3,

{

1, 0, 0, 0,

1, 0, 0, 0,

1, 1, 0, 0,

0, 0, 0, 0

}

},

{

3, 2,

{

1, 1, 1, 0,

1, 0, 0, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

1, 1, 0, 0,

0, 1, 0, 0,

0, 1, 0, 0,

0, 0, 0, 0

}

},

{

3, 2,

{

0, 0, 1, 0,

1, 1, 1, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_rl[] = {

{

2, 3,

{

0, 1, 0, 0,

0, 1, 0, 0,

1, 1, 0, 0,

0, 0, 0, 0

}

},

{

3, 2,

{

1, 0, 0, 0,

1, 1, 1, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

1, 1, 0, 0,

1, 0, 0, 0,

1, 0, 0, 0,

0, 0, 0, 0

}

},

{

3, 2,

{

1, 1, 1, 0,

0, 0, 1, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_s[] = {

{

3, 2,

{

0, 1, 1, 0,

1, 1, 0, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

1, 0, 0, 0,

1, 1, 0, 0,

0, 1, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_z[] = {

{

3, 2,

{

1, 1, 0, 0,

0, 1, 1, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

0, 1, 0, 0,

1, 1, 0, 0,

1, 0, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_box[] = {

{

2, 2,

{

1, 1, 0, 0,

1, 1, 0, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

}

};

const pattern pattern_list_t[] = {

{

3, 2,

{

1, 1, 1, 0,

0, 1, 0, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

0, 1, 0, 0,

1, 1, 0, 0,

0, 1, 0, 0,

0, 0, 0, 0

}

},

{

3, 2,

{

0, 1, 0, 0,

1, 1, 1, 0,

0, 0, 0, 0,

0, 0, 0, 0

}

},

{

2, 3,

{

1, 0, 0, 0,

1, 1, 0, 0,

1, 0, 0, 0,

0, 0, 0, 0

}

}

};

//俄罗斯方块数据表

const tetris tetris_list[] = {

{

sizeof(pattern_list_line) / sizeof(pattern_list_line[0]),

pattern_list_line

},

{

sizeof(pattern_list_l) / sizeof(pattern_list_l[0]),

pattern_list_l

},

{

sizeof(pattern_list_rl) / sizeof(pattern_list_rl[0]),

pattern_list_rl

},

{

sizeof(pattern_list_s) / sizeof(pattern_list_s[0]),

pattern_list_s

},

{

sizeof(pattern_list_z) / sizeof(pattern_list_z[0]),

pattern_list_z

},

{

sizeof(pattern_list_box) / sizeof(pattern_list_box[0]),

pattern_list_box

},

{

sizeof(pattern_list_t) / sizeof(pattern_list_t[0]),

pattern_list_t

}

};

//俄罗斯方块数据表长度

const size_t TETRIS_LIST_LEN = (sizeof(tetris_list) / sizeof(tetris_list[0]));