Skip to content

Commit 28aca03

Browse files
committed
Move Twos grid state and rules into a TwosGrid class
The Twos screen held both the game state and the rules that manipulate it alongside its LVGL objects. Move the grid, the score and the functions that act on them into a TwosGrid class, leaving the screen responsible only for drawing. The four swipe branches of OnTouchEvent were the same algorithm written out once per direction. They are replaced by TwosGrid::Slide, which takes the direction as a row/column step, so the sliding and merging rules now exist in one place. Fixes #1668
1 parent bd5ceff commit 28aca03

2 files changed

Lines changed: 175 additions & 181 deletions

File tree

src/displayapp/screens/Twos.cpp

Lines changed: 137 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,103 @@
55

66
using namespace Pinetime::Applications::Screens;
77

8+
bool TwosGrid::PlaceNewTile() {
9+
unsigned int emptyCells[nCells];
10+
unsigned int nEmpty = 0;
11+
for (unsigned int i = 0; i < nCells; i++) {
12+
const unsigned int row = i / nCols;
13+
const unsigned int col = i % nCols;
14+
if (grid[row][col].value == 0) {
15+
emptyCells[nEmpty] = i;
16+
nEmpty++;
17+
}
18+
}
19+
20+
if (nEmpty == 0) {
21+
return false; // game lost
22+
}
23+
24+
int random = rand() % nEmpty;
25+
26+
if ((rand() % 100) < 90) {
27+
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
28+
} else {
29+
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;
30+
}
31+
return true;
32+
}
33+
34+
void TwosGrid::ResetMergeState() {
35+
for (auto& gridRow : grid) {
36+
for (TwosTile& tile : gridRow) {
37+
tile.merged = false;
38+
}
39+
}
40+
}
41+
42+
bool TwosGrid::TryMerge(int newRow, int newCol, int oldRow, int oldCol) {
43+
if (grid[newRow][newCol].value == grid[oldRow][oldCol].value) {
44+
if ((newCol != oldCol) || (newRow != oldRow)) {
45+
if (!grid[newRow][newCol].merged) {
46+
grid[newRow][newCol].value *= 2;
47+
score += grid[newRow][newCol].value;
48+
grid[oldRow][oldCol].value = 0;
49+
grid[newRow][newCol].merged = true;
50+
return true;
51+
}
52+
}
53+
}
54+
return false;
55+
}
56+
57+
bool TwosGrid::TryMove(int newRow, int newCol, int oldRow, int oldCol) {
58+
if ((newRow == oldRow) && (newCol == oldCol)) {
59+
return false;
60+
}
61+
grid[newRow][newCol].value = grid[oldRow][oldCol].value;
62+
grid[oldRow][oldCol].value = 0;
63+
return true;
64+
}
65+
66+
bool TwosGrid::Slide(int rowStep, int colStep) {
67+
ResetMergeState();
68+
69+
bool validMove = false;
70+
// Tiles closest to the edge they are sliding towards have to be resolved
71+
// first, so that the ones behind them can use the space they free up.
72+
for (int i = 0; i < nRows; i++) {
73+
const int row = rowStep > 0 ? nRows - 1 - i : i;
74+
for (int j = 0; j < nCols; j++) {
75+
const int col = colStep > 0 ? nCols - 1 - j : j;
76+
if (grid[row][col].value == 0) {
77+
continue;
78+
}
79+
80+
// Look for the furthest empty cell in this direction, stopping at the
81+
// first tile in the way and merging with it if possible.
82+
int newRow = row;
83+
int newCol = col;
84+
for (int r = row + rowStep, c = col + colStep; (r >= 0) && (r < nRows) && (c >= 0) && (c < nCols); r += rowStep, c += colStep) {
85+
if (grid[r][c].value == 0) {
86+
newRow = r;
87+
newCol = c;
88+
} else { // blocked by another tile
89+
if (TryMerge(r, c, row, col)) {
90+
validMove = true;
91+
}
92+
break;
93+
}
94+
}
95+
96+
if (TryMove(newRow, newCol, row, col)) {
97+
validMove = true;
98+
}
99+
}
100+
}
101+
102+
return validMove;
103+
}
104+
8105
Twos::Twos() {
9106

10107
struct colorPair {
@@ -35,13 +132,12 @@ Twos::Twos() {
35132
lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL1 + i, &cellStyles[i]);
36133
}
37134

38-
lv_table_set_col_cnt(gridDisplay, nCols);
39-
lv_table_set_row_cnt(gridDisplay, nRows);
40-
for (int col = 0; col < nCols; col++) {
41-
static constexpr int colWidth = LV_HOR_RES_MAX / nCols;
135+
lv_table_set_col_cnt(gridDisplay, TwosGrid::nCols);
136+
lv_table_set_row_cnt(gridDisplay, TwosGrid::nRows);
137+
for (int col = 0; col < TwosGrid::nCols; col++) {
138+
static constexpr int colWidth = LV_HOR_RES_MAX / TwosGrid::nCols;
42139
lv_table_set_col_width(gridDisplay, col, colWidth);
43-
for (int row = 0; row < nRows; row++) {
44-
grid[row][col].value = 0;
140+
for (int row = 0; row < TwosGrid::nRows; row++) {
45141
lv_table_set_cell_type(gridDisplay, row, col, 1);
46142
lv_table_set_cell_align(gridDisplay, row, col, LV_LABEL_ALIGN_CENTER);
47143
}
@@ -51,16 +147,17 @@ Twos::Twos() {
51147

52148
lv_obj_clean_style_list(gridDisplay, LV_TABLE_PART_BG);
53149

54-
placeNewTile();
55-
placeNewTile();
150+
grid.PlaceNewTile();
151+
grid.PlaceNewTile();
152+
UpdateGridDisplay();
56153

57154
// format score text
58155
scoreText = lv_label_create(lv_scr_act(), nullptr);
59156
lv_obj_set_width(scoreText, LV_HOR_RES);
60157
lv_label_set_align(scoreText, LV_ALIGN_IN_LEFT_MID);
61158
lv_obj_align(scoreText, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
62159
lv_label_set_recolor(scoreText, true);
63-
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", score);
160+
UpdateScoreDisplay();
64161
}
65162

66163
Twos::~Twos() {
@@ -70,184 +167,52 @@ Twos::~Twos() {
70167
lv_obj_clean(lv_scr_act());
71168
}
72169

73-
bool Twos::placeNewTile() {
74-
unsigned int emptyCells[nCells];
75-
unsigned int nEmpty = 0;
76-
for (unsigned int i = 0; i < nCells; i++) {
77-
const unsigned int row = i / nCols;
78-
const unsigned int col = i % nCols;
79-
if (grid[row][col].value == 0) {
80-
emptyCells[nEmpty] = i;
81-
nEmpty++;
82-
}
83-
}
84-
85-
if (nEmpty == 0) {
86-
return false; // game lost
87-
}
88-
89-
int random = rand() % nEmpty;
90-
91-
if ((rand() % 100) < 90) {
92-
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
93-
} else {
94-
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;
95-
}
96-
updateGridDisplay();
97-
return true;
98-
}
99-
100-
bool Twos::tryMerge(int newRow, int newCol, int oldRow, int oldCol) {
101-
if (grid[newRow][newCol].value == grid[oldRow][oldCol].value) {
102-
if ((newCol != oldCol) || (newRow != oldRow)) {
103-
if (!grid[newRow][newCol].merged) {
104-
grid[newRow][newCol].value *= 2;
105-
score += grid[newRow][newCol].value;
106-
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", score);
107-
grid[oldRow][oldCol].value = 0;
108-
grid[newRow][newCol].merged = true;
109-
return true;
110-
}
111-
}
112-
}
113-
return false;
114-
}
115-
116-
bool Twos::tryMove(int newRow, int newCol, int oldRow, int oldCol) {
117-
if (((newCol >= 0) && (newCol != oldCol)) || ((newRow >= 0) && (newRow != oldRow))) {
118-
grid[newRow][newCol].value = grid[oldRow][oldCol].value;
119-
grid[oldRow][oldCol].value = 0;
120-
return true;
121-
}
122-
return false;
123-
}
124-
125170
bool Twos::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
126-
bool validMove = false;
127-
for (unsigned int i = 0; i < nCells; i++) {
128-
const unsigned int row = i / nCols;
129-
const unsigned int col = i % nCols;
130-
grid[row][col].merged = false; // reinitialize merge state
131-
}
171+
int rowStep = 0;
172+
int colStep = 0;
132173
switch (event) {
133174
case TouchEvents::SwipeLeft:
134-
for (int col = 1; col < nCols; col++) { // ignore tiles already on far left
135-
for (int row = 0; row < nRows; row++) {
136-
if (grid[row][col].value > 0) {
137-
int newCol = -1;
138-
for (int potentialNewCol = col - 1; potentialNewCol >= 0; potentialNewCol--) {
139-
if (grid[row][potentialNewCol].value == 0) {
140-
newCol = potentialNewCol;
141-
} else { // blocked by another tile
142-
if (tryMerge(row, potentialNewCol, row, col)) {
143-
validMove = true;
144-
}
145-
break;
146-
}
147-
}
148-
if (tryMove(row, newCol, row, col)) {
149-
validMove = true;
150-
}
151-
}
152-
}
153-
}
154-
if (validMove) {
155-
placeNewTile();
156-
}
157-
return true;
175+
colStep = -1;
176+
break;
158177
case TouchEvents::SwipeRight:
159-
for (int col = nCols - 2; col >= 0; col--) { // ignore tiles already on far right
160-
for (int row = 0; row < nRows; row++) {
161-
if (grid[row][col].value > 0) {
162-
int newCol = -1;
163-
for (int potentialNewCol = col + 1; potentialNewCol < nCols; potentialNewCol++) {
164-
if (grid[row][potentialNewCol].value == 0) {
165-
newCol = potentialNewCol;
166-
} else { // blocked by another tile
167-
if (tryMerge(row, potentialNewCol, row, col)) {
168-
validMove = true;
169-
}
170-
break;
171-
}
172-
}
173-
if (tryMove(row, newCol, row, col)) {
174-
validMove = true;
175-
}
176-
}
177-
}
178-
}
179-
if (validMove) {
180-
placeNewTile();
181-
}
182-
return true;
178+
colStep = 1;
179+
break;
183180
case TouchEvents::SwipeUp:
184-
for (int row = 1; row < nRows; row++) { // ignore tiles already on top
185-
for (int col = 0; col < nCols; col++) {
186-
if (grid[row][col].value > 0) {
187-
int newRow = -1;
188-
for (int potentialNewRow = row - 1; potentialNewRow >= 0; potentialNewRow--) {
189-
if (grid[potentialNewRow][col].value == 0) {
190-
newRow = potentialNewRow;
191-
} else { // blocked by another tile
192-
if (tryMerge(potentialNewRow, col, row, col)) {
193-
validMove = true;
194-
}
195-
break;
196-
}
197-
}
198-
if (tryMove(newRow, col, row, col)) {
199-
validMove = true;
200-
}
201-
}
202-
}
203-
}
204-
if (validMove) {
205-
placeNewTile();
206-
}
207-
return true;
181+
rowStep = -1;
182+
break;
208183
case TouchEvents::SwipeDown:
209-
for (int row = nRows - 2; row >= 0; row--) { // ignore tiles already on bottom
210-
for (int col = 0; col < nCols; col++) {
211-
if (grid[row][col].value > 0) {
212-
int newRow = -1;
213-
for (int potentialNewRow = row + 1; potentialNewRow < nRows; potentialNewRow++) {
214-
if (grid[potentialNewRow][col].value == 0) {
215-
newRow = potentialNewRow;
216-
} else { // blocked by another tile
217-
if (tryMerge(potentialNewRow, col, row, col)) {
218-
validMove = true;
219-
}
220-
break;
221-
}
222-
}
223-
if (tryMove(newRow, col, row, col)) {
224-
validMove = true;
225-
}
226-
}
227-
}
228-
}
229-
if (validMove) {
230-
placeNewTile();
231-
}
232-
return true;
184+
rowStep = 1;
185+
break;
233186
default:
234187
return false;
235188
}
236-
return false;
189+
190+
if (grid.Slide(rowStep, colStep)) {
191+
grid.PlaceNewTile();
192+
UpdateGridDisplay();
193+
UpdateScoreDisplay();
194+
}
195+
return true;
237196
}
238197

239-
void Twos::updateGridDisplay() {
240-
for (unsigned int i = 0; i < nCells; i++) {
241-
const unsigned int row = i / nCols;
242-
const unsigned int col = i % nCols;
243-
if (grid[row][col].value > 0) {
244-
char buffer[7];
245-
snprintf(buffer, sizeof(buffer), "%u", grid[row][col].value);
198+
void Twos::UpdateScoreDisplay() {
199+
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", grid.GetScore());
200+
}
201+
202+
void Twos::UpdateGridDisplay() {
203+
for (unsigned int i = 0; i < TwosGrid::nCells; i++) {
204+
const unsigned int row = i / TwosGrid::nCols;
205+
const unsigned int col = i % TwosGrid::nCols;
206+
const unsigned int value = grid.GetTileValue(row, col);
207+
if (value > 0) {
208+
// Large enough for any unsigned int, so the value can never be truncated
209+
char buffer[11];
210+
snprintf(buffer, sizeof(buffer), "%u", value);
246211
lv_table_set_cell_value(gridDisplay, row, col, buffer);
247212
} else {
248213
lv_table_set_cell_value(gridDisplay, row, col, "");
249214
}
250-
switch (grid[row][col].value) {
215+
switch (value) {
251216
case 0:
252217
lv_table_set_cell_type(gridDisplay, row, col, 1);
253218
break;

0 commit comments

Comments
 (0)