1.俄罗斯方块
主入口
#include <iostream>
#include <Windows.h>
#include <conio.h>
using namespace std;
#include "CGame.h"
int main(){
#if 0
// 方块测试
CBlock block;
block.CreateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.RotateBlock();
block.CreateBlock();
#endif // 0
#if 0
// 背景测试
CBackground bg;
bg.InitBk();
bg.RemoveRows();
#endif // 0
CGame game;
game.InitGame();
game.ShowGame();
// 控制方块 直到游戏结束
while (1) { // 是否有键盘输入
int nRet = _kbhit();
if (nRet != 0) {
char nKey = _getch(); // 有键盘输入 获取按下的值
switch (nKey) {
// 上
case 'W':
case 'w':
game.MoveUp();
break;
// 下
case 'S':
case 's':
game.MoveDown();
break;
// 左
case 'A':
case 'a':
game.MoveLeft();
break;
// 右
case 'D':
case 'd':
game.MoveRight();
break;
}
// 每次操作完之后再显示界面
game.ShowGame();
}
}
system("pause");
return 0;
}
方块封装
#pragma once
#define BLOCK_ROW 4 // 方块有几行
#define BLOCK_COL 4 // 方块有几列
/*
对方块进行封装
*/
class CBlock
{
public:
CBlock();
~CBlock();
void CreateBlock(); // 创建一个方块
void RotateBlock(); // 旋转方块
int GetIdx() const;
void SetIdx(int idx);
// 创建一个指向BLOCK_COL列的二维数组指针
typedef char(*PBLOCkDATA)[BLOCK_COL];
// 返回方块的数据
const PBLOCkDATA GetBlockData() const;
private:
char m_aryBlock[BLOCK_ROW][BLOCK_COL];// 方块的数组 每个方块都是四行四列
int m_nType; // 方块的类型
int m_nIdex; // 方块类型中的第几个形态
};
#include "CBlock.h"
#include <time.h>
#include <stdlib.h>
#include <string.h>
#define BLOCK_SIZE 16 // 每个方块的每个类型是16个字节大小
#define BLOCK_TYPE_COUNT 4 // 每种方块有四个类型
// 总共就是 每个方块占 BLOCK_SIZE * BLOCK_TYPE_COUNT 64 个字节
#define BLOCK_TYPE_ROW_COUNT 16 // 每种方块在大数组中占16行
/*
每个方块
和每个方块对应的四种形态
二维数组
每四行代表一个方块的形态
每十六行代表一个方块的四种形态
*/
static char g_uchBlocks[][BLOCK_COL] = {
/*
■■ ■■■■ ■ ■■■
■■ ■■■ ■
1代表方块 0代表空格
每个方块有四种形状
*/
/*
■■
■■
*/
1,1,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
/*
■■■■
*/
1,1,1,1,
0,0,0,0,
0,0,0,0,
0,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0,
1,1,1,1,
0,0,0,0,
0,0,0,0,
0,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0,
/*
■
■■■
*/
0,1,0,0,
1,1,1,0,
0,0,0,0,
0,0,0,0,
0,1,0,0,
1,1,0,0,
0,1,0,0,
0,0,0,0,
1,1,1,0,
0,1,0,0,
0,0,0,0,
0,0,0,0,
0,1,0,0,
0,1,1,0,
0,1,0,0,
0,0,0,0,
/*
■■■
■
*/
1,1,1,0,
0,0,1,0,
0,0,0,0,
0,0,0,0,
0,1,0,0,
0,1,0,0,
1,1,0,0,
0,0,0,0,
1,0,0,0,
1,1,1,0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,0,0,0,
1,0,0,0,
0,0,0,0
};
CBlock::CBlock()
{
// 初始化时间种子
srand((unsigned)time(NULL));
}
CBlock::~CBlock()
{
}
// 创建一个方块
void CBlock::CreateBlock()
{
/*
int m_nType; // 方块的类型
int m_nIdex; // 方块类型中的第几个形态
*/
// 大数组中有四类
// 二维数组总的字节 / 64 就是有多少种类型的方块 一个方块64字节
this->m_nType = rand() % (sizeof(g_uchBlocks) / (BLOCK_TYPE_COUNT * BLOCK_SIZE));
// 每一类有四个形态 那么对4取模 就是 0 - 3 0 代表第一种
this->m_nIdex = rand() % BLOCK_TYPE_COUNT;
// 拷贝方块的数据
memcpy_s(this->m_aryBlock,
sizeof(this->m_aryBlock),
&g_uchBlocks[this->m_nType*BLOCK_TYPE_ROW_COUNT + this->m_nIdex* BLOCK_TYPE_COUNT][0],
sizeof(this->m_aryBlock));
}
// 旋转方块
void CBlock::RotateBlock()
{
++(this->m_nIdex); // 直接形态+1
if(this->m_nIdex >= BLOCK_TYPE_COUNT){ // 如果形态已经不是术语当前方块的量
this->m_nIdex = 0; // 那么就为
}
// 重新拷贝方块数据 完成旋转
memcpy_s(this->m_aryBlock,
sizeof(this->m_aryBlock),
&g_uchBlocks[this->m_nType * BLOCK_TYPE_ROW_COUNT + this->m_nIdex * BLOCK_TYPE_COUNT][0],
sizeof(this->m_aryBlock));
}
int CBlock::GetIdx() const
{
return this->m_nIdex;
}
void CBlock::SetIdx(int idx)
{
this->m_nIdex = idx;
// 重新拷贝方块数据 恢复旋转之前的状态
memcpy_s(this->m_aryBlock,
sizeof(this->m_aryBlock),
&g_uchBlocks[this->m_nType * BLOCK_TYPE_ROW_COUNT + this->m_nIdex * BLOCK_TYPE_COUNT][0],
sizeof(this->m_aryBlock));
}
/*
返回方块的数据
*/
const CBlock::PBLOCkDATA CBlock::GetBlockData() const
{
return (PBLOCkDATA)this->m_aryBlock;
}
背景封装
#pragma once
#include "CBlock.h"
#define BK_ROW 12 // 背景的行数
#define BK_COL 16 // 背景的列数
#define WALL 1 // 标志,墙
#define SPACE 0 // 标志 非墙
/*
背景墙
*/
class CBackground
{
public:
CBackground();
~CBackground();
// 初始化背景墙
void InitBk();
// 消除行数 返回消除了多少行
int RemoveRows();
// 固定方块 方块所在坐标
void FixedBlock(const CBlock &block,size_t nRow,size_t nCol);
// 清除原来的方块
void ClearBlock(const CBlock& block, size_t nRow, size_t nCol);
// 定义一个拥有BK_COL列的二维数组指针 名字叫做 BKDATA
typedef unsigned char (*BKDATA)[BK_COL];
// 返回当前背景的二维数组指针
const BKDATA GetBkData() const;
private:
bool IsCanRemove(size_t nRow); // 此行是否可以消除
void RemoveRow(size_t nRow); // 消除行
private:
unsigned char m_aryBkData[BK_ROW][BK_COL];
};
#include "CBackground.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
CBackground::CBackground()
{
}
CBackground::~CBackground()
{
}
/*
初始化背景墙
*/
void CBackground::InitBk()
{
for (size_t nRow = 0; nRow < BK_ROW; nRow++){
for(size_t nCol = 0; nCol < BK_COL; nCol++){
// 如果是左右边界 或者 下边界 才是方块
if (nCol == 0 || nCol == BK_COL - 1 || nRow == BK_ROW - 1){
this->m_aryBkData[nRow][nCol] = WALL; // 墙
}else{
this->m_aryBkData[nRow][nCol] = SPACE; // 非墙
}
}
}
}
const CBackground::BKDATA CBackground::GetBkData() const
{
return (BKDATA)this->m_aryBkData;
}
/*
此行是否可以消除
*/
bool CBackground::IsCanRemove(size_t nRow)
{
/*
从第二列开始到倒数第二列
*/
for (size_t nCol = 1; nCol < BK_COL - 1; nCol++) {
if (this->m_aryBkData[nRow][nCol] == SPACE){
// 如果这一行里面有一个是空白 那么就证明这一行不是全部为方块了
// 所以这里直接return 就好了
return false;
}
}
// 如果前面一行都遍历完了还没有return 就证明这一行都是墙壁 那么久可以return true了
// 证明这一行可以消除
return true;
}
/*
消除行
*/
void CBackground::RemoveRow(size_t nRow)
{
/*
从当前这行可以被消除的行开始向上复制
*/
for (size_t i = nRow ; i > 0; i--) {
/*
从这一行开始每次向上拷贝一行下来覆盖当前的数据
直至第二行 即i = 1的时候也拷贝
i = 0 第一行 就不拷贝了
*/
memcpy_s(&this->m_aryBkData[i][0],
BK_COL,
&m_aryBkData[i-1][0],
BK_COL);
}
}
/*
消行
*/
int CBackground::RemoveRows()
{
int removeRow = 0;
/*
从最后一行 非墙的位置 往上遍历 逐行判断是否可以消行
*/
for (size_t nRow = BK_ROW - 2; nRow > 0;) {
// 每一行都去判断是否可以消除
if(IsCanRemove(nRow)){
// 如果可以消的话
// 从要被消除的这行开始 要把上面的复制下来
RemoveRow(nRow); // 消除此行
removeRow++; // 每次消除一行计数+1
}else{
// 因为删除之后 当前行就是上一行了 不需要再--
// 只有当这一行没被删除的时候 我们才继续向上扫描
nRow--;
}
}
return removeRow;
}
/*
固定方块 其实就是变为了墙壁
其实就是把方块的数据拷贝到背景墙中
方块所在坐标 行和列
*/
void CBackground::FixedBlock(const CBlock& block,size_t nRow, size_t nCol)
{
CBlock::PBLOCkDATA pBlockData = block.GetBlockData();
/*
将方块的数据写入到背景墙
*/
for (size_t i = 0; i < BLOCK_ROW; i++) {
for (size_t j = 0; j < BLOCK_COL; j++) {
// 如果是为1
if(pBlockData[i][j] == WALL){ // 找出四行四列中是为方块的坐标
// 写入到大数组中
// 从当前的行数开始遍历四行 从当前的列数开始遍历四列
this->m_aryBkData[nRow+i][nCol+j] = pBlockData[i][j];
}
}
}
}
/*
清除原来的方块
*/
void CBackground::ClearBlock(const CBlock& block, size_t nRow, size_t nCol)
{
// 获取当前方块
CBlock::PBLOCkDATA pBlockData = block.GetBlockData();
/*
将方块的数据写入到背景墙
*/
for (size_t i = 0; i < BLOCK_ROW; i++) {
for (size_t j = 0; j < BLOCK_COL; j++) {
// 如果是为1
if (pBlockData[i][j] == WALL) { // 找出四行四列中是为方块的坐标
// 写入到大数组中
// 从当前的行数开始遍历四行 从当前的列数开始遍历四列
// 把当前为1的墙壁修改为空格
this->m_aryBkData[nRow + i][nCol + j] = SPACE;
}
}
}
}
控制封装
#pragma once
#include "CBackground.h"
#include "CBlock.h"
/*
游戏的操作逻辑
*/
class CGame
{
public:
CGame();
~CGame();
// 初始化游戏
void InitGame();
// 显示游戏
void ShowGame();
// 移动方块
void MoveLeft();
void MoveRight();
void MoveDown();
void MoveUp();
private:
bool IsCanMove(); // 是否可以移动
private:
CBlock m_block; // 持有方块
CBackground m_bk; // 持有背景
size_t m_nRow; // 方块的x坐标
size_t m_nCol; // 方块的y坐标
};
#include "CGame.h"
#include <stdlib.h>
#include <iostream>
using namespace std;
CGame::CGame()
{
}
CGame::~CGame()
{
}
/*
初始化游戏
*/
void CGame::InitGame()
{
/*
初始化背景
初始化方块
初始化方块的坐标
*/
this->m_bk.InitBk(); // 把背景左右两边和下面初始化为墙壁
this->m_block.CreateBlock(); // 挑选出方块的类型和形态写入方块数组中
this->m_nRow = 0; // 默认方块所在的行是第0行
this->m_nCol = BK_COL / 2 - 2; // 默认方块所在的位置是正中间
// 游戏一开始就显示一个方块在正中间
/*
通过方块对象获取方块的小数组遍历,
根据当前方块的坐标
把当前方块加入到大数组中
*/
this->m_bk.FixedBlock(this->m_block,m_nRow,m_nCol);
}
/*
显示游戏
*/
void CGame::ShowGame()
{
// 清除屏幕
system("cls");
// 获取背景数据
/*
因为我们已经把小方块的数据写入到背景中了
这个时候小方块所在的位置也是墙壁了
所以我们直接遍历整个大数组显示就好了
*/
CBackground::BKDATA pData = this->m_bk.GetBkData();
// 开始显示
for (size_t nRow = 0; nRow < BK_ROW; nRow++) {
for (size_t nCol = 0; nCol < BK_COL; nCol++) {
// 如果是墙壁
if(pData[nRow][nCol] == WALL){
cout << "■";
}else{
cout << " ";
}
}
cout << endl;
}
}
/*
向左移动
*/
void CGame::MoveLeft()
{
/*
先进行清除原来的方块
*/
this->m_bk.ClearBlock(this->m_block, this->m_nRow, this->m_nCol);
/*
因为在初始化的时候我们就已经保存了方块的初始坐标
和已经把方块固定到大背景中了 所以这里可以直接进行操作了
*/
this->m_nCol--; // 列数--
// 如果撞到墙壁了
if (!IsCanMove()) { // 如果不能移动
this->m_nCol++;
}
/*
清除原来的方块位置
移动完以后
重新将方块写入到背景中【即股东】
*/
this->m_bk.FixedBlock(this->m_block, this->m_nRow, this->m_nCol);
}
/*
向右移动
*/
void CGame::MoveRight()
{
/*
先进行清除原来的方块
*/
this->m_bk.ClearBlock(this->m_block, this->m_nRow, this->m_nCol);
/*
因为在初始化的时候我们就已经保存了方块的初始坐标
和已经把方块固定到大背景中了 所以这里可以直接进行操作了
*/
this->m_nCol++; // 列数++
// 如果撞到墙壁了
if (!IsCanMove()) { // 如果不能移动
this->m_nCol--;
}
/*
清除原来的方块位置
移动完以后
重新将方块写入到背景中【即股东】
*/
this->m_bk.FixedBlock(this->m_block, this->m_nRow, this->m_nCol);
}
/*
向下移动
*/
void CGame::MoveDown()
{
/*
先进行清除原来的方块
*/
this->m_bk.ClearBlock(this->m_block, this->m_nRow, this->m_nCol);
/*
因为在初始化的时候我们就已经保存了方块的初始坐标
和已经把方块固定到大背景中了 所以这里可以直接进行操作了
*/
this->m_nRow++; // 行数++
if(!IsCanMove()){ // 如果不能移动
this->m_nRow--;
// 向下不能移动了 固定住
this->m_bk.FixedBlock(this->m_block, this->m_nRow, this->m_nCol);
// 产生新的方块
this->m_block.CreateBlock(); // 挑选出方块的类型和形态写入方块数组中
this->m_nRow = 0; // 默认方块所在的行是第0行
this->m_nCol = BK_COL / 2 - 2; // 默认方块所在的位置是正中间
// 检查是否能清除
this->m_bk.RemoveRows();
}
/*
清除原来的方块位置
移动完以后
重新将方块写入到背景中【即股东】
*/
this->m_bk.FixedBlock(this->m_block,this->m_nRow,this->m_nCol);
}
/*
旋转
*/
void CGame::MoveUp()
{
/*
先进行清除原来的方块
*/
this->m_bk.ClearBlock(this->m_block, this->m_nRow, this->m_nCol);
// 旋转
int nOldIdx = this->m_block.GetIdx(); // 获取原来旋转之前的形态
/*
进行旋转
旋转会修改小方块数组对应的1和0
*/
this->m_block.RotateBlock();
/*
旋转之后进行判断 看看旋转之后的形态
是否跟墙壁重叠了
如果重叠的话就恢复没旋转之前的形态
*/
if(!IsCanMove()){
// 如果不可以移动的话
this->m_block.SetIdx(nOldIdx);
}
/*
清除原来的方块位置
旋转完以后
重新将方块写入到背景中【即股东】
*/
this->m_bk.FixedBlock(this->m_block, this->m_nRow, this->m_nCol);
}
/*
是否可以移动
*/
bool CGame::IsCanMove()
{
// 获取背景数据
CBackground::BKDATA pData = this->m_bk.GetBkData();
// 获取当前方块
CBlock::PBLOCkDATA pBlockData = this->m_block.GetBlockData();
// 循环判断
for(size_t i = 0; i < BLOCK_ROW; i++){
for(size_t j = 0; j < BLOCK_COL; j++){
// 如果方块中墙壁的位置和背景的墙的位置重叠了
/*
如果小方块在大数组中移动到了墙壁
并且是实心的地方移动到墙壁
*/
if(pData[this->m_nRow+i][this->m_nCol+j] == WALL
&& pBlockData[i][j] == WALL){
return false;
}
}
}
return true;
}