项目地址:
https://yunpan.cn/cYkeA6gGbFrXD (提取码:a2a3)
一、定义了一些结构体和常量
// 每个俄罗斯方块(block)由4个正方形(pston)组成,一个正方形(pston)的定义如下:
typedef struct pston
{
int row;
int col;
} ty_pston;
// 定义一个俄罗斯方块(block),包含了4个正方形pst,及颜色属性color,距离左l与顶t的位置
typedef struct block
{
ty_pston pst[4];
COLORREF color;
int t;
int l;
} ty_block;
// 游戏区域(gamePst)的坐标(ps)是否有方块(bSet)
typedef struct gamePst
{
BOOL bSet;
COLORREF color;
ty_pston ps;
} ty_gamepst;
// 随机颜色
#define COLOR_RAND RGB(rand()%256, rand() % 256, rand() % 256)
// 俄罗斯方块(block)的种类(pst_kind)有7种,每种有4个正方形(pston)
const ty_pston pst_kind[7][4] = {
{ {0,-1}, {0,0}, {0,1}, {0,2} },
{ { 0,-1},{ 0,0 },{ 0,1 },{ 1,1 } },
{ { 0,-1 },{ 0,0 },{ 0,1 },{ -1,1 } },
{ { 0,-1 },{ 0,0 },{ -1,0 },{ -1,1 } },
{ { 0,-1 },{ 0,0 },{ 1,0 },{ 1,1 } },
{ { 0,-1 },{ 0,0 },{ 1,0 },{ 1,-1 } },
{ { 0,-1 },{ 0,0 },{ 0,1 },{ 1,0 } },
};
// 俄罗斯方块的移动方向(direction)
enum direction {LEFT = -1,DOWN = 0,RIGHT = 1};
// 每个正方形(pston)的宽度
const int PST_WIDTH = 24;
// 游戏区域相对于左边与上边的距离
const int GAME_SIDE_LEFT = 10;
const int GAME_SIDE_TOP = 10;
#pragma once
class CStone
{
public:
CStone();
~CStone();
private:
ty_block m_block;
int curBlockType;
ty_gamepst m_gPst[18][10];
int score;
public:
ty_block m_preBlock;
public:
void drawBlock();
void makeBlock(ty_block& block);
void alterBlock();
void MoveTo(int to);
void reMakeBlock();
BOOL canMove(ty_block block);
CRect getPstRect(pston pst);
CRect getGamePstRect(pston pst);
CRect getPrePstRect(pston pst);
BOOL IsTop();
};
------------
#include "stdafx.h"
#include "Stone.h"
#include
#include
#include
#include "TetrisDlg.h"
CStone::CStone()
{
// 初始化时生成一个游戏区的俄罗斯方块
makeBlock(m_block);
// 把游戏区域18*10=180个正方形初始化状态与位置
for (int i = 0; i < 18; ++i)
for (int j = 0; j < 10; ++j)
m_gPst[i][j] = { FALSE ,RGB(255,255,255),{ i,j } };
// 初始化分数
score = 0;
}
CStone::~CStone()
{
}
// 生成一个俄罗斯方块
void CStone::makeBlock(ty_block& block)
{
srand((unsigned)time(0));
curBlockType = rand() % 7;
for (int i = 0; i < 4; ++i)
block.pst[i] = pst_kind[curBlockType][i];
block.color = COLOR_RAND;
block.l = 4;
block.t = 0;
}
// 绘制界面
void CStone::drawBlock()
{
// 获取客户区的绘图设备
CClientDC* p_dc = (CClientDC*)AfxGetMainWnd()->GetDC();
// 获取兼容的绘图设备
CDC mdc;
mdc.CreateCompatibleDC(p_dc);
// 获取兼容的位图
CBitmap bmp_cpb;
bmp_cpb.CreateCompatibleBitmap(p_dc, 10 * PST_WIDTH, 18 * PST_WIDTH);
// 载入位图
mdc.SelectObject(&bmp_cpb);
// 填充背景色为白色
mdc.FillSolidRect(CRect(0,0,10* PST_WIDTH,18* PST_WIDTH), RGB(255,255,255));
// 绘制游戏区的俄罗斯方块
for (int a = 0; a < 4;++a)
{
CBrush * bs_block = new CBrush(m_block.color);
mdc.FillRect(getPstRect(m_block.pst[a]), bs_block);
bs_block->DeleteObject();
bs_block = nullptr;
}
// 绘制游戏区的底部正方形
for (int i = 0; i < 18;++i)
{
for (int j = 0; j < 10;++j)
{
if (m_gPst[i][j].bSet) {
CBrush * bs_gPst = new CBrush(m_gPst[i][j].color);
mdc.FillRect(getGamePstRect(m_gPst[i][j].ps), bs_gPst);
bs_gPst->DeleteObject();
bs_gPst = nullptr;
}
}
}
// 将缓冲区的位图显示在游戏区
p_dc->BitBlt(GAME_SIDE_LEFT, GAME_SIDE_TOP, 10 * PST_WIDTH, 18 * PST_WIDTH, &mdc, 0, 0, SRCCOPY);
mdc.DeleteDC();
// 获取兼容的绘图设备
CDC premdc;
premdc.CreateCompatibleDC(p_dc);
// 绘制预览区的俄罗斯方块
CBitmap bmp_precpb;
bmp_precpb.CreateCompatibleBitmap(p_dc, 6 * PST_WIDTH, 18 * PST_WIDTH);
// 载入位图
premdc.SelectObject(&bmp_precpb);
// 填充背景色为白色
premdc.FillSolidRect(CRect(0, 0, 6 * PST_WIDTH, 18 * PST_WIDTH), RGB(255, 255, 255));
for (int b = 0; b < 4; ++b)
{
CBrush * bs_preblock = new CBrush(m_preBlock.color);
premdc.FillRect(getPrePstRect(m_preBlock.pst[b]), bs_preblock);
bs_preblock->DeleteObject();
bs_preblock = nullptr;
}
// 绘制游戏得分情况
CString curScore;
curScore.Format(_T("score:%d"), score);
premdc.TextOut(2*PST_WIDTH, 300, curScore);
// 将缓冲区的位图显示在预览区
p_dc->BitBlt((10+1)*PST_WIDTH +GAME_SIDE_LEFT, GAME_SIDE_TOP, (10+1+6) * PST_WIDTH, 18 * PST_WIDTH, &premdc, 0, 0, SRCCOPY);
premdc.DeleteDC();
p_dc->DeleteDC();
}
// 获取俄罗斯方块中的正方形区域
CRect CStone::getPstRect(pston pst)
{
if ((pst.row + m_block.t) < 0 || (pst.col+ m_block.l) < 0) {
return CRect(0,0,0,0);
}
return CRect(
(pst.col+m_block.l)*PST_WIDTH,
(pst.row+m_block.t)*PST_WIDTH,
(pst.col + m_block.l+1) *PST_WIDTH,
(pst.row + m_block.t+1) *PST_WIDTH
);
}
// 获取游戏区域中的每个正方形的区域
CRect CStone::getGamePstRect(pston pst)
{
return CRect(
(pst.col)*PST_WIDTH,
(pst.row)*PST_WIDTH,
(pst.col + 1) *PST_WIDTH,
(pst.row + 1) *PST_WIDTH
);
}
// 获取预览区的俄罗斯方块的区域
CRect CStone::getPrePstRect(pston pst)
{
return CRect(
(pst.col+m_preBlock.l-1)*PST_WIDTH,
(pst.row + m_preBlock.t+3)*PST_WIDTH,
(pst.col + m_preBlock.l)*PST_WIDTH,
(pst.row + m_preBlock.t +4)*PST_WIDTH
);
}
// 变形
void CStone::alterBlock()
{
ty_block tempcopy = m_block;
auto swapBlock = [](ty_pston & ps) {
int temp = ps.row;
ps.row = ps.col*(-1);
ps.col = temp;
};
for (ty_pston &ps : tempcopy.pst)
{
swapBlock(ps);
}
if (canMove(tempcopy)) {
m_block = tempcopy;
}
}
// 移动
void CStone::MoveTo(int to)
{
if (to == DOWN) {
++m_block.t;
if (!canMove(m_block))
{
--m_block.t;
reMakeBlock();
}
}
else {
m_block.l += to;
if (!canMove(m_block))
{
m_block.l -= to;
}
}
}
// 判断能否移动
BOOL CStone::canMove(ty_block block)
{
std::vector vcol,vrow;
for (ty_pston e : block.pst)
{
if (m_gPst[e.row + block.t][e.col + block.l].bSet)
return FALSE;
vcol.push_back(e.col);
vrow.push_back(e.row);
}
int colMax = *std::max_element(vcol.begin(), vcol.end());
int colMin = *std::min_element(vcol.begin(), vcol.end());
int rowMax = *std::max_element(vrow.begin(),vrow.end());
return (colMin + block.l) >= 0 && (colMax + block.l) < 10 && (rowMax + block.t) < 18;
}
// 重新生成俄罗斯方块
void CStone::reMakeBlock()
{
for (ty_pston e : m_block.pst)
{
m_gPst[e.row + m_block.t][e.col + m_block.l].bSet = TRUE;
m_gPst[e.row + m_block.t][e.col + m_block.l].color = m_block.color;
}
auto isfull = [=](int row) {
for (int j=0; j < 10; ++j)
if (!m_gPst[row][j].bSet)
return FALSE;
return TRUE;
};
for (int i=0; i < 18; )
{
if (isfull(i)) {
for (int k = i; k > 0;--k)
{
for (int t = 0; t < 10;++t)
{
m_gPst[k][t].bSet = m_gPst[k - 1][t].bSet;
m_gPst[k][t].color = m_gPst[k - 1][t].color;
}
}
score += 100;
continue;
}
++i;
}
m_block = m_preBlock;
makeBlock(m_preBlock);
IsTop();
}
BOOL CStone::IsTop()
{
++m_block.t;
if (!canMove(m_block))
{
KillTimer(AfxGetMainWnd()->m_hWnd, 1002);
((CTetrisDlg*)AfxGetMainWnd())->m_status = FALSE;
AfxMessageBox(_T("游戏已结束"));
}
--m_block.t;
return 0;
}
三、对话框
void CTetrisDlg::OnPaint()
{
// 游戏区域
CRect rc(9, 9, 251, 443);
// 预览区
CRect preRc(273, 9, 420, 445);
CClientDC* p_dc = (CClientDC*)GetDC();
CPen * pen = new CPen(PS_SOLID, 1, RGB(255, 0, 0));
CPen * oldpen = p_dc->SelectObject(pen);
p_dc->Rectangle(rc);
p_dc->Rectangle(preRc);
p_dc->SelectObject(oldpen);
pen->DeleteObject();
pen = nullptr;
p_dc->Detach();
}
void CTetrisDlg::OnBnClickedOk()
{
if (!m_status) {
m_status = TRUE;
m_stone->makeBlock(m_stone->m_preBlock);
m_stone->drawBlock();
SetTimer(1002, 300, 0);
}
else {
m_status = FALSE;
KillTimer(1002);
}
}
BOOL CTetrisDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
if (pMsg->message == WM_KEYDOWN) {
if (!m_status)
{
return TRUE;
}
switch (pMsg->wParam)
{
case VK_LEFT:
m_stone->MoveTo(LEFT);
break;
case VK_RIGHT:
m_stone->MoveTo(RIGHT);
break;
case VK_DOWN:
m_stone->MoveTo(DOWN);
break;
case VK_UP:
m_stone->alterBlock();
break;
}
m_stone->drawBlock();
return TRUE;
}
return CDialog::PreTranslateMessage(pMsg);
}
void CTetrisDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent) {
case 1002:
{
m_stone->MoveTo(DOWN);
break;
}
default:
break;
}
m_stone->drawBlock();
CDialog::OnTimer(nIDEvent);
}