C语言编写俄罗斯方块出现问题

求博主们解答,不知道哪里错了,可以直接给更改方式,因为还不是很懂C语言
1.用C语言的写俄罗斯方块(用结构体数组来定位坐标)(读取文件中方块的19种类别)(代码问题出在void FockChange()和oid Goal())
[](链接:https://pan.baidu.com/s/1d9nivc5HMKUkpMwnz54IMw?pwd=1111
提取码:1111)源码和文件都在
2.遇到了一些问题:
2.1方块间的转换没达到预期效果,出现了偏差

void FockChange()
{
    //0-3,L型
    if (graph_num == 0)
    {
        if (fock[0].j < 11 && fock[1].j < 11 && fock[2].j < 11 && fock[3].j < 11)//一重条件:不能越界
            if (latt[fock[2].i][fock[2].j + 1] != 1 && latt[fock[2].i][fock[2].j + 2] != 1)//二重条件:不能混乱
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;//改变方块位置
                fock[1].j++;
                fock[2].i--;    fock[2].j += 2;
                fock[3].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 1)
    {
        if (latt[fock[1].i - 1][fock[1].j] != 1 && latt[fock[1].i - 1][fock[1].j-1] != 1
            && latt[fock[1].i + 1][fock[1].j+1] != 1)//二重条件:不能混乱
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;
            fock[1].i--;
            fock[2].j--;
            fock[3].j++;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num++;
        }
    }
    else if (graph_num == 2)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[4].i][fock[4].j - 2] != 1 && latt[fock[4].i][fock[4].j - 1] != 1)
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;    fock[0].j++;
                fock[1].i += 2;    fock[1].j -= 2;
                fock[2].i++;        fock[2].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 3)
    {
        if (latt[fock[3].i - 2][fock[3].j] != 1 && latt[fock[3].i - 1][fock[3].j] != 1)
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;
            fock[0].j--;
            fock[1].i--;
            fock[1].j++;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num -= 3;
        }
    }
    //4有问题4-7,J型
    else if (graph_num == 4)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[2].i][fock[2].j - 2] != 1 && latt[fock[2].i][fock[2].j - 1] != 1)//二重条件:不能混乱
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;    fock[0].j -= 2;
                fock[1].j--;
                fock[2].i--;
                fock[2].j++;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 5)
    {
        if (latt[fock[2].i - 1][fock[2].j] != 1 && latt[fock[2].i - 1][fock[2].j+1] != 1
            && latt[fock[2].i + 1][fock[2].j] != 1)//二重条件:不能混乱
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;            fock[0].j++;
            fock[1].i--;            fock[1].j++;
            fock[2].j--;
            fock[3].j--;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num++;
        }
    }
    else if (graph_num == 6)
    {
        if (fock[0].j < 11 && fock[1].j < 11 && fock[2].j < 11 && fock[3].j < 11)//一重条件:不能越界
            if (latt[fock[4].i][fock[4].j + 2] != 1 && latt[fock[4].i][fock[4].j + 1] != 1)//二重条件:不能混乱
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;
                fock[1].i += 2;        fock[1].j--;
                fock[2].i++;        fock[2].j++;
                fock[3].j += 2;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 7)
    {
        if (latt[fock[3].i - 2][fock[3].j] != 1 && latt[fock[3].i - 1][fock[3].j] != 1)//二重条件:不能混乱
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;            fock[0].j++;
            fock[1].i--;            fock[1].j++;
            fock[3].j -= 2;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num -= 3;
        }
    }
    //9有问题8-11,T型
    else if (graph_num == 8)
    {
        if (latt[fock[1].i - 1][fock[1].j] != 1 && latt[fock[1].i][fock[1].j + 1] != 1)//二重条件:不能混乱
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;
            fock[1].i--;            fock[1].j++;
            fock[2].i--;            fock[2].j++;
            fock[3].j--;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num -= 3;
        }
    }
    else if (graph_num == 9)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[2].i][fock[2].j - 1] != 1)
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;    fock[0].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 10)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[2].i - 1][fock[2].j] != 1)
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i--;    fock[0].j++;
                fock[1].j--;
                fock[2].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 11)
    {
        if (latt[fock[3].i][fock[3].j + 1] != 1)
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i++;                fock[0].j--;
            fock[1].j++;
            fock[2].j++;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num++;
        }
    }
    //12-15,有错误,S型
    else if (graph_num == 12)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[3].i][fock[3].j - 2] != 1 && latt[fock[3].i][fock[3].j - 1] != 1)
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;
                fock[1].j++;
                fock[2].i++;        fock[2].j -= 2;
                fock[3].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 13)
    {
        if (latt[fock[2].i - 1][fock[2].j] != 1 && latt[fock[2].i + 1][fock[2].j] != 1)
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;            fock[0].j++;
            fock[1].j--;
            fock[2].i--;        fock[2].j += 2;
            fock[3].j++;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num++;
        }
    }
    else if (graph_num == 14)
    {
        if (fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[3].i][fock[3].j - 2] != 1 && latt[fock[3].i + 1][fock[3].j] != 1)
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i++;    fock[0].j -= 2;
                fock[2].i++;    fock[1].j--;
                fock[3].j--;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
    else if (graph_num == 15)
    {
        if (latt[fock[2].i - 1][fock[2].j] != 1 && latt[fock[2].i][fock[2].j + 1] != 1)
        {
            for (int k = 0; k < 4; k++)//原有位置清零
                latt[fock[k].i][fock[k].j] = 0;
            fock[0].i--;    fock[0].j++;
            fock[2].i--;    fock[1].j++;
            fock[3].j++;
            for (int q = 0; q < 4; q++)
                latt[fock[q].i][fock[q].j] = 2;
            graph_num++;
        }
    }
    else if (graph_num == 16)
    {
        if (fock[0].j < 11 && fock[1].j < 11 && fock[2].j < 11 && fock[3].j < 11 && fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[3].i][fock[3].j - 1] != 1 && latt[fock[3].i][fock[3].j + 1] != 1 && latt[fock[3].i][fock[3].j + 2] != 1)//二重条件:不能混乱
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i += 3;        fock[0].j--;//改变方块位置
                fock[1].j += 2;
                fock[2].i++;        fock[2].j++;
                fock[2].j += 2;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
        //17-18,有错误,I型
    else if (graph_num == 17)
    {
        if (fock[0].j < 11 && fock[1].j < 11 && fock[2].j < 11 && fock[3].j < 11 && fock[0].j > 0 && fock[1].j > 0 && fock[2].j > 0 && fock[3].j > 0)//一重条件:不能越界
            if (latt[fock[2].i - 3][fock[3].j] != 1 && latt[fock[2].i - 2][fock[2].j] != 1 && latt[fock[2].i - 1][fock[2].j] != 1)//二重条件:不能混乱
            {
                for (int k = 0; k < 4; k++)//原有位置清零
                    latt[fock[k].i][fock[k].j] = 0;
                fock[0].i -= 3;        fock[0].j++;//改变方块位置
                fock[1].j -= 2;
                fock[2].i--;        fock[2].j--;
                fock[2].j -= 2;
                for (int q = 0; q < 4; q++)
                    latt[fock[q].i][fock[q].j] = 2;
                graph_num++;
            }
    }
       //O型 
    else {}
}

