Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 137 additions & 172 deletions src/displayapp/screens/Twos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,103 @@

using namespace Pinetime::Applications::Screens;

bool TwosGrid::PlaceNewTile() {
unsigned int emptyCells[nCells];
unsigned int nEmpty = 0;
for (unsigned int i = 0; i < nCells; i++) {
const unsigned int row = i / nCols;
const unsigned int col = i % nCols;
if (grid[row][col].value == 0) {
emptyCells[nEmpty] = i;
nEmpty++;
}
}

if (nEmpty == 0) {
return false; // game lost
}

int random = rand() % nEmpty;

if ((rand() % 100) < 90) {
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
} else {
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;
}
return true;
}

void TwosGrid::ResetMergeState() {
for (auto& gridRow : grid) {
for (TwosTile& tile : gridRow) {
tile.merged = false;
}
}
}

bool TwosGrid::TryMerge(int newRow, int newCol, int oldRow, int oldCol) {
if (grid[newRow][newCol].value == grid[oldRow][oldCol].value) {
if ((newCol != oldCol) || (newRow != oldRow)) {
if (!grid[newRow][newCol].merged) {
grid[newRow][newCol].value *= 2;
score += grid[newRow][newCol].value;
grid[oldRow][oldCol].value = 0;
grid[newRow][newCol].merged = true;
return true;
}
}
}
return false;
}

bool TwosGrid::TryMove(int newRow, int newCol, int oldRow, int oldCol) {
if ((newRow == oldRow) && (newCol == oldCol)) {
return false;
}
grid[newRow][newCol].value = grid[oldRow][oldCol].value;
grid[oldRow][oldCol].value = 0;
return true;
}

bool TwosGrid::Slide(int rowStep, int colStep) {
ResetMergeState();

bool validMove = false;
// Tiles closest to the edge they are sliding towards have to be resolved
// first, so that the ones behind them can use the space they free up.
for (int i = 0; i < nRows; i++) {
const int row = rowStep > 0 ? nRows - 1 - i : i;
for (int j = 0; j < nCols; j++) {
const int col = colStep > 0 ? nCols - 1 - j : j;
if (grid[row][col].value == 0) {
continue;
}

// Look for the furthest empty cell in this direction, stopping at the
// first tile in the way and merging with it if possible.
int newRow = row;
int newCol = col;
for (int r = row + rowStep, c = col + colStep; (r >= 0) && (r < nRows) && (c >= 0) && (c < nCols); r += rowStep, c += colStep) {
if (grid[r][c].value == 0) {
newRow = r;
newCol = c;
} else { // blocked by another tile
if (TryMerge(r, c, row, col)) {
validMove = true;
}
break;
}
}

if (TryMove(newRow, newCol, row, col)) {
validMove = true;
}
}
}

return validMove;
}

