C Windows控制台字符版本俄羅斯方塊
//一個可以工作在Windows控制台字符界面下的俄羅斯方塊
//工作在非圖形模式,無需其他庫依賴,單個C文件代碼即可運行
//支持最高紀錄,並且對於紀錄進行了加密
//By wrule 2015年12月14日20:53:57
//控制方式 WSAD 鍵對應旋轉,下,左,右
//需要注意的是在進行游戲之前需要按下 Ctrl + 空格 取消輸入法,否則無法正確操作
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
#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] = '\0';
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] = '\0';
}
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], "%d\n\0", score);
len = strlen(&buf[2]);
for (i = 0; i < 8 && name[i]; ++i) {
(&buf[2])[len + i] = name[i];
}
(&buf[2])[len + i] = '\0';
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]));