img

 2. 2统计得分和消行的函数有bug,调用时不能生成方块了
void Goal()
{
    int record;
    for (int i = 28; i > 0; i--)
    {
        for (int j = 0; j < 12; j++)
        {
            record = 0;
            if (latt[i][j] = 1)
            {
                record++;
                if (record == 12)
                {
                    score++;//计分
                    for (int k = 0; k < 12; k++)//原有位置清零
                    {
                        latt[i][k] = 0;
                    }
                    latt[i][j] = latt[i--][j];//上一行覆盖下一行
                }
            }
            
        }
    }
    char point[1000] = { "0" };
    itoa(score, point, 10);//整数转字符
    outtextxy(510, 150, point);
}
int main()
{
    UI_StarView();
    UI_GameView();
    init_latt();//初始化网格二维数组
    Read_Graph();//所有方块类型读取
    initRandomNum();//随机生成方块
    getFockx_y();//更新移动方块位置
    draw_LattAndFocks();//绘制窗口
    BeginBatchDraw();//刷新绘画窗口

    int MoveDownCount = 0;
    //方块移动操作
    while (1)  //while=1为死循环,使窗口不会结束
    {
        if (GetAsyncKeyState(VK_UP) & 0x8000)
            FockChange();
        if (GetAsyncKeyState(VK_LEFT) & 0x8000)//判断案件是否按下的函数<&0x8000为固定搭配>
            MoveFockLeft();
        if (GetAsyncKeyState(VK_RIGHT) & 0x8000)//可同时按下的异步函数
            MoveFockRight();
        Goal();
        if (GetAsyncKeyState(VK_DOWN) & 0x8000)
            if (MoveFockDown() == 0)//纵向移动
            {
                initRandomNum();//如果方块已固定,生成新随机图形
                getFockx_y();
            }
        if (MoveDownCount == 3)
        {
            if (MoveFockDown() == 0)//纵向移动
            {
                initRandomNum();//如果方块已固定,生成新随机图形
                getFockx_y();
            }
            MoveDownCount = 0;
        }
        else
        {
            MoveDownCount++;
        }
        Sleep(Sleep_time);
        draw_LattAndFocks();//更新网格信息
        FlushBatchDraw();
    }
    EndBatchDraw(

3.写代码的思路:构建一个28x12的网格游戏窗口和一个4x4的方块生成窗口,方块的生成和转换都要通过读取文件完成,将方块生成窗口有方块的地方通过循环转到网格里面对应。方块的移动通过循环判断是否碰壁(=1)来限制。
方块转换坐标核对了很多次,还是没有发现错误在哪里,记得分的函数不知道有没有写错

4.目标:我希望能改正我代码错误的地方,不是全部打乱了重写。谢谢

你这代码完全没有修改的价值
你应该引入一点面向对象的思想,方块是方块,坐标是坐标,把一个方块自身的坐标加上绝对坐标就变成实际坐标,而不是像你现在这样一个像素点一个像素点的去抹掉旧的填充新的
if (latt[i][j] = 1)这里是赋值,改成==

写好注释是编程的好习惯

你可参考下别人写的实例【C语言实现俄罗斯方块】,链接:https://blog.csdn.net/qq_54169998/article/details/122800521

写给你了,有注释:

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>

#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数

#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右

#define SPACE 32 //空格键
#define ESC 27 //Esc键

struct Face
{
    int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无)
    int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码
}face;

struct Block
{
    int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种

//隐藏光标
void HideCursor();
//光标跳转
void CursorJump(int x, int y);
//初始化界面
void InitInterface();
//初始化方块信息
void InitBlockInfo();
//颜色设置
void color(int num);
//画出方块
void DrawBlock(int shape, int form, int x, int y);
//空格覆盖
void DrawSpace(int shape, int form, int x, int y);
//合法性判断
int IsLegal(int shape, int form, int x, int y);
//判断得分与结束
int JudeFunc();
//游戏主体逻辑函数
void StartGame();
//从文件读取最高分
void ReadGrade();
//更新最高分到文件
void WriteGrade();

int max, grade; //全局变量
int main()
{
#pragma warning (disable:4996) //消除警告
    max = 0, grade = 0; //初始化变量
    system("title 俄罗斯方块"); //设置cmd窗口的名字
    system("mode con lines=29 cols=60"); //设置cmd窗口的大小
    HideCursor(); //隐藏光标
    ReadGrade(); //从文件读取最高分到max变量    
    InitInterface(); //初始化界面
    InitBlockInfo(); //初始化方块信息
    srand((unsigned int)time(NULL)); //设置随机数生成的起点
    StartGame(); //开始游戏
    return 0;
}

//隐藏光标
void HideCursor()
{
    CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
    curInfo.dwSize = 1;  //如果没赋值的话,隐藏光标无效
    curInfo.bVisible = FALSE; //将光标设置为不可见
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
    SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
//光标跳转
void CursorJump(int x, int y)
{
    COORD pos; //定义光标位置的结构体变量
    pos.X = x; //横坐标设置
    pos.Y = y; //纵坐标设置
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
    SetConsoleCursorPosition(handle, pos); //设置光标位置
}
//初始化界面
void InitInterface()
{
    color(7); //颜色设置为白色
    for (int i = 0; i < ROW; i++)
    {
        for (int j = 0; j < COL + 10; j++)
        {
            if (j == 0 || j == COL - 1 || j == COL + 9)
            {
                face.data[i][j] = 1; //标记该位置有方块
                CursorJump(2 * j, i);
                printf("■");
            }
            else if (i == ROW - 1)
            {
                face.data[i][j] = 1; //标记该位置有方块
                printf("■");
            }
            else
                face.data[i][j] = 0; //标记该位置无方块
        }
    }
    for (int i = COL; i < COL + 10; i++)
    {
        face.data[8][i] = 1; //标记该位置有方块
        CursorJump(2 * i, 8);
        printf("■");
    }

    CursorJump(2 * COL, 1);
    printf("下一个方块:");

    CursorJump(2 * COL + 4, ROW - 19);
    printf("左移:←");

    CursorJump(2 * COL + 4, ROW - 17);
    printf("右移:→");

    CursorJump(2 * COL + 4, ROW - 15);
    printf("加速:↓");

    CursorJump(2 * COL + 4, ROW - 13);
    printf("旋转:空格");

    CursorJump(2 * COL + 4, ROW - 11);
    printf("暂停: S");

    CursorJump(2 * COL + 4, ROW - 9);
    printf("退出: Esc");

    CursorJump(2 * COL + 4, ROW - 7);
    printf("重新开始:R");

    CursorJump(2 * COL + 4, ROW - 5);
    printf("最高纪录:%d", max);

    CursorJump(2 * COL + 4, ROW - 3);
    printf("当前分数:%d", grade);
}
//初始化方块信息
void InitBlockInfo()
{
    //“T”形
    for (int i = 0; i <= 2; i++)
        block[0][0].space[1][i] = 1;
    block[0][0].space[2][1] = 1;

    //“L”形
    for (int i = 1; i <= 3; i++)
        block[1][0].space[i][1] = 1;
    block[1][0].space[3][2] = 1;

    //“J”形
    for (int i = 1; i <= 3; i++)
        block[2][0].space[i][2] = 1;
    block[2][0].space[3][1] = 1;

    for (int i = 0; i <= 1; i++)
    {
        //“Z”形
        block[3][0].space[1][i] = 1;
        block[3][0].space[2][i + 1] = 1;
        //“S”形
        block[4][0].space[1][i + 1] = 1;
        block[4][0].space[2][i] = 1;
        //“O”形
        block[5][0].space[1][i + 1] = 1;
        block[5][0].space[2][i + 1] = 1;
    }

    //“I”形
    for (int i = 0; i <= 3; i++)
        block[6][0].space[i][1] = 1;

    int temp[4][4];
    for (int shape = 0; shape < 7; shape++) //7种形状
    {
        for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种)
        {
            //获取第form种形态
            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    temp[i][j] = block[shape][form].space[i][j];
                }
            }
            //将第form种形态顺时针旋转,得到第form+1种形态
            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    block[shape][form + 1].space[i][j] = temp[3 - j][i];
                }
            }
        }
    }
}
//颜色设置
void color(int c)
{
    switch (c)
    {
    case 0:
        c = 13; //“T”形方块设置为紫色
        break;
    case 1:
    case 2:
        c = 12; //“L”形和“J”形方块设置为红色
        break;
    case 3:
    case 4:
        c = 10; //“Z”形和“S”形方块设置为绿色
        break;
    case 5:
        c = 14; //“O”形方块设置为黄色
        break;
    case 6:
        c = 11; //“I”形方块设置为浅蓝色
        break;
    default:
        c = 7; //其他默认设置为白色
        break;
    }
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
    //注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
//画出方块
void DrawBlock(int shape, int form, int x, int y)
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            if (block[shape][form].space[i][j] == 1) //如果该位置有方块
            {
                CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
                printf("■"); //输出方块
            }
        }
    }
}
//空格覆盖
void DrawSpace(int shape, int form, int x, int y)
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            if (block[shape][form].space[i][j] == 1) //如果该位置有方块
            {
                CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
                printf("  "); //打印空格覆盖(两个空格)
            }
        }
    }
}
//合法性判断
int IsLegal(int shape, int form, int x, int y)
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            //如果方块落下的位置本来就已经有方块了,则不合法
            if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1))
                return 0; //不合法
        }
    }
    return 1; //合法
}
//判断得分与结束
int JudeFunc()
{
    //判断是否得分
    for (int i = ROW - 2; i > 4; i--)
    {
        int sum = 0; //记录第i行的方块个数
        for (int j = 1; j < COL - 1; j++)
        {
            sum += face.data[i][j]; //统计第i行的方块个数
        }
        if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)
            break; //跳出循环
        if (sum == COL - 2) //该行全是方块,可得分
        {
            grade += 10; //满一行加10分
            color(7); //颜色设置为白色
            CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置
            printf("当前分数:%d", grade); //更新当前分数
            for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息
            {
                face.data[i][j] = 0; //该位置得分后被清除,标记为无方块
                CursorJump(2 * j, i); //光标跳转到该位置
                printf("  "); //打印空格覆盖(两个空格)
            }
            //把被清除行上面的行整体向下挪一格
            for (int m = i; m >1; m--)
            {
                sum = 0; //记录上一行的方块个数
                for (int n = 1; n < COL - 1; n++)
                {
                    sum += face.data[m - 1][n]; //统计上一行的方块个数
                    face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行
                    face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行
                    if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块
                    {
                        CursorJump(2 * n, m); //光标跳转到该位置
                        color(face.color[m][n]); //颜色设置为还方块的颜色
                        printf("■"); //打印方块
                    }
                    else //上一行移下来的是空格,打印空格
                    {
                        CursorJump(2 * n, m); //光标跳转到该位置
                        printf("  "); //打印空格(两个空格)
                    }
                }
                if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)
                    return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)
            }
        }
    }
    //判断游戏是否结束
    for (int j = 1; j < COL - 1; j++)
    {
        if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行)
        {
            Sleep(1000); //留给玩家反应时间
            system("cls"); //清空屏幕
            color(7); //颜色设置为白色
            CursorJump(2 * (COL / 3), ROW / 2 - 3);
            if (grade>max)
            {
                printf("恭喜你打破最高记录,最高记录更新为%d", grade);
                WriteGrade();
            }
            else if (grade == max)
            {
                printf("与最高记录持平,加油再创佳绩", grade);
            }
            else
            {
                printf("请继续加油,当前与最高记录相差%d", max - grade);
            }
            CursorJump(2 * (COL / 3), ROW / 2);
            printf("GAME OVER");
            while (1)
            {
                char ch;
                CursorJump(2 * (COL / 3), ROW / 2 + 3);
                printf("再来一局?(y/n):");
                scanf("%c", &ch);
                if (ch == 'y' || ch == 'Y')
                {
                    system("cls");
                    main();
                }
                else if (ch == 'n' || ch == 'N')
                {
                    CursorJump(2 * (COL / 3), ROW / 2 + 5);
                    exit(0);
                }
                else
                {
                    CursorJump(2 * (COL / 3), ROW / 2 + 4);
                    printf("选择错误,请再次选择");
                }
            }
        }
    }
    return 0; //判断结束,无需再调用该函数进行判断
}
//游戏主体逻辑函数
void StartGame()
{
    int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态
    while (1)
    {
        int t = 0;
        int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态
        int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标
        color(nextShape); //颜色设置为下一个方块的颜色
        DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角
        while (1)
        {
            color(shape); //颜色设置为当前正在下落的方块
            DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置
            if (t == 0)
            {
                t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度)
            }
            while (--t)
            {
                if (kbhit() != 0) //若键盘被敲击,则退出循环
                    break;
            }
            if (t == 0) //键盘未被敲击
            {
                if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部)
                {
                    //将当前方块的信息录入face当中
                    //face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。
                    for (int i = 0; i < 4; i++)
                    {
                        for (int j = 0; j < 4; j++)
                        {
                            if (block[shape][form].space[i][j] == 1)
                            {
                                face.data[y + i][x + j] = 1; //将该位置标记为有方块
                                face.color[y + i][x + j] = shape; //记录该方块的颜色数值
                            }
                        }
                    }
                    while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束
                    break; //跳出当前死循环,准备进行下一个方块的下落
                }
                else //未到底部
                {
                    DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
                    y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)
                }
            }
            else //键盘被敲击
            {
                char ch = getch(); //读取keycode
                switch (ch)
                {
                case DOWN: //方向键:下
                    if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法
                    {
                        //方块下落后合法才进行以下操作
                        DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
                        y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)
                    }
                    break;
                case LEFT: //方向键:左
                    if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法
                    {
                        //方块左移后合法才进行以下操作
                        DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
                        x--; //横坐标自减(下一次显示方块时就相当于左移了一格了)
                    }
                    break;
                case RIGHT: //方向键:右
                    if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法
                    {
                        //方块右移后合法才进行以下操作
                        DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
                        x++; //横坐标自增(下一次显示方块时就相当于右移了一格了)
                    }
                    break;
                case SPACE: //空格键
                    if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法
                    {
                        //方块旋转后合法才进行以下操作
                        DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
                        y++; //纵坐标自增(总不能原地旋转吧)
                        form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了)
                    }
                    break;
                case ESC: //Esc键
                    system("cls"); //清空屏幕
                    color(7);
                    CursorJump(COL, ROW / 2);
                    printf("  游戏结束  ");
                    CursorJump(COL, ROW / 2 + 2);
                    exit(0); //结束程序
                case 's':
                case 'S':  //暂停
                    system("pause>nul"); //暂停(按任意键继续)
                    break;
                case 'r':
                case 'R': //重新开始
                    system("cls"); //清空屏幕
                    main(); //重新执行主函数
                }
            }
        }
        shape = nextShape, form = nextForm; //获取下一个方块的信息
        DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖
    }
}
//从文件读取最高分
void ReadGrade()
{
    FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "r"); //以只读方式打开文件
    if (pf == NULL) //打开文件失败
    {
        pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件(文件不存在可以自动创建该文件)
        fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0
    }
    fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头
    fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中
    fclose(pf); //关闭文件
    pf = NULL; //文件指针及时置空
}
//更新最高分到文件
void WriteGrade()
{
    FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件
    if (pf == NULL) //打开文件失败
    {
        printf("保存最高得分记录失败\n");
        exit(0);
    }
    fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分)
    fclose(pf); //关闭文件
    pf = NULL; //文件指针及时置空
}