Twos::Twos() {

struct colorPair {
Expand Down Expand Up @@ -35,13 +132,12 @@ Twos::Twos() {
lv_obj_add_style(gridDisplay, LV_TABLE_PART_CELL1 + i, &cellStyles[i]);
}

lv_table_set_col_cnt(gridDisplay, nCols);
lv_table_set_row_cnt(gridDisplay, nRows);
for (int col = 0; col < nCols; col++) {
static constexpr int colWidth = LV_HOR_RES_MAX / nCols;
lv_table_set_col_cnt(gridDisplay, TwosGrid::nCols);
lv_table_set_row_cnt(gridDisplay, TwosGrid::nRows);
for (int col = 0; col < TwosGrid::nCols; col++) {
static constexpr int colWidth = LV_HOR_RES_MAX / TwosGrid::nCols;
lv_table_set_col_width(gridDisplay, col, colWidth);
for (int row = 0; row < nRows; row++) {
grid[row][col].value = 0;
for (int row = 0; row < TwosGrid::nRows; row++) {
lv_table_set_cell_type(gridDisplay, row, col, 1);
lv_table_set_cell_align(gridDisplay, row, col, LV_LABEL_ALIGN_CENTER);
}
Expand All @@ -51,16 +147,17 @@ Twos::Twos() {

lv_obj_clean_style_list(gridDisplay, LV_TABLE_PART_BG);

placeNewTile();
placeNewTile();
grid.PlaceNewTile();
grid.PlaceNewTile();
UpdateGridDisplay();

// format score text
scoreText = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_width(scoreText, LV_HOR_RES);
lv_label_set_align(scoreText, LV_ALIGN_IN_LEFT_MID);
lv_obj_align(scoreText, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
lv_label_set_recolor(scoreText, true);
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", score);
UpdateScoreDisplay();
}

Twos::~Twos() {
Expand All @@ -70,184 +167,52 @@ Twos::~Twos() {
lv_obj_clean(lv_scr_act());
}

bool Twos::placeNewTile() {
unsigned int emptyCells[nCells];
unsigned int nEmpty = 0;
for (unsigned int i = 0; i < nCells; i++) {
const unsigned int row = i / nCols;
const unsigned int col = i % nCols;
if (grid[row][col].value == 0) {
emptyCells[nEmpty] = i;
nEmpty++;
}
}

if (nEmpty == 0) {
return false; // game lost
}

int random = rand() % nEmpty;

if ((rand() % 100) < 90) {
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
} else {
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;
}
updateGridDisplay();
return true;
}

bool Twos::tryMerge(int newRow, int newCol, int oldRow, int oldCol) {
if (grid[newRow][newCol].value == grid[oldRow][oldCol].value) {
if ((newCol != oldCol) || (newRow != oldRow)) {
if (!grid[newRow][newCol].merged) {
grid[newRow][newCol].value *= 2;
score += grid[newRow][newCol].value;
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", score);
grid[oldRow][oldCol].value = 0;
grid[newRow][newCol].merged = true;
return true;
}
}
}
return false;
}

bool Twos::tryMove(int newRow, int newCol, int oldRow, int oldCol) {
if (((newCol >= 0) && (newCol != oldCol)) || ((newRow >= 0) && (newRow != oldRow))) {
grid[newRow][newCol].value = grid[oldRow][oldCol].value;
grid[oldRow][oldCol].value = 0;
return true;
}
return false;
}

bool Twos::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
bool validMove = false;
for (unsigned int i = 0; i < nCells; i++) {
const unsigned int row = i / nCols;
const unsigned int col = i % nCols;
grid[row][col].merged = false; // reinitialize merge state
}
int rowStep = 0;
int colStep = 0;
switch (event) {
case TouchEvents::SwipeLeft:
for (int col = 1; col < nCols; col++) { // ignore tiles already on far left
for (int row = 0; row < nRows; row++) {
if (grid[row][col].value > 0) {
int newCol = -1;
for (int potentialNewCol = col - 1; potentialNewCol >= 0; potentialNewCol--) {
if (grid[row][potentialNewCol].value == 0) {
newCol = potentialNewCol;
} else { // blocked by another tile
if (tryMerge(row, potentialNewCol, row, col)) {
validMove = true;
}
break;
}
}
if (tryMove(row, newCol, row, col)) {
validMove = true;
}
}
}
}
if (validMove) {
placeNewTile();
}
return true;
colStep = -1;
break;
case TouchEvents::SwipeRight:
for (int col = nCols - 2; col >= 0; col--) { // ignore tiles already on far right
for (int row = 0; row < nRows; row++) {
if (grid[row][col].value > 0) {
int newCol = -1;
for (int potentialNewCol = col + 1; potentialNewCol < nCols; potentialNewCol++) {
if (grid[row][potentialNewCol].value == 0) {
newCol = potentialNewCol;
} else { // blocked by another tile
if (tryMerge(row, potentialNewCol, row, col)) {
validMove = true;
}
break;
}
}
if (tryMove(row, newCol, row, col)) {
validMove = true;
}
}
}
}
if (validMove) {
placeNewTile();
}
return true;
colStep = 1;
break;
case TouchEvents::SwipeUp:
for (int row = 1; row < nRows; row++) { // ignore tiles already on top
for (int col = 0; col < nCols; col++) {
if (grid[row][col].value > 0) {
int newRow = -1;
for (int potentialNewRow = row - 1; potentialNewRow >= 0; potentialNewRow--) {
if (grid[potentialNewRow][col].value == 0) {
newRow = potentialNewRow;
} else { // blocked by another tile
if (tryMerge(potentialNewRow, col, row, col)) {
validMove = true;
}
break;
}
}
if (tryMove(newRow, col, row, col)) {
validMove = true;
}
}
}
}
if (validMove) {
placeNewTile();
}
return true;
rowStep = -1;
break;
case TouchEvents::SwipeDown:
for (int row = nRows - 2; row >= 0; row--) { // ignore tiles already on bottom
for (int col = 0; col < nCols; col++) {
if (grid[row][col].value > 0) {
int newRow = -1;
for (int potentialNewRow = row + 1; potentialNewRow < nRows; potentialNewRow++) {
if (grid[potentialNewRow][col].value == 0) {
newRow = potentialNewRow;
} else { // blocked by another tile
if (tryMerge(potentialNewRow, col, row, col)) {
validMove = true;
}
break;
}
}
if (tryMove(newRow, col, row, col)) {
validMove = true;
}
}
}
}
if (validMove) {
placeNewTile();
}
return true;
rowStep = 1;
break;
default:
return false;
}
return false;

if (grid.Slide(rowStep, colStep)) {
grid.PlaceNewTile();
UpdateGridDisplay();
UpdateScoreDisplay();
}
return true;
}

void Twos::updateGridDisplay() {
for (unsigned int i = 0; i < nCells; i++) {
const unsigned int row = i / nCols;
const unsigned int col = i % nCols;
if (grid[row][col].value > 0) {
char buffer[7];
snprintf(buffer, sizeof(buffer), "%u", grid[row][col].value);
void Twos::UpdateScoreDisplay() {
lv_label_set_text_fmt(scoreText, "Score #FFFF00 %i#", grid.GetScore());
}

void Twos::UpdateGridDisplay() {
for (unsigned int i = 0; i < TwosGrid::nCells; i++) {
const unsigned int row = i / TwosGrid::nCols;
const unsigned int col = i % TwosGrid::nCols;
const unsigned int value = grid.GetTileValue(row, col);
if (value > 0) {
// Large enough for any unsigned int, so the value can never be truncated
char buffer[11];
snprintf(buffer, sizeof(buffer), "%u", value);
lv_table_set_cell_value(gridDisplay, row, col, buffer);
} else {
lv_table_set_cell_value(gridDisplay, row, col, "");
}
switch (grid[row][col].value) {
switch (value) {
case 0:
lv_table_set_cell_type(gridDisplay, row, col, 1);
break;
Expand Down
Loading
Loading