思路
我设计时,思路历程:
- 方块如何旋转
- 光标定位
- 方块的移动
- 方块非法动作取消
- 方块生命消亡检查
- 消除一行
方块旋转
一开始想到的笨方法,就是将方块整体融入一个九宫格矩阵中,旋转即使行列进行数据调换,这就需要创建一个3*3的数组以及一个定位坐标作为成员变量的结构体数组,由于太笨,放弃。
观察到旋转变换与角度有关,于是将方块某一点,视为极点,进行坐标运算,旋转即是θ-π/2,计算结果很简单:
逆时针:x=y ,y=-x;
顺时针:x=-y,y=x;
然后再转换为控制台上坐标系上的坐标,即加上极点对应的横纵坐标即可。
==注意:算的时候,要对应上数轴的正方向==
光标定位
本人也没过多理解,如何定位,直接拿来用了。大致就是获取控制台的标准输出句柄,然后传入一个坐标结构体参数给它。 代码片
.
//将光标设置跳转到具体坐标
void gotoxy(int x, int y)
{
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
方块的移动
前面的实现了,这个方块移动就不难了,就是消除前一个足迹,然后打印新的。然后要注意的是一个方块符"■",在很坐标上占两个字宽,所以坐标的偏移上要*2
/*
方块动作执行
参数: flag int 类型
ANTICLOCKWISE :逆时针旋转
CLOCKWISE :顺时针旋转
MOVE_L :左移
MOVE_R : 右移
MOVE_U:上移
MOVE_D : 下移
*/
void action(int flag) {
//逆时针旋转
if (flag == ANTICLOCKWISE) {
for (int i = 0; i < 4; i++) {
if (i == 2)continue;//轴心,可有可无
int x = tetris[2].x + tetris[i].y - tetris[2].y;
int y = tetris[2].x + tetris[2].y - tetris[i].x;
tetris[i].x = x;
tetris[i].y = y;
}
}
//顺时针旋转
else if (flag == CLOCKWISE) {
for (int i = 0; i < 4; i++) {
if (i == 2)continue;//轴心,可有可无
int x = tetris[2].x + tetris[2].y - tetris[i].y;
int y = tetris[i].x + tetris[2].y - tetris[2].x;
tetris[i].x = x;
tetris[i].y = y;
}
}
//向左移动
else if (flag == MOVE_L) {
for (int i = 0; i < 4; i++) {
tetris[i].x += MOVE_L;
}
}
//向右移动
else if (flag == MOVE_R) {
for (int i = 0; i < 4; i++) {
tetris[i].x += MOVE_R;
}
}
else if (flag == MOVE_U) {
//向上移动
for (int i = 0; i < 4; i++) {
tetris[i].y += -1;
}
}
else if (flag == MOVE_D) {
//向下移动
for (int i = 0; i < 4; i++) {
tetris[i].y += 1;
}
}
}
方块非法动作取消
方块的非法动作行为如:超出活动边界以及碰触到“前人遗骸”。实现是先让动作执行,再检测方块是否越界,是的话,逆做动作还原。
//行为异常进行反转、取消前动作
if (true ==sign) {
switch (temp) {
case UP:
action(-1 * CLOCKWISE);
break;
case LEFT:
action(-1 * MOVE_L);
break;
case RIGHT:
action(-1 * MOVE_R);
break;
default:
action(-1 * MOVE_D);
break;
}
}
有点不想写了
方块生命消亡检查
生命检查,就是通过先判断上一个方块动作是否为下移,在这个前提下,判断方块坐标是否触及地图的非活动区域(即已经有方块存在了)
//死亡行为检查
if (map[tetris[i].y][tetris[i].x] == 1) {
sign = true;
if (temp != UP && temp != LEFT && temp != LEFT && temp != RIGHT)
status = DEAD;
break;
}
消除一行
消除一行需要检查是否存在满足消除条件,需要对全地图遍历,同时消除后,还需对地图更正,填充消除的一行,集体下移。
/*
检查是否可以消行
*/
void updateMap()
{
int record[HEIGTH] = {0}; //记录满行 行下标
int top = 0,count; // top 记录栈顶,最小为0,0代表没有记录 ** count 计数
for (int i = HEIGTH - 2; i > 0; i--) {
count = 0;
for (int j = 1; j < WIDTH - 1; j++) {
if (map[i][j] == 1) {
count++;
}
else break;
}
if (count == WIDTH - 2) record[top++] = i;
}
score = score + top * 10; //计算分值
//top大于零,则取出栈顶元素 ---被消除的行下标
while (top > 0) {
for (int i = record[top-1]; i > 1; i--) {
for (int j = 0; j < WIDTH - 1; j++) {
map[i][j] = map[i - 1][j];
}
}
system("cls"); //完全清屏
top--;
}
}
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>//windows编程头文件
#include <time.h>
#include <conio.h>//控制台输入输出头文件
#define CLOCKWISE 2 //顺时针旋转
#define ANTICLOCKWISE -2 //逆时针旋转
#define MOVE_L -1 // 左移
#define MOVE_R 1 // 右移
#define MOVE_U -3 //上移
#define MOVE_D 3 //下移
#define DEAD 0 //方块死亡
#define LIVE 1 //方块存活
#define UP 72 //向上键 ** 旋转方块
#define DOWN 80 //快速到底
#define LEFT 75 //向左移动
#define RIGHT 77 //向右移动
#define WIDTH 20 //地图长度
#define HEIGTH 30 //地图宽度
#define NORMAL_SPEED 200 //正常速度
#define GAME_OVER 0 //游戏结束
#define PLAY_GAME 1 //游戏开始
void action(int flag);
void gotoxy(int x, int y);
void draw_block();
void get_direction();
void play();
void update(int dir);
void draw_map();
void init();
void createBlock();
bool check();
void clean();
void updateMap();
struct
{
int x;
int y;
}tetris[4] = { { 1,WIDTH/2 },{ 1,WIDTH / 2 },{ 1,WIDTH / 2 },{1,WIDTH / 2 } };
unsigned char direction=0;
int map[HEIGTH][WIDTH]{0};
int speed= NORMAL_SPEED;
bool status = DEAD;
int score = 0;
bool gameStatus = PLAY_GAME;
int main() {
//获取标准输入句柄
HANDLE fd = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cinfo;
cinfo.bVisible = 0; //将光标设置为不可见
cinfo.dwSize = 1;
SetConsoleCursorInfo(fd, &cinfo);
//初始化
init();
while (1)
{
play();
if (GAME_OVER == gameStatus) {
system("cls");
gotoxy(WIDTH / 2, HEIGTH / 2);
printf("游戏结束,您的得分:%d", score);
gotoxy(WIDTH / 2, HEIGTH / 2+1);
printf("按回车结束");
getchar();
break;
}
}
return 0;
}
/**
*控制台按键所代表的数字
*“↑”:72
*“↓”:80
*“←”:75
*“→”:77
*/
//将光标设置跳转到具体坐标
void gotoxy(int x, int y)
{
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
/*
绘制方块
*/
void draw_block()
{
for (int i = 0; i < 4; i++) {
gotoxy(tetris[i].x*2, tetris[i].y);
printf("■");
}
gotoxy(0, 0); //将光标移到远处,防止输入其他字符影响
}
/*
获取输入的方向
上:旋转
下:快速到底
左:左移
右:右移
*/
void get_direction()
{
if (_kbhit())//如果用户按下了键盘中的某个键
{
//清空缓冲区
rewind(stdin);
//getch()读取方向键的时候,要读取两次,第一次调用返回0或者224,第二次调用返回的才是实际值
direction = _getch();//第一次调用返回的不是实际值
if (direction != 0 || direction != 224 || direction ==EOF) return; //非方向键按下会产生干扰
direction = _getch();//第二次调用返回实际值
rewind(stdin);
}
}
/*
开始游戏
*/
void play()
{
clean(); //清除方块足迹
get_direction(); //获取用户选择
if (!check()) { //检查方块状态行为
createBlock();
status = LIVE;
draw_map();
}
draw_map();
draw_block();
Sleep(speed);
}
/*
更新数据
*/
void update(int dir)
{
switch (dir) {
case UP:
action(CLOCKWISE);
break;
case LEFT:
action(MOVE_L);
break;
case RIGHT:
action(MOVE_R);
break;
case DOWN:
speed = 0;
default: // 默认下移
action(MOVE_D);
break;
}
direction = 0;
}
/*
绘制地图
*/
void draw_map()
{
for (int i = 0; i < HEIGTH; i++) {
for (int j = 0; j < WIDTH; j++) {
if (map[i][j] == 1) {
gotoxy(j*2, i);
printf("■");
}
}
}
}
/*
初始化
设置边界
创建方块、地图数据模型 设置状态:live
*/
void init()
{
for (int j = 0; j < WIDTH; j++) {
map[0][j] = 1;
map[HEIGTH - 1][j] = 1;
}
for (int i = 0; i < HEIGTH; i++) {
map[i][0] = 1;
map[i][WIDTH - 1] = 1;
}
createBlock();
status = LIVE;
}
/*
创建方块
随机数生成:0-3 之间的数
赋值四种类型之一的数据模型
方向初始化为:0
速度初始化为:正常速度 NORMAL_SPEED 200
*/
void createBlock()
{
srand((unsigned int)time(NULL));
int type = rand() % 4;
switch (type)
{
case 0:
tetris[0] = { WIDTH / 2 - 1 ,1};
tetris[1] = { WIDTH / 2 - 1 ,2};
tetris[2] = { WIDTH / 2 ,2};
tetris[3] = { WIDTH / 2+1 ,2 };
break;
case 1:
tetris[0] = { WIDTH / 2 ,1 };
tetris[1] = { WIDTH / 2 +1 ,1 };
tetris[2] = { WIDTH / 2 ,2 };
tetris[3] = { WIDTH / 2 +1 ,2 };
break;
case 2:
tetris[0] = { WIDTH / 2 - 1 , 2 };
tetris[1] = { WIDTH / 2 , 1 };
tetris[2] = { WIDTH / 2 , 2 };
tetris[3] = { WIDTH / 2 +1 , 2 };
break;
case 3:
tetris[0] = { WIDTH / 2 - 2 , 2 };
tetris[1] = { WIDTH / 2 - 1 , 2 };
tetris[2] = { WIDTH / 2 , 2 };
tetris[3] = { WIDTH / 2 + 1 , 2 };
break;
default:
break;
}
direction = 0;
speed = NORMAL_SPEED;
}
/*
检查方块行为、检查游戏状态
参数:无
返回: true LIVE 方块存活;
false DEAD 方块死亡;
*/
bool check()
{
int temp = direction;
update(temp);
bool sign=false;//行为取消标识
for (int i = 0; i < 4; i++) {
//碰壁行为检查
if (tetris[i].y == 0 || tetris[i].x == 0 || tetris[i].x ==WIDTH-1) {
sign = true;
break;
}
//死亡行为检查
if (map[tetris[i].y][tetris[i].x] == 1) {
sign = true;
if (temp != UP && temp != LEFT && temp != RIGHT)
status = DEAD;
break;
}
}
//行为异常进行反转、取消前动作
if (true ==sign) {
switch (temp) {
case UP:
action(-1 * CLOCKWISE);
break;
case LEFT:
action(-1 * MOVE_L);
break;
case RIGHT:
action(-1 * MOVE_R);
break;
default:
action(-1 * MOVE_D);
break;
}
}
//方块死亡,将数据加入地图数据模型
if (DEAD == status) {
for (int i = 0; i < 4; i++) {
map[tetris[i].y][tetris[i].x] = 1;
}
}
updateMap(); //更新地图,检查是否存在消行
//检查是否游戏结束
for (int i = 1; i < WIDTH - 1; i++) {
if (map[1][i] == 1) {
gameStatus = GAME_OVER;
break;
}
}
return status;
}
/*
消除方块足迹
*/
void clean()
{
for (int i = 0; i < 4; i++) {
gotoxy(tetris[i].x * 2, tetris[i].y);
printf(" ");
}
}
/*
检查是否可以消行
*/
void updateMap()
{
int record[HEIGTH] = {0}; //记录满行 行下标
int top = 0,count; // top 记录栈顶,最小为0,0代表没有记录 ** count 计数
for (int i = HEIGTH - 2; i > 0; i--) {
count = 0;
for (int j = 1; j < WIDTH - 1; j++) {
if (map[i][j] == 1) {
count++;
}
else break;
}
if (count == WIDTH - 2) record[top++] = i;
}
score = score + top * 10; //计算分值
//top大于零,则取出栈顶元素 ---被消除的行下标
while (top > 0) {
for (int i = record[top-1]; i > 1; i--) {
for (int j = 0; j < WIDTH - 1; j++) {
map[i][j] = map[i - 1][j];
}
}
system("cls"); //完全清屏
top--;
}
}
/*
方块动作执行
参数: flag int 类型
ANTICLOCKWISE :逆时针旋转
CLOCKWISE :顺时针旋转
MOVE_L :左移
MOVE_R : 右移
MOVE_U:上移
MOVE_D : 下移
*/
void action(int flag) {
//逆时针旋转
if (flag == ANTICLOCKWISE) {
for (int i = 0; i < 4; i++) {
if (i == 2)continue;//轴心,可有可无
int x = tetris[2].x + tetris[i].y - tetris[2].y;
int y = tetris[2].x + tetris[2].y - tetris[i].x;
tetris[i].x = x;
tetris[i].y = y;
}
}
//顺时针旋转
else if (flag == CLOCKWISE) {
for (int i = 0; i < 4; i++) {
if (i == 2)continue;//轴心,可有可无
int x = tetris[2].x + tetris[2].y - tetris[i].y;
int y = tetris[i].x + tetris[2].y - tetris[2].x;
tetris[i].x = x;
tetris[i].y = y;
}
}
//向左移动
else if (flag == MOVE_L) {
for (int i = 0; i < 4; i++) {
tetris[i].x += MOVE_L;
}
}
//向右移动
else if (flag == MOVE_R) {
for (int i = 0; i < 4; i++) {
tetris[i].x += MOVE_R;
}
}
else if (flag == MOVE_U) {
//向上移动
for (int i = 0; i < 4; i++) {
tetris[i].y += -1;
}
}
else if (flag == MOVE_D) {
//向下移动
for (int i = 0; i < 4; i++) {
tetris[i].y += 1;
}
}
}