看看我的这个:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
 
#ifdef _MSC_VER // M$的编译器要给予特殊照顾
 #if _MSC_VER <= 1200 // VC6及以下版本
 #error 你是不是还在用VC6?!
 #else // VC6以上版本
 #if _MSC_VER >= 1600 // 据说VC10及以上版本有stdint.h了
  #include <stdint.h>
 #else // VC10以下版本,自己定义int8_t和uint16_t
  typedef signed char int8_t;
  typedef unsigned short uint16_t;
 #endif
 #ifndef __cplusplus // 据说VC都没有stdbool.h,不用C++编译,自己定义bool
  typedef int bool;
  #define true 1
  #define false 0
 #endif
 #endif
#else // 其他的编译器都好说
 #include <stdint.h>
 #ifndef __cplusplus // 不用C++编译,需要stdbool.h里的bool
 #include <stdbool.h>
 #endif
#endif
 
// =============================================================================
// 7种方块的4旋转状态(4位为一行)
static const uint16_t gs_uTetrisTable[7][4] =
{
 { 0x00F0U, 0x2222U, 0x00F0U, 0x2222U }, // I型
 { 0x0072U, 0x0262U, 0x0270U, 0x0232U }, // T型
 { 0x0223U, 0x0074U, 0x0622U, 0x0170U }, // L型
 { 0x0226U, 0x0470U, 0x0322U, 0x0071U }, // J型
 { 0x0063U, 0x0264U, 0x0063U, 0x0264U }, // Z型
 { 0x006CU, 0x0462U, 0x006CU, 0x0462U }, // S型
 { 0x0660U, 0x0660U, 0x0660U, 0x0660U } // O型
};
 
