本文俄罗斯方块实现主要思路:把方块看做一个4x4的小数组在规定界面(看做一个大数组)内移动,用简单的信号控制并发,做到方块边下落边响应键盘的操作。
本代码运用ANSI控制码来实现方块和界面的上色,提供从文件读取并更新历史最高分,暂停游戏,根据等级加快方块下落速度等功能,具体效果如下图所示:
本文为博主第一次发表文章,不懂的地方可以留言,都会回复,喜欢的可以点赞收藏,代码不足的地方欢迎大家提出建议和改进方法 ,大家共同进步,以下为代码实现:
函数声明:
#ifndef GAME_H__
#define GAME_H__
#define ROW 19
#define LINE 14
#define DIA 4
#define BASE 7
#define SPINSTA 4
//方块函数,base为基本类型,spinsta为旋转状态
struct Blocks
{
int space[DIA][DIA];
}blocks[BASE][SPINSTA];
void Game_start(); //游戏开始函数
void Interface(); //游戏界面
int Init_blocks(); //方块初始化
void Next_blocks(); //下一个方块的打印
int Read_record(); //读取历史记录
void Grade_print(int); //分数打印
void Select_colour(int); //选择颜色
void Ols_load(); //棋盘读入数据
int Set_time(); //设置下落速度
void Game_pause(); //游戏暂停
int Run(); //游戏运行
int Judge_over(); //判断游戏结束
int Judge_move(int); //判断方块是否能移动
int Judge_chage(); //判断方块能否旋转
void Judge_dis(); //判断能否消行
void Exec_command(int); //执行命令
void Dis_last(int); //销毁上一位置方块
void Exec_dis(int); //执行消行
#endif
主要代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <time.h>
#include <sys/time.h>
#include "game.h"
#include "kbhit.h" //Linux需要自己实现,windows下自带
#define BUFFSIZE 1024
//历史最高记录读取文件名
#define FILENAME "/home/ll/Gitee/c-code/项目/俄罗斯方块/log"
int Els[ROW][LINE] = {0}; //棋盘数组
int row_ = 0; //方块在棋盘中的行
int line_ = 0; //方块在棋盘中的列
int colour_[ROW][LINE] = {0}; //每个方块对应的颜色
int colour = 0; //当前方块的颜色
int colour_next = 0; //下一个方块的颜色
int base = 0; //当前方块类型
int base_next = 0; //下一个方块类型
int spinsta = 0; //方块的4种变化
int grade = 0; //成绩
int level = 1; //等级
int stop_ = 1; //当前方块能否继续下落
static void trave_() //根据棋盘数值打印相对应的颜色
{
int i, j;
int colour_cur = 0;
printf("%c[6;12H",'\033');
for (i = 2; i < ROW-2; i++)
{
for (j = 2; j < LINE-2; j++)
{
if (Els[i][j] == 1) //表示正在下落的方块
{
Select_colour(colour);
printf("[]\033[0m");
}
else if(Els[i][j] == 2) //已经下落完成的方块
{
Select_colour(colour_[i][j]);
printf("[]\033[0m");
}
else
printf(" ");
}
printf("\n\033[11C");
}
}
void Interface() //利用ANSI控制码打印界面
{
int i, j, k;
printf("\033[2J\033[5;10H\033[45m--====================-------------------\033[0m\n");
printf("%c[6;10H",'\033');
for (i = 2; i < ROW-2; i++)
{
printf("\033[45m||\033[0m");
for (j = 2; j < LINE-2; j++)
{
printf(" ");
}
printf("\033[45m||\t\t||\033[0m\n\033[9C");
}
printf("\033[21;10H\033[45m--====================-------------------\033[0m\n");
printf("\033[12;34H\033[45m---------------\033[0m\n");
printf("\033[16;34H\033[45m---------------\033[0m\n");
printf("\033[13;36H\033[32mHigh Record\033[0m");
printf("\033[14;40H\033[31m%d\033[0m",Read_record(0));
printf("\033[18;38H\033[32mScore\033[0m");
printf("\033[19;40H\033[31m%d\033[0m",grade);
//初始化棋盘边界
for (i = 0,k = ROW-2; i < 2 && k < ROW; i++,k++)
{
for (j = 0; j < LINE; j++)
{
Els[i][j] = 2;
Els[k][j] = 2;
}
}
for (i = 2; i < ROW-2; i++)
{
for (j = 0,k = LINE-2; j < 2 && k < LINE; j++,k++)
{
Els[i][j] = 2;
Els[i][k] = 2;
}
}
}
int Init_blocks() //初始化方块,共28种
{
int i, j;
int temp[DIA][DIA] = {0};
int base_, spinsta_;
for (i = 0; i < 2; i++)
{
//田
blocks[0][0].space[1][i+1] = 1;
blocks[0][0].space[2][i+1] = 1;
//Z
blocks[1][0].space[1][i] = 1;
blocks[1][0].space[2][i+1] = 1;
//反Z
blocks[2][0].space[1][i+1] = 1;
blocks[2][0].space[2][i] = 1;
}
for (i = 1;i < 4; i++)
{
//7
blocks[3][0].space[1][0] = 1;
blocks[3][0].space[i][1] = 1;
//反7
blocks[4][0].space[1][2] = 1;
blocks[4][0].space[i][1] = 1;
}
for (i = 0; i < 4; i++)
{
//|
blocks[5][0].space[i][1] = 1;
}
for (i = 0; i < 3; i++)
{
//土
blocks[6][0].space[1][1] = 1;
blocks[6][0].space[2][i] = 1;
}
//根据7种基本形态衍生的其他方块
for (base_ = 0; base_ < 7; base_++)
{
for (spinsta_ = 0; spinsta_ < 3; spinsta_++)
{
for (i = 0; i < DIA; i++)
{
for (j = 0;j < DIA; j++)
{
temp[i][j] = blocks[base_][spinsta_].space[i][j];
}
}
for (i = 0; i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
blocks[base_][spinsta_+1].space[i][j] = temp[4-1-j][i];
}
}
}
}
return 0;
}
void Next_blocks() //下一个方块的打印
{
int i, j;
printf("\033[7;38H");
for (i = 0;i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
if (blocks[base_next][0].space[i][j] == 1)
{
Select_colour(colour_next);
printf("[]\033[0m");
continue;
}
printf(" ");
}
printf("\n\033[37C");
}
}
int Read_record() //从文件中读取最高记录
{
FILE *fps = NULL;
FILE *fpd = NULL;
char buff[BUFFSIZE] = {0};
int n = 0;
fps = fopen(FILENAME,"r+");
if (fps == NULL)
{
perror("fopen()");
exit(1);
}
fgets(buff,BUFFSIZE,fps);
n = atoi(buff);
if (grade > n)
{
fpd = fopen(FILENAME,"w+");
if (fpd == NULL)
{
perror("fopen()");
exit(1);
}
fprintf(fpd,"%d",grade);
fclose(fpd);
}
fclose(fps);
return n;
}
void Grade_print(int grade) //打印成绩
{
printf("\033[18;38H\033[32mScore\033[0m");
printf("\033[19;40H\033[31m%d\033[0m",grade);
if (grade > 0 && grade % 100 == 0) //每一100分等级加一
{
level++;
}
printf("\033[14;40H\033[31m%d\033[0m",Read_record());
}
void Select_colour(int select) //颜色选择函数
{
switch(select)
{
case 0:
{
printf("\033[46m");
break;
}
case 1:
{
printf("\033[44m");
break;
}
case 2:
{
printf("\033[41m");
break;
}
case 3:
{
printf("\033[42m");
break;
}
case 4:
{
printf("\033[43m");
break;
}
}
}
void Ols_load() //方块赋值给棋盘
{
int i, j;
for (i = 0; i < DIA; i++)
{
for (j = 0;j < DIA; j++)
{
if (Els[i+row_][line_+j] != 2) //防止覆盖已经下落完成的方块
Els[i+row_][line_+j] = blocks[base][spinsta].space[i][j];
}
}
trave_();
}
void Game_start() //关闭终端回显和\n结束getchar()的功能能
{
struct termios new,old;
tcgetattr(0,&old);
tcgetattr(0,&new);
new.c_lflag = new.c_lflag & ~(ICANON | ECHO);
new.c_cc[VMIN] = 1;
new.c_cc[VTIME] = 0;
tcsetattr(0,TCSANOW,&new);
Interface();
Init_blocks();
printf("\033[?25l"); //隐藏光标
srand((unsigned)time(NULL));
base = rand() % 7;
colour = rand() % 5;
while(Run());
tcsetattr(0,TCSANOW,&old); //还原终端原始设置
printf("\033[?25h\033[2J");
}
static void alrm_handler(int s) //信号控制函数,内为下落函数
{
if (Judge_move(3) == 1)
Exec_command(3);
else
{
Judge_dis();
stop_ = 0;
}
}
int Set_time() //根据等级设置下落速度并用信号控制并发
{
char ch;
signal(SIGALRM,alrm_handler);
if (level == 1)
{
struct itimerval new = {
{0,700000},{0,700000}};
setitimer(ITIMER_REAL,&new,NULL);
}
if (level == 2)
{
struct itimerval new = {
{0,600000},{0,600000}};
setitimer(ITIMER_REAL,&new,NULL);
}
if (level >= 3)
{
struct itimerval new = {
{0,400000},{0,400000}};
setitimer(ITIMER_REAL,&new,NULL);
}
while(stop_)
{
if (kbhit() == 1)
{
ch = getchar();
switch(ch)
{
//退出
case 'q':
case 'Q':
return 0;
case 'p':
Game_pause();
break;
case 't': //变形
if (Judge_chage() == 1)
Exec_command(0);
break;
case 'a': //左移
if (Judge_move(1) == 1)
Exec_command(1);
break;
case 'd': //右移
if (Judge_move(2) == 1)
Exec_command(2);
break;
case 's': //加速下落
if (Judge_move(3) == 1)
{
Exec_command(3);
}
else
{
Judge_dis();
return 1;
}
}
}
}
return 1;
}
void Game_pause() //游戏暂停
{
char ch;
struct itimerval pause_ = {0}, old;
setitimer(ITIMER_REAL,&pause_,&old);
printf("\033[2J\033[10;20H\033[31mGame is pause...\033[0m");
fflush(stdout);
while(1)
{
ch = getchar();
if (ch == 'p')
break;
}
printf("\033[2J\033[10;20H\033[32mGame continue\033[0m");
fflush(stdout);
sleep(1);
Interface();
Next_blocks();
setitimer(ITIMER_REAL,&old,NULL);
}
int Run() //运行函数
{
//初始化方块下落位置
int ret = 0;
spinsta = 0;
row_ = 2;
line_ = 6;
stop_ = 1;
//给下一个方块确定类型和颜色
srand((unsigned)time(NULL));
base_next = rand() % 7;
colour_next = rand() % 5;
Next_blocks();
//判断游戏是否结束
if (Judge_over() == 0)
{
return 0;
}
Ols_load();
//根据等级匹配下落速度
ret = Set_time();
base = base_next;
colour = colour_next;
return ret;
}
//游戏结束判断
int Judge_over()
{
int i, j;
//最上一格存在方块
for (j = 2; j < LINE-2; j++)
{
if (Els[2][j] == 2)
{
return 0;
}
}
//下一个方块不能进入棋盘
for (i = 0; i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
if (blocks[base][spinsta].space[i][j] == 1)
{
if (Els[row_+i][line_+j] == 2)
{
return 0;
}
}
}
}
return 1;
}
//判断方块能否移动
int Judge_move(int move)
{
int i, j;
int f_row,f_line;
switch(move)
{
case 1: //left
f_row = 0;
f_line = -1;
break;
case 2: //right
f_row = 0;
f_line = 1;
break;
case 3: //down
f_row = 1;
f_line = 0;
break;
}
for (i = 0; i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
if (Els[row_+i][line_+j] == 1)
{
if (Els[row_+i+f_row][line_+j+f_line] == 2)
{
return 0;
}
}
}
}
return 1;
}
//判断方块是否能旋转
int Judge_chage()
{
int i, j;
int count = 0;
if (spinsta+1 > 3)
spinsta = -1;
for (i = 0; i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
if (blocks[base][spinsta+1].space[i][j] == 1)
{
if (Els[row_+i][line_+j] != 2)
{
count++;
if (count == 4)
{
return 1;
}
}
}
}
}
return 0;
}
void Judge_dis()
{
int i, j;
int count = 0;
int temp = 0;
int flag = 0;
int n = 0;
int score = 0;
//下落完成的方块记录颜色并赋值为2
for (i = 2; i < ROW-2; i++)
{
for (j = 2; j < LINE-2; j++)
{
if (Els[i][j] == 1)
{
Els[i][j] = 2;
colour_[i][j] = colour;
}
}
}
//判断是否能消行
for (i = ROW-3; i >= 2; i--)
{
count = 0;
flag = 0;
for (j = 2; j < LINE-2; j++)
{
if (Els[i][j] == 2)
{
count++;
}
}
if (count == 10)
{
temp = i;
Exec_dis(temp);
n++;
grade += 10;
i = temp+1; //i从原来位置重新判断
flag = 1;
}
if (flag)
score += n*10;
}
if (n >= 2)
grade += score;
Grade_print(grade);
trave_();
}
//执行命令
void Exec_command(int command)
{
switch(command)
{
case 0: //旋转
spinsta++;
Dis_last(command);
break;
case 1: //左移
line_--;
Dis_last(command);
break;
case 2: //右移
line_++;
Dis_last(command);
break;
case 3: //下落
row_++;
Dis_last(command);
}
}
//执行消行
void Exec_dis(int row)
{
int i, j;
for (j = 2;j < LINE-2; j++)
{
Els[row][j] = 0;
}
for (i = row; i > 2; i--)
{
for (j = 2; j < LINE-2; j++)
{
Els[i][j] = 0;
Els[i][j] = Els[i-1][j];
}
}
}
void Dis_last(int flag) //消除上个状态
{
int i, j;
int last_row, last_line;
//变形
if (flag == 0)
{
last_row = 0;
last_line = 0;
}
//左移
else if(flag == 1)
{
last_row = 0;
last_line = 1;
}
//右移
else if(flag == 2)
{
last_row = 0;
last_line = -1;
}
//下移
else
{
last_row = -1;
last_line = 0;
}
for (i = 0; i < DIA; i++)
{
for (j = 0; j < DIA; j++)
{
if (Els[i+row_+last_row][line_+j+last_line] != 2)
Els[i+row_+last_row][line_+j+last_line] = 0;
}
}
Ols_load();
}
由于linux下没有kbhit函数(判断键盘是否被敲击)的实现,以下为函数实现:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
int kbhit(void)
{
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF)
{
ungetc(ch, stdin);
return 1;
}
return 0;
}