13 : playLineSound(std::move(lineSoundCallback)) {
18 std::srand(
static_cast<unsigned int>(std::time(
nullptr)));
22 timeout = difficulty == 0 ? 1200U : difficulty == 1 ? 900U
31 level = std::clamp((1200 -
static_cast<int>(
timeout)) / 100, 0, 10);
32 if (clearVisualRun()) {
36 for (
int x = 0; x < focusGrid.width(); ++x) {
37 for (
int y = 0; y < focusGrid.height(); ++y) {
38 if (clearRun(focusGrid, x, y, 0, 1) ||
39 clearRun(focusGrid, x, y, 1, 0) ||
40 clearRun(focusGrid, x, y, 1, 1) ||
41 clearRun(focusGrid, x, y, -1, 1)) {
47 clearTopBottomSeams();
51 Block *PuzzleGame::blockAt(
const CellRef &cell) {
52 if (cell.gridIndex < 0 || cell.gridIndex >=
static_cast<int>(
grid.size())) {
55 return grid[
static_cast<std::size_t
>(cell.gridIndex)].at(cell.x, cell.y);
58 const Block *PuzzleGame::blockAt(
const CellRef &cell)
const {
59 if (cell.gridIndex < 0 || cell.gridIndex >=
static_cast<int>(
grid.size())) {
62 return grid[
static_cast<std::size_t
>(cell.gridIndex)].at(cell.x, cell.y);
65 PuzzleGame::CellBounds PuzzleGame::cellBounds(
const CellRef &cell) {
66 if (cell.gridIndex == 0) {
69 if (cell.gridIndex == 1) {
72 if (cell.gridIndex == 2) {
84 bool PuzzleGame::sameCell(
const CellRef &a,
const CellRef &b)
const {
85 return a.gridIndex == b.gridIndex && a.x == b.x && a.y == b.y;
88 bool PuzzleGame::findNextVisualMatch(
const CellRef ¤t,
int color,
int dx,
int dy, CellRef &next)
const {
89 const CellBounds currentBounds = cellBounds(current);
90 const int currentCenterX2 = currentBounds.x * 2 + currentBounds.width;
91 const int currentCenterY2 = currentBounds.y * 2 + currentBounds.height;
93 int bestError = TOLERANCE2 * TOLERANCE2 * 4 + 1;
96 for (
int gridIndex = 0; gridIndex < static_cast<int>(
grid.size()); ++gridIndex) {
97 const GameGrid &focusGrid =
grid[
static_cast<std::size_t
>(gridIndex)];
98 for (
int y = 0; y < focusGrid.height(); ++y) {
99 for (
int x = 0; x < focusGrid.width(); ++x) {
100 const CellRef candidate{gridIndex, x, y};
101 if (sameCell(current, candidate)) {
104 const Block *block = blockAt(candidate);
105 if (block ==
nullptr || block->color != color) {
109 const CellBounds candidateBounds = cellBounds(candidate);
110 const int candidateCenterX2 = candidateBounds.x * 2 + candidateBounds.width;
111 const int candidateCenterY2 = candidateBounds.y * 2 + candidateBounds.height;
112 const int expectedDeltaX2 = dx == 0 ? 0 : dx * (currentBounds.width + candidateBounds.width);
113 const int expectedDeltaY2 = dy == 0 ? 0 : dy * (currentBounds.height + candidateBounds.height);
114 const int errorX = std::abs((candidateCenterX2 - currentCenterX2) - expectedDeltaX2);
115 const int errorY = std::abs((candidateCenterY2 - currentCenterY2) - expectedDeltaY2);
116 if (errorX > TOLERANCE2 || errorY > TOLERANCE2) {
120 const int error = errorX * errorX + errorY * errorY;
121 if (error < bestError) {
133 bool PuzzleGame::clearVisualRun() {
134 constexpr std::array<std::pair<int, int>, 4> directions{{{1, 0}, {0, 1}, {1, 1}, {-1, 1}}};
135 for (
int gridIndex = 0; gridIndex < static_cast<int>(
grid.size()); ++gridIndex) {
136 GameGrid &focusGrid =
grid[
static_cast<std::size_t
>(gridIndex)];
137 for (
int y = 0; y < focusGrid.height(); ++y) {
138 for (
int x = 0; x < focusGrid.width(); ++x) {
139 const CellRef start{gridIndex, x, y};
140 const Block *startBlock = blockAt(start);
141 if (startBlock ==
nullptr || startBlock->color <= 0) {
145 for (
const auto &[dx, dy] : directions) {
147 if (findNextVisualMatch(start, startBlock->color, -dx, -dy, previous)) {
151 std::vector<CellRef> run{start};
152 CellRef current = start;
153 while (findNextVisualMatch(current, startBlock->color, dx, dy, current)) {
154 if (std::any_of(run.begin(), run.end(), [&](
const CellRef &cell) {
155 return sameCell(cell, current);
159 run.push_back(current);
162 if (run.size() < 3U) {
166 for (
const CellRef &cell : run) {
167 if (Block *block = blockAt(cell)) {
168 markClearing(*block);
182 bool PuzzleGame::clearRun(
GameGrid &focusGrid,
int x,
int y,
int dx,
int dy) {
183 Block *start = focusGrid.at(x, y);
184 if (start ==
nullptr || start->color <= 0) {
188 const int color = start->color;
189 const Block *previous = focusGrid.at(x - dx, y - dy);
190 if (previous !=
nullptr && previous->color == color) {
194 std::vector<Block *> blocks{};
197 while (Block *block = focusGrid.at(cx, cy)) {
198 if (block->color != color) {
201 blocks.push_back(block);
206 if (blocks.size() < 3U) {
210 for (Block *block : blocks) {
211 markClearing(*block);
219 void PuzzleGame::clearTopBottomSeams() {
220 for (
int x = 0; x <
grid[0].width(); ++x) {
221 const int topY =
grid[0].height() - 1;
222 const int bottomY =
grid[2].height() - 1;
223 if (clearSeam({
grid[0].at(x, topY),
grid[0].at(x, topY - 1),
grid[2].at(x, bottomY),
grid[2].at(x, bottomY - 1)})) {
226 if (clearSeam({
grid[2].at(x, bottomY),
grid[0].at(x, topY),
grid[2].at(x, bottomY - 1),
grid[0].at(x, topY - 1)})) {
232 bool PuzzleGame::clearSeam(std::array<Block *, 4> blocks) {
233 if (blocks[0] ==
nullptr || blocks[1] ==
nullptr || blocks[2] ==
nullptr || blocks[0]->color <= 0) {
236 if (blocks[0]->color != blocks[1]->color || blocks[0]->color != blocks[2]->color) {
239 if (blocks[3] !=
nullptr && blocks[3]->color == blocks[0]->color) {
240 markClearing(*blocks[3]);
244 markClearing(*blocks[0]);
245 markClearing(*blocks[1]);
246 markClearing(*blocks[2]);
253 void PuzzleGame::moveDownBlocks() {
254 for (GameGrid &focusGrid :
grid) {
255 for (
int x = 0; x < focusGrid.width(); ++x) {
256 for (
int y = focusGrid.height() - 2; y >= 0; --y) {
257 Block *current = focusGrid.at(x, y);
258 Block *below = focusGrid.at(x, y + 1);
259 if (current !=
nullptr && below !=
nullptr && current->color > 0 && below->color == 0) {
260 std::swap(current->color, below->color);
268 void PuzzleGame::markClearing(
Block &block) {
270 block.clearElapsedMs = 0;
273 void PuzzleGame::recordClear() {
280 void PuzzleGame::playClearSound() {
PuzzleGame(int difficulty, std::function< void()> lineSoundCallback)
void newGame(int difficulty)
std::array< GameGrid, 4 > grid
constexpr unsigned int MIN_DROP_TIMEOUT_MS
constexpr int BLOCK_HEIGHT
constexpr int SIDE_GRID_Y
constexpr int SIDE_GRID_WIDTH
constexpr int CLEARS_PER_LEVEL
constexpr int CENTER_GRID_X
constexpr int BOTTOM_GRID_Y
constexpr int SIDE_GRID_HEIGHT
constexpr int BOTTOM_GRID_HEIGHT
constexpr int LEVEL_TIMEOUT_STEP_MS
constexpr int TOP_GRID_HEIGHT
constexpr int RIGHT_GRID_X
constexpr int BLOCK_WIDTH