// =============================================================================
// 初始状态的游戏池
// 每个元素表示游戏池的一行,下标大的是游戏池底部
// 两端各置21,底部2全置为1,便于进行碰撞检测
// 这样一来游戏池的宽度为12// 如果想要传统的10列,只需多填两个1即可(0xE007),当然显示相关部分也要随之改动
// 当某个元素为0xFFFFU时,说明该行已被填满
// 顶部4行用于给方块,不显示出来
// 再除去底部2行,显示出来的游戏池高度为22行
static const uint16_t gs_uInitialTetrisPool[28] =
{
 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,
 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,
 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,
 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xFFFFU, 0xFFFFU
};
 
#define COL_BEGIN 2
#define COL_END 14
#define ROW_BEGIN 4
#define ROW_END 26
 
// =============================================================================
typedef struct TetrisManager // 这个结构体存储游戏相关数据
{
 uint16_t pool[28]; // 游戏池
 int8_t x; // 当前方块x坐标,此处坐标为方块左上角坐标
 int8_t y; // 当前方块y坐标
 int8_t type[3]; // 当前、下一个和下下一个方块类型
 int8_t orientation[3]; // 当前、下一个和下下一个方块旋转状态
 unsigned score; // 得分
 unsigned erasedCount[4]; // 消行数
 unsigned erasedTotal; // 消行总数
 unsigned tetrisCount[7]; // 各方块数
 unsigned tetrisTotal; // 方块总数
 bool dead; // 挂
} TetrisManager;
 
