1272 lines
37 KiB
C++
1272 lines
37 KiB
C++
#include "stdafx.h"
|
|
#include "Tetris.h"
|
|
#include "TetrisLogicInternal.h"
|
|
|
|
int nType = 0;
|
|
int type = 0;
|
|
int state = 0;
|
|
int tScore = 0;
|
|
bool gameOverFlag = false;
|
|
bool suspendFlag = false;
|
|
bool targetFlag = false;
|
|
bool reviveAvailable = false;
|
|
int workRegion[20][10] = { 0 };
|
|
Point point = { 0, 0 };
|
|
Point target = { 0, 0 };
|
|
MenuState menuState = { 0, 2 };
|
|
PlayerStats classicStats = { 0, 1, 0, 0, 0 };
|
|
PlayerStats rogueStats = { 0, 1, 0, 30, 0, 100, 100, 0 };
|
|
UpgradeUiState upgradeUiState = { 0, 0, 0, 0, {} };
|
|
FeedbackState feedbackState = { 0, _T(""), _T("") };
|
|
ClearEffectState clearEffectState = { 0, 0, 0, {} };
|
|
FloatingTextEffect floatingTextEffects[8] = {};
|
|
ParticleEffect particleEffects[96] = {};
|
|
int currentScreen = SCREEN_MENU;
|
|
int currentMode = MODE_CLASSIC;
|
|
int currentFallInterval = 500;
|
|
int nextTypes[3] = { 0, 0, 0 };
|
|
int holdType = -1;
|
|
bool holdUsedThisTurn = false;
|
|
bool currentPieceIsExplosive = false;
|
|
bool currentPieceIsLaser = false;
|
|
bool currentPieceIsCross = false;
|
|
bool currentPieceIsRainbow = false;
|
|
Point pendingChainBombCenter = { 0, 0 };
|
|
bool pendingChainBombFollowup = false;
|
|
static int pendingLineClearEffectTicks = 0;
|
|
static int pendingLineClearEffectRows[8] = {};
|
|
static int pendingLineClearEffectRowCount = 0;
|
|
static int pendingLineClearEffectLineCount = 0;
|
|
|
|
int bricks[7][4][4][4] =
|
|
{
|
|
{
|
|
{{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}},
|
|
{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}},
|
|
{{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}},
|
|
{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}}
|
|
},
|
|
{
|
|
{{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 2, 2, 2}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 0, 2, 2}, {0, 0, 2, 0}},
|
|
{{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 2, 2, 2}, {0, 0, 2, 0}},
|
|
{{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 2, 2, 0}, {0, 0, 2, 0}}
|
|
},
|
|
{
|
|
{{0, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 3, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {3, 3, 3, 0}, {3, 0, 0, 0}, {0, 0, 0, 0}},
|
|
{{3, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 3, 0}, {3, 3, 3, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}
|
|
},
|
|
{
|
|
{{0, 0, 4, 0}, {0, 0, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}},
|
|
{{0, 4, 0, 0}, {0, 4, 4, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 4, 4}, {0, 0, 4, 0}, {0, 0, 4, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 4, 4, 4}, {0, 0, 0, 4}, {0, 0, 0, 0}}
|
|
},
|
|
{
|
|
{{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}}
|
|
},
|
|
{
|
|
{{0, 6, 0, 0}, {0, 6, 6, 0}, {0, 0, 6, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 6, 6, 0}, {6, 6, 0, 0}, {0, 0, 0, 0}},
|
|
{{6, 0, 0, 0}, {6, 6, 0, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}},
|
|
{{0, 6, 6, 0}, {6, 6, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}
|
|
},
|
|
{
|
|
{{0, 0, 7, 0}, {0, 7, 7, 0}, {0, 7, 0, 0}, {0, 0, 0, 0}},
|
|
{{0, 7, 7, 0}, {0, 0, 7, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 7}, {0, 0, 7, 7}, {0, 0, 7, 0}, {0, 0, 0, 0}},
|
|
{{0, 0, 0, 0}, {0, 7, 7, 0}, {0, 0, 7, 7}, {0, 0, 0, 0}}
|
|
}
|
|
};
|
|
|
|
COLORREF BrickColor[7] =
|
|
{
|
|
RGB(244, 144, 165),
|
|
RGB(255, 181, 197),
|
|
RGB(170, 215, 255),
|
|
RGB(134, 230, 220),
|
|
RGB(255, 187, 143),
|
|
RGB(255, 223, 146),
|
|
RGB(197, 170, 255)
|
|
};
|
|
|
|
/**
|
|
* @brief 计算指定方块在指定旋转状态下的最小包围盒边界。
|
|
*
|
|
* 该函数会遍历 4x4 形状矩阵,找出所有非空单元的上下左右边界,
|
|
* 供后续统一计算生成位置和对齐方式时使用。
|
|
*
|
|
* @param brickType 方块类型编号。
|
|
* @param brickState 方块旋转状态编号。
|
|
* @param minRow 返回最上方非空行号。
|
|
* @param maxRow 返回最下方非空行号。
|
|
* @param minCol 返回最左侧非空列号。
|
|
* @param maxCol 返回最右侧非空列号。
|
|
*/
|
|
static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxRow, int& minCol, int& maxCol)
|
|
{
|
|
minRow = 4;
|
|
maxRow = -1;
|
|
minCol = 4;
|
|
maxCol = -1;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[brickType][brickState][i][j] != 0)
|
|
{
|
|
if (i < minRow)
|
|
{
|
|
minRow = i;
|
|
}
|
|
if (i > maxRow)
|
|
{
|
|
maxRow = i;
|
|
}
|
|
if (j < minCol)
|
|
{
|
|
minCol = j;
|
|
}
|
|
if (j > maxCol)
|
|
{
|
|
maxCol = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 计算指定方块的统一生成位置。
|
|
*
|
|
* 该函数会根据方块在初始旋转状态下的最小包围盒,
|
|
* 自动把方块水平居中到游戏区附近,并将顶部非空行对齐到可视区域顶部。
|
|
* 这样不同形状的方块在生成时看起来会更加统一。
|
|
*
|
|
* @param brickType 方块类型编号。
|
|
* @return Point 计算得到的生成坐标。
|
|
*/
|
|
Point GetSpawnPoint(int brickType)
|
|
{
|
|
int minRow, maxRow, minCol, maxCol;
|
|
GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol);
|
|
|
|
int brickWidth = maxCol - minCol + 1;
|
|
int brickHeight = maxRow - minRow + 1;
|
|
Point spawnPoint;
|
|
spawnPoint.x = (nGameWidth - brickWidth) / 2 - minCol;
|
|
spawnPoint.y = -brickHeight;
|
|
|
|
return spawnPoint;
|
|
}
|
|
|
|
void ResetPlayerStats(PlayerStats& stats, bool useRogueRules)
|
|
{
|
|
stats.score = 0;
|
|
stats.level = 1;
|
|
stats.exp = 0;
|
|
stats.requiredExp = useRogueRules ? 10 : 0;
|
|
stats.totalLinesCleared = 0;
|
|
stats.scoreMultiplierPercent = 100;
|
|
stats.expMultiplierPercent = 100;
|
|
stats.slowFallStacks = 0;
|
|
stats.comboBonusStacks = 0;
|
|
stats.comboChain = 0;
|
|
stats.previewCount = 1;
|
|
stats.lastChanceCount = 0;
|
|
stats.scoreUpgradeLevel = 0;
|
|
stats.expUpgradeLevel = 0;
|
|
stats.previewUpgradeLevel = 0;
|
|
stats.lastChanceUpgradeLevel = 0;
|
|
stats.holdUnlocked = 0;
|
|
stats.pressureReliefLevel = 0;
|
|
stats.sweeperLevel = 0;
|
|
stats.sweeperCharge = 0;
|
|
stats.explosiveLevel = 0;
|
|
stats.explosivePieceCounter = 0;
|
|
stats.chainBlastLevel = 0;
|
|
stats.chainBombLevel = 0;
|
|
stats.laserLevel = 0;
|
|
stats.thunderTetrisLevel = 0;
|
|
stats.thunderLaserLevel = 0;
|
|
stats.feverLevel = 0;
|
|
stats.rageStackLevel = 0;
|
|
stats.infiniteFeverLevel = 0;
|
|
stats.feverLineCharge = 0;
|
|
stats.feverTicks = 0;
|
|
stats.screenBombLevel = 0;
|
|
stats.screenBombCharge = 0;
|
|
stats.screenBombCount = 0;
|
|
stats.terminalClearLevel = 0;
|
|
stats.dualChoiceLevel = 0;
|
|
stats.destinyWheelLevel = 0;
|
|
stats.perfectRotateLevel = 0;
|
|
stats.timeDilationLevel = 0;
|
|
stats.timeDilationTicks = 0;
|
|
stats.highPressureLevel = 0;
|
|
stats.tetrisGambleLevel = 0;
|
|
stats.extremePlayerLevel = 0;
|
|
stats.extremeSlowTicks = 0;
|
|
stats.extremeDangerTicks = 30;
|
|
stats.extremeDangerLevel = 0;
|
|
stats.upgradeShockwaveLevel = 0;
|
|
stats.evolutionImpactLevel = 0;
|
|
stats.controlMasterLevel = 0;
|
|
stats.holdSlowTicks = 0;
|
|
stats.blockStormLevel = 0;
|
|
stats.blockStormPiecesRemaining = 0;
|
|
stats.blackHoleLevel = 0;
|
|
stats.blackHoleCharges = 0;
|
|
stats.reshapeLevel = 0;
|
|
stats.reshapeCharges = 0;
|
|
stats.rainbowPieceLevel = 0;
|
|
stats.voidCoreLevel = 0;
|
|
stats.pendingRainbowPieceCount = 0;
|
|
stats.stableStructureLevel = 0;
|
|
stats.doubleGrowthLevel = 0;
|
|
stats.gamblerLevel = 0;
|
|
stats.difficultyElapsedMs = 0;
|
|
stats.difficultyLevel = 0;
|
|
stats.lockedRows = 0;
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
stats.pieceTuningLevels[i] = 0;
|
|
}
|
|
}
|
|
|
|
void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks)
|
|
{
|
|
feedbackState.visibleTicks = ticks;
|
|
lstrcpyn(feedbackState.title, title, sizeof(feedbackState.title) / sizeof(TCHAR));
|
|
lstrcpyn(feedbackState.detail, detail, sizeof(feedbackState.detail) / sizeof(TCHAR));
|
|
}
|
|
|
|
void ResetVisualEffects()
|
|
{
|
|
clearEffectState.ticks = 0;
|
|
clearEffectState.totalTicks = 0;
|
|
clearEffectState.rowCount = 0;
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
floatingTextEffects[i].ticks = 0;
|
|
}
|
|
|
|
for (int i = 0; i < 96; i++)
|
|
{
|
|
particleEffects[i].ticks = 0;
|
|
}
|
|
}
|
|
|
|
bool TickVisualEffects()
|
|
{
|
|
bool active = false;
|
|
|
|
if (clearEffectState.ticks > 0)
|
|
{
|
|
clearEffectState.ticks--;
|
|
active = true;
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (floatingTextEffects[i].ticks > 0)
|
|
{
|
|
floatingTextEffects[i].ticks--;
|
|
active = true;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 96; i++)
|
|
{
|
|
if (particleEffects[i].ticks > 0)
|
|
{
|
|
particleEffects[i].ticks--;
|
|
active = true;
|
|
}
|
|
}
|
|
|
|
return active;
|
|
}
|
|
|
|
static void AddFloatingText(int boardX, int boardY, const TCHAR* text, COLORREF color)
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (floatingTextEffects[i].ticks <= 0)
|
|
{
|
|
floatingTextEffects[i].ticks = 22;
|
|
floatingTextEffects[i].totalTicks = 22;
|
|
floatingTextEffects[i].boardX = boardX;
|
|
floatingTextEffects[i].boardY = boardY;
|
|
floatingTextEffects[i].color = color;
|
|
lstrcpyn(floatingTextEffects[i].text, text, sizeof(floatingTextEffects[i].text) / sizeof(TCHAR));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void AddParticle(int boardX, int boardY, int velocityX, int velocityY, int size, COLORREF color)
|
|
{
|
|
for (int i = 0; i < 96; i++)
|
|
{
|
|
if (particleEffects[i].ticks <= 0)
|
|
{
|
|
particleEffects[i].ticks = 12 + rand() % 7;
|
|
particleEffects[i].totalTicks = particleEffects[i].ticks;
|
|
particleEffects[i].boardX = boardX;
|
|
particleEffects[i].boardY = boardY;
|
|
particleEffects[i].velocityX = velocityX;
|
|
particleEffects[i].velocityY = velocityY;
|
|
particleEffects[i].size = size;
|
|
particleEffects[i].color = color;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void AddBurstParticles(int boardX, int boardY, COLORREF baseColor, bool strongBurst)
|
|
{
|
|
int burstCount = strongBurst ? 5 : 3;
|
|
for (int i = 0; i < burstCount; i++)
|
|
{
|
|
int angleSeed = rand() % 8;
|
|
int speed = strongBurst ? (9 + rand() % 9) : (6 + rand() % 7);
|
|
int velocityX = 0;
|
|
int velocityY = 0;
|
|
|
|
switch (angleSeed)
|
|
{
|
|
case 0:
|
|
velocityX = speed;
|
|
velocityY = -rand() % 4;
|
|
break;
|
|
case 1:
|
|
velocityX = -speed;
|
|
velocityY = -rand() % 4;
|
|
break;
|
|
case 2:
|
|
velocityX = (rand() % 5) - 2;
|
|
velocityY = -speed;
|
|
break;
|
|
case 3:
|
|
velocityX = (rand() % 5) - 2;
|
|
velocityY = speed / 2;
|
|
break;
|
|
case 4:
|
|
velocityX = speed;
|
|
velocityY = -speed;
|
|
break;
|
|
case 5:
|
|
velocityX = -speed;
|
|
velocityY = -speed;
|
|
break;
|
|
case 6:
|
|
velocityX = speed;
|
|
velocityY = speed / 3;
|
|
break;
|
|
default:
|
|
velocityX = -speed;
|
|
velocityY = speed / 3;
|
|
break;
|
|
}
|
|
|
|
velocityX += (rand() % 7) - 3;
|
|
velocityY += (rand() % 7) - 3;
|
|
|
|
COLORREF color = (i % 3 == 0) ? RGB(255, 248, 220) : baseColor;
|
|
AddParticle(
|
|
boardX + (rand() % 31) - 15,
|
|
boardY + (rand() % 31) - 15,
|
|
velocityX,
|
|
velocityY,
|
|
strongBurst ? (4 + rand() % 5) : (3 + rand() % 4),
|
|
color);
|
|
}
|
|
}
|
|
|
|
static void QueueLineClearEffect(const int* rows, int rowCount, int linesCleared)
|
|
{
|
|
if (rows == nullptr || rowCount <= 0 || linesCleared <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (rowCount > 8)
|
|
{
|
|
rowCount = 8;
|
|
}
|
|
|
|
pendingLineClearEffectTicks = 1;
|
|
pendingLineClearEffectRowCount = rowCount;
|
|
pendingLineClearEffectLineCount = linesCleared;
|
|
for (int i = 0; i < rowCount; i++)
|
|
{
|
|
pendingLineClearEffectRows[i] = rows[i];
|
|
}
|
|
}
|
|
|
|
void PlayPendingLineClearEffect()
|
|
{
|
|
if (pendingLineClearEffectTicks <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pendingLineClearEffectTicks = 0;
|
|
TriggerLineClearEffect(
|
|
pendingLineClearEffectRows,
|
|
pendingLineClearEffectRowCount,
|
|
pendingLineClearEffectLineCount);
|
|
pendingLineClearEffectRowCount = 0;
|
|
pendingLineClearEffectLineCount = 0;
|
|
}
|
|
|
|
void TriggerLineClearEffect(const int* rows, int rowCount, int linesCleared)
|
|
{
|
|
if (rows == nullptr || rowCount <= 0 || linesCleared <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (rowCount > 8)
|
|
{
|
|
rowCount = 8;
|
|
}
|
|
|
|
clearEffectState.ticks = 16;
|
|
clearEffectState.totalTicks = 16;
|
|
clearEffectState.rowCount = rowCount;
|
|
|
|
int rowSum = 0;
|
|
for (int i = 0; i < rowCount; i++)
|
|
{
|
|
clearEffectState.rows[i] = rows[i];
|
|
rowSum += rows[i];
|
|
for (int x = 0; x < nGameWidth; x++)
|
|
{
|
|
COLORREF particleColor = BrickColor[(x + rows[i]) % 7];
|
|
int centerX = x * 100 + 50;
|
|
int centerY = rows[i] * 100 + 50;
|
|
AddBurstParticles(centerX, centerY, particleColor, linesCleared >= 4);
|
|
|
|
if (linesCleared >= 4)
|
|
{
|
|
AddParticle(
|
|
centerX,
|
|
centerY,
|
|
((x < nGameWidth / 2) ? -1 : 1) * (16 + rand() % 12),
|
|
-16 - rand() % 10,
|
|
4 + rand() % 3,
|
|
RGB(255, 238, 120));
|
|
}
|
|
}
|
|
}
|
|
|
|
TCHAR text[64];
|
|
if (linesCleared >= 4)
|
|
{
|
|
_stprintf_s(text, _T("TETRIS"));
|
|
}
|
|
else
|
|
{
|
|
_stprintf_s(text, _T("%d LINE%s"), linesCleared, linesCleared > 1 ? _T("S") : _T(""));
|
|
}
|
|
AddFloatingText(nGameWidth * 50, (rowSum * 100 / rowCount) - 20, text, linesCleared >= 4 ? RGB(255, 232, 120) : RGB(255, 250, 252));
|
|
}
|
|
|
|
void TriggerCellClearEffect(const Point* cells, int cellCount, bool strongBurst)
|
|
{
|
|
if (cells == nullptr || cellCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < cellCount; i++)
|
|
{
|
|
if (cells[i].x < 0 || cells[i].x >= nGameWidth || cells[i].y < 0 || cells[i].y >= nGameHeight)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
COLORREF particleColor = BrickColor[(cells[i].x + cells[i].y) % 7];
|
|
AddBurstParticles(cells[i].x * 100 + 50, cells[i].y * 100 + 50, particleColor, strongBurst);
|
|
}
|
|
}
|
|
|
|
bool IsPiecePlacementValid(int pieceType, int pieceState, Point position)
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[pieceType][pieceState][i][j] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int checkY = position.y + i;
|
|
int checkX = position.x + j;
|
|
|
|
if (checkX < 0 || checkX >= nGameWidth || checkY >= GetRoguePlayableHeight())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (checkY >= 0 && workRegion[checkY][checkX] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 判断当前方块是否可以继续向下移动。
|
|
*
|
|
* 遍历当前处于活动状态下方块的 4x4 矩阵,计算其下落一步(Y 坐标加 1)后的位置,
|
|
* 并检查每个非空方块单元:
|
|
* 1. 是否超出了游戏工作区的底部边界(对应数组索引 >= 20)。
|
|
* 2. 是否与工作区下方已经固定的其他方块发生碰撞(即对应位置的值不为 0)。
|
|
* 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续下落。
|
|
*
|
|
* @return bool 如果可以继续安全下落返回 true,否则返回 false。
|
|
*/
|
|
bool CanMoveDown()
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[type][state][i][j] != 0)
|
|
{
|
|
int nextY = point.y + i + 1;
|
|
int nextX = point.x + j;
|
|
|
|
// 检查是否到达底部边界
|
|
if (nextY >= GetRoguePlayableHeight())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 检查下方是否有其他固定方块
|
|
if (nextY >= 0 && workRegion[nextY][nextX] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 判断当前方块是否可以继续向左移动。
|
|
*
|
|
* 遍历当前处于活动状态下方块的 4x4 矩阵,计算其向左移动一步(X 坐标减 1)后的位置,
|
|
* 并检查每个非空方块单元:
|
|
* 1. 是否超出了游戏工作区的左侧边界(对应数组索引 < 0)。
|
|
* 2. 是否与工作区左侧已经固定的其他方块发生碰撞(即对应位置的值不为 0)。
|
|
* 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续左移。
|
|
*
|
|
* @return bool 如果可以继续安全左移返回 true,否则返回 false。
|
|
*/
|
|
bool CanMoveLeft()
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[type][state][i][j] != 0)
|
|
{
|
|
int nextY = point.y + i;
|
|
int nextX = point.x + j - 1;
|
|
|
|
// 检查是否到达左侧边界
|
|
if (nextX < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 检查左侧是否有其他固定方块
|
|
if (nextY >= 0 && workRegion[nextY][nextX] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 判断当前方块是否可以继续向右移动。
|
|
*
|
|
* 遍历当前处于活动状态下方块的 4x4 矩阵,计算其向右移动一步(X 坐标加 1)后的位置,
|
|
* 并检查每个非空方块单元:
|
|
* 1. 是否超出了游戏工作区的右侧边界(对应数组索引 >= 10)。
|
|
* 2. 是否与工作区右侧已经固定的其他方块发生碰撞(即对应位置的值不为 0)。
|
|
* 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续右移。
|
|
*
|
|
* @return bool 如果可以继续安全右移返回 true,否则返回 false。
|
|
*/
|
|
bool CanMoveRight()
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[type][state][i][j] != 0)
|
|
{
|
|
int nextY = point.y + i;
|
|
int nextX = point.x + j + 1;
|
|
|
|
// 检查是否到达右侧边界
|
|
if (nextX >= nGameWidth)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 检查右侧是否有其他固定方块
|
|
if (nextY >= 0 && workRegion[nextY][nextX] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TryRotateWithOffset(int nextState, int offsetX)
|
|
{
|
|
Point rotatedPoint = point;
|
|
rotatedPoint.x += offsetX;
|
|
return IsPiecePlacementValid(type, nextState, rotatedPoint);
|
|
}
|
|
|
|
/**
|
|
* @brief 将当前活动方块向下移动一格。
|
|
*
|
|
* 该函数只负责修改当前活动方块的纵坐标,将其在工作区中的位置向下推进 1 格。
|
|
* 是否允许下移由外部配合 CanMoveDown 函数提前判断。
|
|
*/
|
|
void MoveDown()
|
|
{
|
|
// 当前方块下移一格
|
|
point.y++;
|
|
}
|
|
|
|
/**
|
|
* @brief 将当前活动方块向左移动一格。
|
|
*
|
|
* 该函数只负责修改当前活动方块的横坐标,将其在工作区中的位置向左推进 1 格。
|
|
* 是否允许左移由外部配合 CanMoveLeft 函数提前判断。
|
|
*/
|
|
void MoveLeft()
|
|
{
|
|
// 当前方块左移一格
|
|
point.x--;
|
|
}
|
|
|
|
/**
|
|
* @brief 将当前活动方块向右移动一格。
|
|
*
|
|
* 该函数只负责修改当前活动方块的横坐标,将其在工作区中的位置向右推进 1 格。
|
|
* 是否允许右移由外部配合 CanMoveRight 函数提前判断。
|
|
*/
|
|
void MoveRight()
|
|
{
|
|
// 当前方块右移一格
|
|
point.x++;
|
|
}
|
|
|
|
/**
|
|
* @brief 旋转当前活动方块到下一种朝向。
|
|
*
|
|
* 游戏中的每种方块都预置了 4 种旋转状态,该函数会先尝试切换到下一状态,
|
|
* 然后检查旋转后的方块是否越界或与固定方块重叠。
|
|
* 如果旋转后的状态非法,则恢复到旋转前的状态。
|
|
*/
|
|
void Rotate()
|
|
{
|
|
int nextState = (state + 1) % 4;
|
|
if (IsPiecePlacementValid(type, nextState, point))
|
|
{
|
|
state = nextState;
|
|
return;
|
|
}
|
|
|
|
if (currentMode == MODE_ROGUE && rogueStats.perfectRotateLevel > 0)
|
|
{
|
|
if (TryRotateWithOffset(nextState, -1))
|
|
{
|
|
state = nextState;
|
|
point.x--;
|
|
return;
|
|
}
|
|
|
|
if (TryRotateWithOffset(nextState, 1))
|
|
{
|
|
state = nextState;
|
|
point.x++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 让当前活动方块快速下落到当前位置能够到达的最低点。
|
|
*
|
|
* 该函数会持续检查当前方块是否还能继续下移,只要可以下移就重复调用 MoveDown,
|
|
* 直到方块到达底部或被其他固定方块阻挡为止。
|
|
*/
|
|
void DropDown()
|
|
{
|
|
// 只要还能继续下落,就不断下移
|
|
while (CanMoveDown())
|
|
{
|
|
MoveDown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 将当前活动方块固定到工作区,并生成下一个活动方块。
|
|
*
|
|
* 遍历当前方块 4x4 形状矩阵,把其中所有非空单元写入工作区数组,
|
|
* 表示该方块已经落地并转为固定状态。
|
|
* 如果固定时仍有任意非空单元位于可视区域顶部之外,则判定游戏结束。
|
|
* 此时当前方块在可视区域内的部分仍会保留在工作区中。
|
|
* 若未超出顶部,再将“下一方块”切换为新的当前方块,重置旋转状态,
|
|
* 并把新方块生成到工作区上方的初始位置,同时刷新预测落点。
|
|
*/
|
|
void Fixing()
|
|
{
|
|
bool overflowTop = false;
|
|
Point explosiveCells[4] = {};
|
|
int explosiveCellCount = 0;
|
|
int rainbowFilledCount = 0;
|
|
pendingChainBombFollowup = false;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[type][state][i][j] != 0)
|
|
{
|
|
int fixY = point.y + i;
|
|
int fixX = point.x + j;
|
|
|
|
// 只要当前方块任意非空单元仍超出顶部,就标记为结束
|
|
if (fixY < 0)
|
|
{
|
|
overflowTop = true;
|
|
}
|
|
|
|
// 将当前方块在可视区域内的部分写入工作区
|
|
if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth)
|
|
{
|
|
workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j];
|
|
if (currentPieceIsExplosive && explosiveCellCount < 4)
|
|
{
|
|
explosiveCells[explosiveCellCount].x = fixX;
|
|
explosiveCells[explosiveCellCount].y = fixY;
|
|
explosiveCellCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!overflowTop && currentPieceIsRainbow)
|
|
{
|
|
rainbowFilledCount = TriggerRainbowRowCompletion(point.y, point.y + 3);
|
|
if (rainbowFilledCount > 0)
|
|
{
|
|
TCHAR rainbowDetail[128];
|
|
_stprintf_s(rainbowDetail, _T("彩虹能量补齐 %d 个缺口,消行机会扩大。"), rainbowFilledCount);
|
|
SetFeedbackMessage(_T("彩虹方块"), rainbowDetail, 10);
|
|
}
|
|
}
|
|
|
|
if (overflowTop)
|
|
{
|
|
if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0)
|
|
{
|
|
rogueStats.lastChanceCount--;
|
|
rogueStats.screenBombCount--;
|
|
|
|
int clearedByTerminal = TriggerScreenBomb();
|
|
rogueStats.feverTicks = 10;
|
|
currentFallInterval = GetRogueFallInterval();
|
|
|
|
TCHAR terminalDetail[128];
|
|
_stprintf_s(
|
|
terminalDetail,
|
|
_T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"),
|
|
clearedByTerminal);
|
|
SetFeedbackMessage(_T("终末清场"), terminalDetail, 14);
|
|
}
|
|
else if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0)
|
|
{
|
|
rogueStats.lastChanceCount--;
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
DeleteOneLine(GetRoguePlayableHeight() - 1);
|
|
}
|
|
|
|
SetFeedbackMessage(
|
|
_T("最后一搏"),
|
|
_T("底部 3 行被清除,战局得以延续。"),
|
|
14);
|
|
}
|
|
else
|
|
{
|
|
gameOverFlag = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentPieceIsExplosive)
|
|
{
|
|
int explosiveCellsCleared = 0;
|
|
|
|
for (int i = 0; i < explosiveCellCount; i++)
|
|
{
|
|
explosiveCellsCleared += ClearExplosiveAreaAt(explosiveCells[i].y, explosiveCells[i].x);
|
|
}
|
|
|
|
int explosiveScoreGain = 0;
|
|
int explosiveExpGain = 0;
|
|
if (currentMode == MODE_ROGUE && explosiveCellsCleared > 0)
|
|
{
|
|
AwardRogueSkillClearRewards(explosiveCellsCleared, explosiveScoreGain, explosiveExpGain, false);
|
|
ApplyBoardGravity();
|
|
}
|
|
|
|
TCHAR explosiveDetail[128];
|
|
_stprintf_s(
|
|
explosiveDetail,
|
|
_T("爆破清除 %d 格 +%d 分 +%d EXP"),
|
|
explosiveCellsCleared,
|
|
explosiveScoreGain,
|
|
explosiveExpGain);
|
|
SetFeedbackMessage(_T("爆破核心"), explosiveDetail, 12);
|
|
|
|
if (rogueStats.chainBombLevel > 0 && explosiveCellCount > 0)
|
|
{
|
|
pendingChainBombCenter = explosiveCells[0];
|
|
pendingChainBombFollowup = true;
|
|
}
|
|
}
|
|
|
|
if (currentPieceIsLaser)
|
|
{
|
|
int laserColumn = point.x + 1;
|
|
if (laserColumn < 0)
|
|
{
|
|
laserColumn = 0;
|
|
}
|
|
if (laserColumn >= nGameWidth)
|
|
{
|
|
laserColumn = nGameWidth - 1;
|
|
}
|
|
|
|
int laserCellsCleared = ClearColumnAt(laserColumn);
|
|
if (currentMode == MODE_ROGUE && laserCellsCleared > 0)
|
|
{
|
|
int laserScore = 0;
|
|
int laserExp = 0;
|
|
AwardRogueSkillClearRewards(laserCellsCleared, laserScore, laserExp, false);
|
|
ApplyBoardGravity();
|
|
|
|
TCHAR laserDetail[128];
|
|
_stprintf_s(laserDetail, _T("激光贯穿一列,清除 %d 格 +%d 分 +%d EXP"), laserCellsCleared, laserScore, laserExp);
|
|
SetFeedbackMessage(_T("棱镜激光"), laserDetail, 12);
|
|
}
|
|
}
|
|
|
|
if (currentPieceIsCross)
|
|
{
|
|
int crossRow = point.y + 1;
|
|
int crossColumn = point.x + 1;
|
|
if (crossRow < 0)
|
|
{
|
|
crossRow = 0;
|
|
}
|
|
if (crossRow >= GetRoguePlayableHeight())
|
|
{
|
|
crossRow = GetRoguePlayableHeight() - 1;
|
|
}
|
|
if (crossColumn < 0)
|
|
{
|
|
crossColumn = 0;
|
|
}
|
|
if (crossColumn >= nGameWidth)
|
|
{
|
|
crossColumn = nGameWidth - 1;
|
|
}
|
|
|
|
int crossCellsCleared = ClearRowAt(crossRow);
|
|
int columnCellsCleared = ClearColumnAt(crossColumn);
|
|
if (workRegion[crossRow][crossColumn] == 0 && columnCellsCleared > 0)
|
|
{
|
|
// center cell may already be counted by row clear
|
|
}
|
|
int totalCrossCleared = crossCellsCleared + columnCellsCleared;
|
|
|
|
if (currentMode == MODE_ROGUE && totalCrossCleared > 0)
|
|
{
|
|
int crossScore = 0;
|
|
int crossExp = 0;
|
|
AwardRogueSkillClearRewards(totalCrossCleared, crossScore, crossExp, false);
|
|
ApplyBoardGravity();
|
|
|
|
TCHAR crossDetail[128];
|
|
_stprintf_s(crossDetail, _T("十字冲击清除 %d 格 +%d 分 +%d EXP"), totalCrossCleared, crossScore, crossExp);
|
|
SetFeedbackMessage(_T("十字方块"), crossDetail, 12);
|
|
}
|
|
}
|
|
|
|
if (TryStabilizeBoard() > 0)
|
|
{
|
|
SetFeedbackMessage(_T("稳定结构"), _T("附近空洞被自动填补,阵型更加稳固。"), 10);
|
|
}
|
|
|
|
if (currentMode == MODE_ROGUE)
|
|
{
|
|
currentFallInterval = GetRogueFallInterval();
|
|
}
|
|
|
|
// 生成下一个活动方块
|
|
type = ConsumeNextType();
|
|
nType = nextTypes[0];
|
|
state = 0;
|
|
holdUsedThisTurn = false;
|
|
RollCurrentPieceSpecialFlags(true);
|
|
point = GetSpawnPoint(type);
|
|
target = point;
|
|
ComputeTarget();
|
|
}
|
|
|
|
/**
|
|
* @brief 删除指定行,并让其上方所有行整体下移一格。
|
|
*
|
|
* 该函数会先将目标行上方的所有数据逐行向下复制,
|
|
* 再把最顶端一行清空,从而完成一次标准的消行下移操作。
|
|
*
|
|
* @param number 需要被删除的目标行号。
|
|
*/
|
|
void DeleteOneLine(int number)
|
|
{
|
|
for (int i = number; i > 0; i--)
|
|
{
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
workRegion[i][j] = workRegion[i - 1][j];
|
|
}
|
|
}
|
|
|
|
// 清空最顶端一行
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
workRegion[0][j] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 检查并删除所有已满的行,同时更新当前得分。
|
|
*
|
|
* 该函数会从底部向上遍历工作区,判断每一行是否被完全填满。
|
|
* 如果某一行全部非 0,则调用 DeleteOneLine 删除该行,
|
|
* 并将该行上方的内容整体下移。为了避免连续满行被漏检,
|
|
* 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。
|
|
*/
|
|
int DeleteLines()
|
|
{
|
|
int clearedLines = 0;
|
|
bool clearedWithRainbow = false;
|
|
int clearedRows[8] = {};
|
|
int clearedRowCount = 0;
|
|
|
|
int playableHeight = GetRoguePlayableHeight();
|
|
for (int i = playableHeight - 1; i >= 0; i--)
|
|
{
|
|
bool fullLine = true;
|
|
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
if (workRegion[i][j] == 0)
|
|
{
|
|
fullLine = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fullLine)
|
|
{
|
|
if (clearedRowCount < 8)
|
|
{
|
|
clearedRows[clearedRowCount] = i;
|
|
clearedRowCount++;
|
|
}
|
|
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
if (IsRainbowBoardCell(workRegion[i][j]))
|
|
{
|
|
clearedWithRainbow = true;
|
|
break;
|
|
}
|
|
}
|
|
DeleteOneLine(i);
|
|
clearedLines++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
ApplyLineClearResult(clearedLines);
|
|
if (currentScreen == SCREEN_UPGRADE)
|
|
{
|
|
QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines);
|
|
}
|
|
else
|
|
{
|
|
TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines);
|
|
}
|
|
|
|
if (pendingChainBombFollowup && clearedLines > 0)
|
|
{
|
|
pendingChainBombFollowup = false;
|
|
|
|
int followupCleared = 0;
|
|
int centerY = pendingChainBombCenter.y;
|
|
int centerX = pendingChainBombCenter.x;
|
|
Point followupCells[9] = {};
|
|
|
|
for (int y = centerY - 1; y <= centerY + 1; y++)
|
|
{
|
|
for (int x = centerX - 1; x <= centerX + 1; x++)
|
|
{
|
|
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0)
|
|
{
|
|
if (followupCleared < 9)
|
|
{
|
|
followupCells[followupCleared].x = x;
|
|
followupCells[followupCleared].y = y;
|
|
}
|
|
workRegion[y][x] = 0;
|
|
followupCleared++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentMode == MODE_ROGUE && followupCleared > 0)
|
|
{
|
|
TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true);
|
|
int followupScore = 0;
|
|
int followupExp = 0;
|
|
AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false);
|
|
ApplyBoardGravity();
|
|
|
|
TCHAR followupDetail[128];
|
|
_stprintf_s(
|
|
followupDetail,
|
|
_T("追加爆炸清除 %d 格 +%d 分 +%d EXP"),
|
|
followupCleared,
|
|
followupScore,
|
|
followupExp);
|
|
SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pendingChainBombFollowup = false;
|
|
}
|
|
|
|
if (currentMode == MODE_ROGUE && clearedWithRainbow && rogueStats.voidCoreLevel > 0)
|
|
{
|
|
int miniBlackHoleCleared = TriggerMiniBlackHole(5);
|
|
if (miniBlackHoleCleared > 0)
|
|
{
|
|
int miniScore = 0;
|
|
int miniExp = 0;
|
|
AwardRogueSkillClearRewards(miniBlackHoleCleared, miniScore, miniExp, false);
|
|
ApplyBoardGravity();
|
|
|
|
TCHAR miniDetail[128];
|
|
_stprintf_s(miniDetail, _T("彩虹消行撕开小型黑洞,清除 %d 格 +%d 分 +%d EXP"), miniBlackHoleCleared, miniScore, miniExp);
|
|
SetFeedbackMessage(_T("虚空核心"), miniDetail, 12);
|
|
}
|
|
}
|
|
|
|
return clearedLines;
|
|
}
|
|
|
|
/**
|
|
* @brief 计算当前活动方块的预测落点位置。
|
|
*
|
|
* 该函数以当前活动方块的位置为起点,使用虚拟下落的方式不断尝试向下移动,
|
|
* 直到方块无法继续下落为止。最终得到的最低可达位置会写入 target,
|
|
* 供后续界面绘制瞄准器或落点提示时使用。
|
|
*
|
|
* 计算过程中不会改变当前方块的真实位置 point。
|
|
*/
|
|
void ComputeTarget()
|
|
{
|
|
Point originalPoint = point;
|
|
|
|
// 从当前方块位置开始向下试探
|
|
target = point;
|
|
|
|
while (CanMoveDown())
|
|
{
|
|
point.y++;
|
|
target = point;
|
|
}
|
|
|
|
// 恢复当前方块的真实位置
|
|
point = originalPoint;
|
|
}
|
|
|
|
/**
|
|
* @brief 重置整个游戏状态,开始新的一局。
|
|
*
|
|
* 该函数会清空工作区中的所有固定方块数据,重置分数、结束标记和暂停标记,
|
|
* 并重新初始化当前方块、下一方块、旋转状态以及生成位置。
|
|
* 最后会重新计算一次当前方块的预测落点。
|
|
*/
|
|
void Restart()
|
|
{
|
|
for (int i = 0; i < nGameHeight; i++)
|
|
{
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
workRegion[i][j] = 0;
|
|
}
|
|
}
|
|
|
|
gameOverFlag = false;
|
|
suspendFlag = false;
|
|
targetFlag = true;
|
|
reviveAvailable = true;
|
|
currentFallInterval = 500;
|
|
|
|
ResetPlayerStats(classicStats, false);
|
|
ResetPlayerStats(rogueStats, true);
|
|
upgradeUiState.selectedIndex = 0;
|
|
upgradeUiState.optionCount = 0;
|
|
upgradeUiState.pendingCount = 0;
|
|
upgradeUiState.totalChosenCount = 0;
|
|
upgradeUiState.picksRemaining = 0;
|
|
feedbackState.visibleTicks = 0;
|
|
feedbackState.title[0] = _T('\0');
|
|
feedbackState.detail[0] = _T('\0');
|
|
ResetPendingRogueVisualEvents();
|
|
ResetVisualEffects();
|
|
pendingLineClearEffectTicks = 0;
|
|
pendingLineClearEffectRowCount = 0;
|
|
pendingLineClearEffectLineCount = 0;
|
|
holdType = -1;
|
|
holdUsedThisTurn = false;
|
|
RollCurrentPieceSpecialFlags(false);
|
|
tScore = 0;
|
|
|
|
ResetNextQueue();
|
|
type = ConsumeNextType();
|
|
nType = nextTypes[0];
|
|
state = 0;
|
|
holdUsedThisTurn = false;
|
|
RollCurrentPieceSpecialFlags(true);
|
|
point = GetSpawnPoint(type);
|
|
target = point;
|
|
|
|
ComputeTarget();
|
|
}
|
|
|
|
void ReviveAfterVideo()
|
|
{
|
|
if (!gameOverFlag || !reviveAvailable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
reviveAvailable = false;
|
|
gameOverFlag = false;
|
|
suspendFlag = false;
|
|
currentScreen = SCREEN_PLAYING;
|
|
|
|
int playableHeight = GetRoguePlayableHeight();
|
|
int rowsToClear = playableHeight / 3;
|
|
if (rowsToClear < 5)
|
|
{
|
|
rowsToClear = 5;
|
|
}
|
|
|
|
for (int y = 0; y < rowsToClear && y < playableHeight; y++)
|
|
{
|
|
for (int x = 0; x < nGameWidth; x++)
|
|
{
|
|
workRegion[y][x] = 0;
|
|
}
|
|
}
|
|
|
|
type = ConsumeNextType();
|
|
nType = nextTypes[0];
|
|
state = 0;
|
|
holdUsedThisTurn = false;
|
|
RollCurrentPieceSpecialFlags(true);
|
|
point = GetSpawnPoint(type);
|
|
target = point;
|
|
ComputeTarget();
|
|
|
|
SetFeedbackMessage(_T("复活成功"), _T("已清理顶部空间,本局复活机会已用完。"), 14);
|
|
}
|
|
|
|
void StartGameWithMode(int mode)
|
|
{
|
|
currentMode = mode;
|
|
currentScreen = SCREEN_PLAYING;
|
|
Restart();
|
|
currentFallInterval = (currentMode == MODE_ROGUE) ? GetRogueFallInterval() : 500;
|
|
tScore = (currentMode == MODE_CLASSIC) ? classicStats.score : rogueStats.score;
|
|
}
|
|
|
|
void ReturnToMainMenu()
|
|
{
|
|
currentScreen = SCREEN_MENU;
|
|
suspendFlag = false;
|
|
gameOverFlag = false;
|
|
ResetVisualEffects();
|
|
ResetPendingRogueVisualEvents();
|
|
pendingLineClearEffectTicks = 0;
|
|
pendingLineClearEffectRowCount = 0;
|
|
pendingLineClearEffectLineCount = 0;
|
|
menuState.optionCount = 3;
|
|
upgradeUiState.pendingCount = 0;
|
|
upgradeUiState.picksRemaining = 0;
|
|
|
|
if (menuState.selectedIndex < 0 || menuState.selectedIndex >= menuState.optionCount)
|
|
{
|
|
menuState.selectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
void OpenRulesScreen()
|
|
{
|
|
currentScreen = SCREEN_RULES;
|
|
suspendFlag = false;
|
|
}
|