55
66using 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+
8105Twos::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
66163Twos::~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-
125170bool 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