// =============================================================================
typedef struct TetrisControl // 这个结构体存储控制相关数据
{
 bool pause; // 暂停
 bool clockwise; // 旋转方向:顺时针为true
 int8_t direction; // 移动方向:0向左移动 1向右移动
 // 游戏池内每格的颜色
 // 由于此版本是彩色的,仅用游戏池数据无法存储颜色信息
 // 当然,如果只实现单色版的,就没必要用这个数组了
 int8_t color[28][16];
} TetrisControl;
 
HANDLE g_hConsoleOutput; // 控制台输出句柄
 
// =============================================================================
// 函数声明
// 如果使用全局变量方式实现,就没必要传参了
void initGame(TetrisManager *manager, TetrisControl *control); // 初始化游戏
void restartGame(TetrisManager *manager, TetrisControl *control); // 重新开始游戏
void giveTetris(TetrisManager *manager); // 给一个方块
bool checkCollision(const TetrisManager *manager); // 碰撞检测
void insertTetris(TetrisManager *manager); // 插入方块
void removeTetris(TetrisManager *manager); // 移除方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control); // 水平移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control); // 向下移动方块
void rotateTetris(TetrisManager *manager, TetrisControl *control); // 旋转方块
void dropDownTetris(TetrisManager *manager, TetrisControl *control); // 方块直接落地
bool checkErasing(TetrisManager *manager, TetrisControl *control); // 消行检测
void keydownControl(TetrisManager *manager, TetrisControl *control, int key); // 键按下
void setPoolColor(const TetrisManager *manager, TetrisControl *control); // 设置颜色
void gotoxyWithFullwidth(short x, short y); // 以全角定位
void printPoolBorder(); // 显示游戏池边界
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control); // 显示游戏池
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control); // 显示当前方块
void printNextTetris(const TetrisManager *manager); // 显示下一个和下下一个方块
void printScore(const TetrisManager *manager); // 显示得分信息
void runGame(TetrisManager *manager, TetrisControl *control); // 运行游戏
void printPrompting(); // 显示提示信息
bool ifPlayAgain(); // 再来一次
 
// =============================================================================
// 主函数
int main()
{
 TetrisManager tetrisManager;
 TetrisControl tetrisControl;
 
 initGame(&tetrisManager, &tetrisControl); // 初始化游戏
 do
 {
 printPrompting(); // 显示提示信息
 printPoolBorder(); // 显示游戏池边界
 runGame(&tetrisManager, &tetrisControl); // 运行游戏
 if (ifPlayAgain()) // 再来一次
 {
  SetConsoleTextAttribute(g_hConsoleOutput, 0x7);
  system("cls"); // 清屏
  restartGame(&tetrisManager, &tetrisControl); // 重新开始游戏
 }
 else
 {
  break;
 }
 } while (1);
 gotoxyWithFullwidth(0, 0);
 CloseHandle(g_hConsoleOutput);
 return 0;
}
 
// =============================================================================
// 初始化游戏
void initGame(TetrisManager *manager, TetrisControl *control)
{
 CONSOLE_CURSOR_INFO cursorInfo = { 1, FALSE }; // 光标信息
 
 g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台输出句柄
 SetConsoleCursorInfo(g_hConsoleOutput, &cursorInfo); // 设置光标隐藏
 SetConsoleTitleA("俄罗斯方块控制台版——By: NEWPLAN");
 
 restartGame(manager, control);
}
 
// =============================================================================
// 重新开始游戏
void restartGame(TetrisManager *manager, TetrisControl *control)
{
 memset(manager, 0, sizeof(TetrisManager)); // 全部置0
 
 // 初始化游戏池
 memcpy(manager->pool, gs_uInitialTetrisPool, sizeof(uint16_t [28]));
 srand((unsigned)time(NULL)); // 设置随机种子
 
 manager->type[1] = rand() % 7; // 下一个
 manager->orientation[1] = rand() & 3;
 
 manager->type[2] = rand() % 7; // 下下一个
 manager->orientation[2] = rand() & 3;
 
 memset(control, 0, sizeof(TetrisControl)); // 全部置0
 
 giveTetris(manager); // 给下一个方块
 setPoolColor(manager, control); // 设置颜色
}
 
// =============================================================================
// 给一个方块
void giveTetris(TetrisManager *manager)
{
 uint16_t tetris;
 
 manager->type[0] = manager->type[1]; // 下一个方块置为当前
 manager->orientation[0] = manager->orientation[1];
 
 manager->type[1] = manager->type[2];// 下下一个置方块为下一个
 manager->orientation[1] = manager->orientation[2];
 
 manager->type[2] = rand() % 7;// 随机生成下下一个方块
 manager->orientation[2] = rand() & 3;
 
 tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]]; // 当前方块
 
 // 设置当前方块y坐标,保证刚给出时只显示方块最下面一行
 // 这种实现使得玩家可以以很快的速度将方块落在不显示出来的顶部4行内
 if (tetris & 0xF000)
 {
 manager->y = 0;
 }
 else
 {
 manager->y = (tetris & 0xFF00) ? 1 : 2;
 }
 manager->x = 6; // 设置当前方块x坐标
 
 if (checkCollision(manager)) // 检测到碰撞
 {
 manager->dead = true; // 标记游戏结束
 }
 else // 未检测到碰撞
 {
 insertTetris(manager); // 将当前方块加入游戏池
 }
 
 ++manager->tetrisTotal; // 方块总数
 ++manager->tetrisCount[manager->type[0]]; // 相应方块数
 
 printNextTetris(manager); // 显示下一个方块
 printScore(manager); // 显示得分信息
}
 
// =============================================================================
// 碰撞检测
bool checkCollision(const TetrisManager *manager)
{
 // 当前方块
 uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];
 uint16_t dest = 0;
 
 // 获取当前方块在游戏池中的区域:
 // 游戏池坐标x y处小方格信息,按低到高存放在16位无符号数中
 dest |= (((manager->pool[manager->y + 0] >> manager->x) << 0x0) & 0x000F);
 dest |= (((manager->pool[manager->y + 1] >> manager->x) << 0x4) & 0x00F0);
 dest |= (((manager->pool[manager->y + 2] >> manager->x) << 0x8) & 0x0F00);
 dest |= (((manager->pool[manager->y + 3] >> manager->x) << 0xC) & 0xF000);
 
 // 若当前方块与目标区域存在重叠(碰撞),则位与的结果不为0
 return ((dest & tetris) != 0);
}
 
// =============================================================================
// 插入方块
void insertTetris(TetrisManager *manager)
{
 // 当前方块
 uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];
 
 // 当前方块每4位取出,位或到游戏池相应位置,即完成插入方块
 manager->pool[manager->y + 0] |= (((tetris >> 0x0) & 0x000F) << manager->x);
 manager->pool[manager->y + 1] |= (((tetris >> 0x4) & 0x000F) << manager->x);
 manager->pool[manager->y + 2] |= (((tetris >> 0x8) & 0x000F) << manager->x);
 manager->pool[manager->y + 3] |= (((tetris >> 0xC) & 0x000F) << manager->x);
}
 
// =============================================================================
// 移除方块
void removeTetris(TetrisManager *manager)
{
 // 当前方块
 uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];
 
 // 当前方块每4位取出,按位取反后位与到游戏池相应位置,即完成移除方块
 manager->pool[manager->y + 0] &= ~(((tetris >> 0x0) & 0x000F) << manager->x);
 manager->pool[manager->y + 1] &= ~(((tetris >> 0x4) & 0x000F) << manager->x);
 manager->pool[manager->y + 2] &= ~(((tetris >> 0x8) & 0x000F) << manager->x);
 manager->pool[manager->y + 3] &= ~(((tetris >> 0xC) & 0x000F) << manager->x);
}
 
// =============================================================================
// 设置颜色
void setPoolColor(const TetrisManager *manager, TetrisControl *control)
{
 // 由于显示游戏池时,先要在游戏池里判断某一方格有方块才显示相应方格的颜色
 // 这里只作设置即可,没必要清除
 // 当移动方块或给一个方块时调用
 
 int8_t i, x, y;
 
 // 当前方块
 uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];
 
 for (i = 0; i < 16; ++i)
 {
 y = (i >> 2) + manager->y; // 待设置的列
 if (y > ROW_END) // 超过底部限制
 {
  break;
 }
 x = (i & 3) + manager->x; // 待设置的行
 if ((tetris >> i) & 1) // 检测的到小方格属于当前方块区域
 {
  control->color[y][x] = (manager->type[0] | 8); // 设置颜色
 }
 }
}
 
// =============================================================================
// 旋转方块
void rotateTetris(TetrisManager *manager, TetrisControl *control)
{
 int8_t ori = manager->orientation[0]; // 记录原旋转状态
 
 removeTetris(manager); // 移走当前方块
 
 // 顺/逆时针旋转
 manager->orientation[0] = (control->clockwise) ? ((ori + 1) & 3) : ((ori + 3) & 3);
 
 if (checkCollision(manager)) // 检测到碰撞
 {
 manager->orientation[0] = ori; // 恢复为原旋转状态
 insertTetris(manager); // 放入当前方块。由于状态没改变,不需要设置颜色
 }
 else
 {
 insertTetris(manager); // 放入当前方块
 setPoolColor(manager, control); // 设置颜色
 printCurrentTetris(manager, control); // 显示当前方块
 }
}
 
// =============================================================================
// 水平移动方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control)
{
 int x = manager->x; // 记录原列位置
 
 removeTetris(manager); // 移走当前方块
 control->direction == 0 ? (--manager->x) : (++manager->x); // 左/右移动
 
 if (checkCollision(manager)) // 检测到碰撞
 {
 manager->x = x; // 恢复为原列位置
 insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色
 }
 else
 {
 insertTetris(manager); // 放入当前方块
 setPoolColor(manager, control); // 设置颜色
 printCurrentTetris(manager, control); // 显示当前方块
 }
}
 
// =============================================================================
// 向下移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control)
{
 int8_t y = manager->y; // 记录原行位置
 
 removeTetris(manager); // 移走当前方块
 ++manager->y; // 向下移动
 
 if (checkCollision(manager)) // 检测到碰撞
 {
 manager->y = y; // 恢复为原行位置
 insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色
 if (checkErasing(manager, control)) // 检测到消行
 {
  printTetrisPool(manager, control); // 显示游戏池
 }
 }
 else
 {
 insertTetris(manager); // 放入当前方块
 setPoolColor(manager, control); // 设置颜色
 printCurrentTetris(manager, control); // 显示当前方块
 }
}
 
// =============================================================================
// 方块直接落地
void dropDownTetris(TetrisManager *manager, TetrisControl *control)
{
 removeTetris(manager); // 移走当前方块
 for (; manager->y < ROW_END; ++manager->y) // 从上往下
 {
 if (checkCollision(manager)) // 检测到碰撞
 {
  break;
 }
 }
 --manager->y; // 上移一格当然没有碰撞
 
 insertTetris(manager); // 放入当前方块
 setPoolColor(manager, control); // 设置颜色
 
 checkErasing(manager, control); // 检测消行
 printTetrisPool(manager, control); // 显示游戏池
}
 
// =============================================================================
// 消行检测
bool checkErasing(TetrisManager *manager, TetrisControl *control)
{
 static const unsigned scores[5] = { 0, 10, 30, 90, 150 }; // 消行得分
 int8_t count = 0;
 int8_t k = 0, y = manager->y + 3; // 从下往上检测
 
 do
 {
 if (y < ROW_END && manager->pool[y] == 0xFFFFU) // 有效区域内且一行已填满
 {
  ++count;
  // 消除一行方块
  memmove(manager->pool + 1, manager->pool, sizeof(uint16_t) * y);
  // 颜色数组的元素随之移动
  memmove(control->color[1], control->color[0], sizeof(int8_t [16]) * y);
 }
 else
 {
  --y;
  ++k;
 }
 } while (y >= manager->y && k < 4);
 
 manager->erasedTotal += count; // 消行总数
 manager->score += scores[count]; // 得分
 
 if (count > 0)
 {
 ++manager->erasedCount[count - 1]; // 消行
 }
 
 giveTetris(manager); // 给下一个方块
 setPoolColor(manager, control); // 设置颜色
 
 return (count > 0);
}
 
// =============================================================================
// 键按下
void keydownControl(TetrisManager *manager, TetrisControl *control, int key)
{
 if (key == 13) // 暂停/解除暂停
 {
 control->pause = !control->pause;
 }
 
 if (control->pause) // 暂停状态,不作处理
 {
 return;
 }
 
 switch (key)
 {
 case 'w': case 'W': case '8': case 72: // 上
 control->clockwise = true; // 顺时针旋转
 rotateTetris(manager, control); // 旋转方块
 break;
 case 'a': case 'A': case '4': case 75: // 左
 control->direction = 0; // 向左移动
 horzMoveTetris(manager, control); // 水平移动方块
 break;
 case 'd': case 'D': case '6': case 77: // 右
 control->direction = 1; // 向右移动
 horzMoveTetris(manager, control); // 水平移动方块
 break;
 case 's': case 'S': case '2': case 80: // 下
 moveDownTetris(manager, control); // 向下移动方块
 break;
 case ' ': // 直接落地
 dropDownTetris(manager, control);
 break;
 case '0': // 反转
 control->clockwise = false; // 逆时针旋转
 rotateTetris(manager, control); // 旋转方块
 break;
 default:
 break;
 }
}
 
// =============================================================================
// 以全角定位
void gotoxyWithFullwidth(short x, short y)
{
 static COORD cd;
 
 cd.X = (short)(x << 1);
 cd.Y = y;
 SetConsoleCursorPosition(g_hConsoleOutput, cd);
}
 
// =============================================================================
// 显示游戏池边界
void printPoolBorder()
{
 int8_t y;
 
 SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
 for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行
 {
 gotoxyWithFullwidth(10, y - 3);
 printf("%2s", "");
 gotoxyWithFullwidth(23, y - 3);
 printf("%2s", "");
 }
 
 gotoxyWithFullwidth(10, y - 3); // 底部边界
 printf("%28s", "");
}
 
// 定位到游戏池中的方格
#define gotoxyInPool(x, y) gotoxyWithFullwidth(x + 9, y - 3)
 
// =============================================================================
// 显示游戏池
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control)
{
 int8_t x, y;
 
 for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行
 {
 gotoxyInPool(2, y); // 定点到游戏池中的方格
 for (x = COL_BEGIN; x < COL_END; ++x) // 不显示左右边界
 {
  if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块
  {
  // 用相应颜色,显示一个实心方块
  SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);
  printf("■");
  }
  else // 没有方块,显示空白
  {
  SetConsoleTextAttribute(g_hConsoleOutput, 0);
  printf("%2s", "");
  }
 }
 }
}
 
// =============================================================================
// 显示当前方块
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control)
{
 int8_t x, y;
 
 // 显示当前方块是在移动后调用的,为擦去移动前的方块,需要扩展显示区域
 // 由于不可能向上移动,故不需要向下扩展
 y = (manager->y > ROW_BEGIN) ? (manager->y - 1) : ROW_BEGIN; // 向上扩展一格
 for (; y < ROW_END && y < manager->y + 4; ++y)
 {
 x = (manager->x > COL_BEGIN) ? (manager->x - 1) : COL_BEGIN; // 向左扩展一格
 for (; x < COL_END && x < manager->x + 5; ++x) // 向右扩展一格
 {
  gotoxyInPool(x, y); // 定点到游戏池中的方格
  if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块
  {
  // 用相应颜色,显示一个实心方块
  SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);
  printf("■");
  }
  else // 没有方块,显示空白
  {
  SetConsoleTextAttribute(g_hConsoleOutput, 0);
  printf("%2s", "");
  }
 }
 }
}
 
// =============================================================================
// 显示下一个和下下一个方块
void printNextTetris(const TetrisManager *manager)
{
 int8_t i;
 uint16_t tetris;
 
 // 边框
 SetConsoleTextAttribute(g_hConsoleOutput, 0xF);
 gotoxyWithFullwidth(26, 1);
 printf("┏━━━━┳━━━━┓");
 gotoxyWithFullwidth(26, 2);
 printf("┃%8s┃%8s┃", "", "");
 gotoxyWithFullwidth(26, 3);
 printf("┃%8s┃%8s┃", "", "");
 gotoxyWithFullwidth(26, 4);
 printf("┃%8s┃%8s┃", "", "");
 gotoxyWithFullwidth(26, 5);
 printf("┃%8s┃%8s┃", "", "");
 gotoxyWithFullwidth(26, 6);
 printf("┗━━━━┻━━━━┛");
 
 // 下一个,用相应颜色显示
 tetris = gs_uTetrisTable[manager->type[1]][manager->orientation[1]];
 SetConsoleTextAttribute(g_hConsoleOutput, manager->type[1] | 8);
 for (i = 0; i < 16; ++i)
 {
 gotoxyWithFullwidth((i & 3) + 27, (i >> 2) + 2);
 ((tetris >> i) & 1) ? printf("■") : printf("%2s", "");
 }
 
 // 下下一个,不显示彩色
 tetris = gs_uTetrisTable[manager->type[2]][manager->orientation[2]];
 SetConsoleTextAttribute(g_hConsoleOutput, 8);
 for (i = 0; i < 16; ++i)
 {
 gotoxyWithFullwidth((i & 3) + 32, (i >> 2) + 2);
 ((tetris >> i) & 1) ? printf("■") : printf("%2s", "");
 }
}
 
// =============================================================================
// 显示得分信息
void printScore(const TetrisManager *manager)
{
 static const char *tetrisName = "ITLJZSO";
 int8_t i;
 
 SetConsoleTextAttribute(g_hConsoleOutput, 0xE);
 
 gotoxyWithFullwidth(2, 2);
 printf("■得分:%u", manager->score);
 
 gotoxyWithFullwidth(1, 6);
 printf("■消行总数:%u", manager->erasedTotal);
 for (i = 0; i < 4; ++i)
 {
 gotoxyWithFullwidth(2, 8 + i);
 printf("□消%d:%u", i + 1, manager->erasedCount[i]);
 }
 
 gotoxyWithFullwidth(1, 15);
 printf("■方块总数:%u", manager->tetrisTotal);
 
 for (i = 0; i < 7; ++i)
 {
 gotoxyWithFullwidth(2, 17 + i);
 printf("□%c形:%u", tetrisName[i], manager->tetrisCount[i]);
 }
}
 
// =============================================================================
// 显示提示信息
void printPrompting()
{
 SetConsoleTextAttribute(g_hConsoleOutput, 0xB);
 gotoxyWithFullwidth(26, 10);
 printf("■控制:");
 gotoxyWithFullwidth(27, 12);
 printf("□向左移动:← A 4");
 gotoxyWithFullwidth(27, 13);
 printf("□向右移动:→ D 6");
 gotoxyWithFullwidth(27, 14);
 printf("□向下移动:↓ S 2");
 gotoxyWithFullwidth(27, 15);
 printf("□顺时针转:↑ W 8");
 gotoxyWithFullwidth(27, 16);
 printf("□逆时针转:0");
 gotoxyWithFullwidth(27, 17);
 printf("□直接落地:空格");
 gotoxyWithFullwidth(27, 18);
 printf("□暂停游戏:回车");
 gotoxyWithFullwidth(25, 23);
 printf("■By: NEWPLAN @ UESTC");
}
 
// =============================================================================
// 运行游戏
void runGame(TetrisManager *manager, TetrisControl *control)
{
 clock_t clockLast, clockNow;
 
 clockLast = clock(); // 计时
 printTetrisPool(manager, control); // 显示游戏池
 
 while (!manager->dead) // 没挂
 {
 while (_kbhit()) // 有键按下
 {
  keydownControl(manager, control, _getch()); // 处理按键
 }
 
 if (!control->pause) // 未暂停
 {
  clockNow = clock(); // 计时
  // 两次记时的间隔超过0.45if (clockNow - clockLast > 0.45F * CLOCKS_PER_SEC)
  {
  clockLast = clockNow;
  keydownControl(manager, control, 80); // 方块往下移
  }
 }
 }
}
 
// =============================================================================
// 再来一次
bool ifPlayAgain()
{
 int ch;
 
 SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
 gotoxyWithFullwidth(15, 10);
 printf("游戏结束");
 gotoxyWithFullwidth(13, 11);
 printf("按Y重玩,按N退出");
 
 do
 {
 ch = _getch();
 if (ch == 'Y' || ch == 'y')
 {
  return true;
 }
 else if (ch == 'N' || ch == 'n')
 {
  return false;
 }
 } while (1);
}