mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-08-05 21:24:27 -04:00
Tetris: Combined cpp files. Helper files into hpp. Dark mode. Encoder on. (#2587)
This commit is contained in:
parent
20c64c98bd
commit
4b000c8da6
6 changed files with 653 additions and 921 deletions
562
firmware/application/external/tetris/ui_tetris.cpp
vendored
562
firmware/application/external/tetris/ui_tetris.cpp
vendored
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* 2025 updates by RocketGod (https://betaskynet.com/)
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
@ -23,23 +24,528 @@
|
|||
|
||||
namespace ui::external_app::tetris {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
// external code, so ignore warnings
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
#include "tetris.cpp"
|
||||
#pragma GCC diagnostic pop
|
||||
Ticker game;
|
||||
Ticker joystick;
|
||||
|
||||
unsigned char level = 0;
|
||||
const float delays[4] = {1.2, 0.7, 0.4, 0.25};
|
||||
unsigned int score = 0;
|
||||
bool firstTime = true;
|
||||
bool gameStarted = false;
|
||||
unsigned char nextFigure = 1;
|
||||
short board[20][10] = {0};
|
||||
const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
|
||||
const short DIMENSION = 16;
|
||||
const short DIMENSION_NEXT = 12;
|
||||
short figuresX[7][4] = {{0, 0, 0, 0}, {0, 0, 1, 1}, {0, 1, 1, 1}, {1, 1, 0, 0}, {0, 1, 0, 1}, {1, 1, 1, 0}, {1, 1, 1, 0}};
|
||||
short figuresY[7][4] = {{0, 1, 2, 3}, {1, 0, 0, 1}, {1, 1, 2, 0}, {0, 1, 1, 2}, {0, 1, 1, 2}, {2, 1, 0, 0}, {0, 1, 2, 2}};
|
||||
|
||||
const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
Painter painter;
|
||||
|
||||
bool but_RIGHT = false;
|
||||
bool but_LEFT = false;
|
||||
bool but_UP = false;
|
||||
bool but_DOWN = false;
|
||||
bool but_SELECT = false;
|
||||
|
||||
static int x_pos{0};
|
||||
static int y_pos{0};
|
||||
static int fg_color;
|
||||
static int bg_color;
|
||||
|
||||
static Callback fall_timer_callback = nullptr;
|
||||
static uint32_t fall_timer_timeout = 0;
|
||||
static uint32_t fall_timer_counter = 0;
|
||||
static Callback dir_button_callback = nullptr;
|
||||
|
||||
void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
}
|
||||
|
||||
void background(int color) {
|
||||
bg_color = color;
|
||||
}
|
||||
|
||||
void foreground(int color) {
|
||||
fg_color = color;
|
||||
}
|
||||
|
||||
void locate(int x, int y) {
|
||||
x_pos = x;
|
||||
y_pos = y;
|
||||
}
|
||||
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
void printf(std::string str) {
|
||||
auto style = (fg_color == White) ? *ui::Theme::getInstance()->bg_darkest : *ui::Theme::getInstance()->fg_light;
|
||||
painter.draw_string({x_pos, y_pos - 1}, style, str);
|
||||
}
|
||||
|
||||
void printf(std::string str, int v) {
|
||||
if (str.find_first_of("%") != std::string::npos) {
|
||||
str.resize(str.find_first_of("%"));
|
||||
}
|
||||
printf(str + to_string_dec_uint(v));
|
||||
}
|
||||
|
||||
void check_fall_timer() {
|
||||
if (fall_timer_callback) {
|
||||
if (++fall_timer_counter >= fall_timer_timeout) {
|
||||
fall_timer_counter = 0;
|
||||
fall_timer_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
if (delay_sec == 0.3) {
|
||||
dir_button_callback = func;
|
||||
} else {
|
||||
fall_timer_callback = func;
|
||||
fall_timer_timeout = delay_sec * 60;
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
dir_button_callback = nullptr;
|
||||
fall_timer_callback = nullptr;
|
||||
}
|
||||
|
||||
unsigned int GenerateRandomSeed() {
|
||||
return LPC_RTC->CTIME0;
|
||||
}
|
||||
|
||||
void Init() {
|
||||
}
|
||||
|
||||
void ShowScore() {
|
||||
fillrect(165, 10, 235, 60, Black);
|
||||
rect(165, 10, 235, 60, White);
|
||||
locate(200, 35);
|
||||
printf("%d", score);
|
||||
}
|
||||
|
||||
void ShowNextFigure() {
|
||||
fillrect(165, 70, 235, 130, Black);
|
||||
rect(165, 70, 235, 130, White);
|
||||
int upperLeftX = 176, upperLeftY = 83;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
|
||||
fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
|
||||
rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCursor(int color, unsigned char lev) {
|
||||
fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
|
||||
}
|
||||
|
||||
void ShowLevelMenu() {
|
||||
cls();
|
||||
background(Black);
|
||||
foreground(White);
|
||||
locate(80, 50);
|
||||
printf("LEVEL 1");
|
||||
locate(80, 120);
|
||||
printf("LEVEL 2");
|
||||
locate(80, 190);
|
||||
printf("LEVEL 3");
|
||||
locate(80, 260);
|
||||
printf("LEVEL 4");
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
|
||||
void ReadJoystickForLevel() {
|
||||
unsigned char old = level;
|
||||
if (but_UP) {
|
||||
(level == 0) ? level = 3 : level--;
|
||||
} else if (but_DOWN) {
|
||||
level = (level + 1) % 4;
|
||||
}
|
||||
if (old != level) {
|
||||
DrawCursor(Black, old);
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
}
|
||||
|
||||
void EndPlay() {
|
||||
joystick.detach();
|
||||
game.detach();
|
||||
score = 0;
|
||||
firstTime = true;
|
||||
gameStarted = false;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
board[i][j] = 0;
|
||||
}
|
||||
}
|
||||
ShowLevelMenu();
|
||||
joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
}
|
||||
|
||||
void StartGame() {
|
||||
cls();
|
||||
background(Black);
|
||||
foreground(White);
|
||||
fillrect(0, 0, 162, 320, Black);
|
||||
rect(162, 0, 164, 320, White);
|
||||
fillrect(164, 0, 240, 320, Black);
|
||||
ShowScore();
|
||||
ShowNextFigure();
|
||||
}
|
||||
|
||||
void copyCoordinates(short X[], short Y[], unsigned char index) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
X[i] = figuresX[index][i];
|
||||
Y[i] = figuresY[index][i];
|
||||
}
|
||||
}
|
||||
|
||||
bool BottomEdge(int x) {
|
||||
return x > 19;
|
||||
}
|
||||
|
||||
bool LeftEdge(int y) {
|
||||
return y < 0;
|
||||
}
|
||||
|
||||
bool RightEdge(int y) {
|
||||
return y > 9;
|
||||
}
|
||||
|
||||
bool OutOfBounds(int y, int x) {
|
||||
return y < 0 || y > 9 || x > 19;
|
||||
}
|
||||
|
||||
void PutBorders(short x, short y) {
|
||||
for (int i = x - 1; i <= x + 1; i++) {
|
||||
for (int j = y - 1; j <= y + 1; j++) {
|
||||
if (i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
|
||||
rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
short X[4];
|
||||
short Y[4];
|
||||
short boardX, boardY;
|
||||
unsigned char colorIndex;
|
||||
|
||||
void Tetromino(unsigned char c) {
|
||||
Initialize(c);
|
||||
}
|
||||
|
||||
void Initialize(unsigned char c) {
|
||||
colorIndex = c;
|
||||
boardX = 0;
|
||||
boardY = 4;
|
||||
copyCoordinates(X, Y, c - 1);
|
||||
}
|
||||
|
||||
void Rotate() {
|
||||
short pivotX = X[1];
|
||||
short pivotY = Y[1];
|
||||
short newX[4];
|
||||
short newY[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
short tmp = X[i], tmp2 = Y[i];
|
||||
newX[i] = pivotX + pivotY - tmp2;
|
||||
newY[i] = tmp + pivotX - pivotY;
|
||||
if (OutOfBounds(boardY + newY[i], boardX + newX[i]) || board[boardX + newX[i]][boardY + newY[i]] != 0) return;
|
||||
}
|
||||
DeleteFigure();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
X[i] = newX[i];
|
||||
Y[i] = newY[i];
|
||||
}
|
||||
DrawFigure();
|
||||
}
|
||||
|
||||
void DrawFigure() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
|
||||
rect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteFigure() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect(upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
|
||||
PutBorders(upperLeftY, upperLeftX);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAttached() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
board[boardX + X[i]][boardY + Y[i]] = colorIndex;
|
||||
}
|
||||
}
|
||||
|
||||
bool MoveDown(char delta) {
|
||||
if (!InCollisionDown(delta)) {
|
||||
DeleteFigure();
|
||||
boardX += delta;
|
||||
DrawFigure();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MoveLeft() {
|
||||
if (!InCollisionLeft()) {
|
||||
DeleteFigure();
|
||||
boardY--;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void MoveRight() {
|
||||
if (!InCollisionRight()) {
|
||||
DeleteFigure();
|
||||
boardY++;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void SoftDrop() {
|
||||
DeleteFigure();
|
||||
MoveDown(2);
|
||||
DrawFigure();
|
||||
score += 2 * (level + 1);
|
||||
ShowScore();
|
||||
}
|
||||
|
||||
bool InCollisionDown(char delta) {
|
||||
int newX, newY;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
newX = boardX + X[i] + delta;
|
||||
newY = boardY + Y[i];
|
||||
if (BottomEdge(newX) || board[newX][newY] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InCollisionLeft() {
|
||||
int newX, newY;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] - 1;
|
||||
if (LeftEdge(newY) || board[newX][newY] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InCollisionRight() {
|
||||
int newX, newY;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] + 1;
|
||||
if (RightEdge(newY) || board[newX][newY] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReadJoystickForFigure() {
|
||||
if (but_LEFT) {
|
||||
MoveLeft();
|
||||
} else if (but_RIGHT) {
|
||||
MoveRight();
|
||||
} else if (but_UP) {
|
||||
pause_game();
|
||||
} else if (but_DOWN) {
|
||||
SoftDrop();
|
||||
} else if (but_SELECT) {
|
||||
Rotate();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckLines(short& firstLine, short& numberOfLines) {
|
||||
firstLine = -1;
|
||||
numberOfLines = 0;
|
||||
for (int i = 19; i >= 0; i--) {
|
||||
short temp = 0;
|
||||
for (int j = 0; j < 10; j++) {
|
||||
if (board[i][j] == 0) {
|
||||
if (numberOfLines > 0) return;
|
||||
break;
|
||||
}
|
||||
temp++;
|
||||
}
|
||||
if (temp == 10) {
|
||||
numberOfLines++;
|
||||
if (firstLine == -1) firstLine = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int UpdateScore(short numOfLines) {
|
||||
unsigned int newIncrement = 0;
|
||||
switch (numOfLines) {
|
||||
case 1:
|
||||
newIncrement = 40;
|
||||
break;
|
||||
case 2:
|
||||
newIncrement = 100;
|
||||
break;
|
||||
case 3:
|
||||
newIncrement = 300;
|
||||
break;
|
||||
case 4:
|
||||
newIncrement = 1200;
|
||||
break;
|
||||
default:
|
||||
newIncrement = 0;
|
||||
break;
|
||||
}
|
||||
return newIncrement * (level + 1);
|
||||
}
|
||||
|
||||
void UpdateBoard() {
|
||||
short firstLine, numberOfLines;
|
||||
do {
|
||||
CheckLines(firstLine, numberOfLines);
|
||||
for (int i = firstLine; i >= numberOfLines; i--) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
board[i][j] = board[i - numberOfLines][j];
|
||||
board[i - numberOfLines][j] = 0;
|
||||
}
|
||||
}
|
||||
fillrect(0, 0, 162, 320, Black);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
if (board[i][j] != 0) {
|
||||
fillrect(j * DIMENSION, i * DIMENSION, (j + 1) * DIMENSION, (i + 1) * DIMENSION, colors[board[i][j]]);
|
||||
rect(j * DIMENSION, i * DIMENSION, (j + 1) * DIMENSION, (i + 1) * DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
score += UpdateScore(numberOfLines);
|
||||
DrawFigure();
|
||||
} while (numberOfLines != 0);
|
||||
}
|
||||
|
||||
bool IsOver() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (board[0][i] != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowGameOverScreen() {
|
||||
background(Black);
|
||||
foreground(White);
|
||||
locate(60, 120);
|
||||
printf("GAME OVER");
|
||||
locate(40, 150);
|
||||
printf("YOUR SCORE IS %d", score);
|
||||
wait(5);
|
||||
}
|
||||
|
||||
void InitGame() {
|
||||
if (firstTime) {
|
||||
Tetromino(rand() % 7 + 1);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayGame() {
|
||||
InitGame();
|
||||
if (!MoveDown(1)) {
|
||||
OnAttached();
|
||||
UpdateBoard();
|
||||
ShowScore();
|
||||
Tetromino(nextFigure);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
if (IsOver()) {
|
||||
game.detach();
|
||||
ShowGameOverScreen();
|
||||
EndPlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnTasterPressed() {
|
||||
static uint32_t debounce_counter = 0;
|
||||
const uint32_t debounce_threshold = 12;
|
||||
|
||||
if (debounce_counter == 0) {
|
||||
if (!gameStarted) {
|
||||
joystick.detach();
|
||||
gameStarted = true;
|
||||
StartGame();
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]);
|
||||
}
|
||||
debounce_counter = debounce_threshold;
|
||||
} else if (debounce_counter > 0) {
|
||||
debounce_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
void pause_game() {
|
||||
game.detach();
|
||||
joystick.detach();
|
||||
locate(180, 200);
|
||||
printf("PAUSED");
|
||||
while ((get_switches_state().to_ulong() & 0x10) == 0)
|
||||
;
|
||||
printf(" ");
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]);
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::srand(GenerateRandomSeed());
|
||||
Init();
|
||||
ShowLevelMenu();
|
||||
joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TetrisView::TetrisView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
: nav_{nav} {
|
||||
add_children({&dummy});
|
||||
}
|
||||
|
||||
void TetrisView::on_show() {
|
||||
}
|
||||
|
||||
void TetrisView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +556,45 @@ void TetrisView::frame_sync() {
|
|||
}
|
||||
|
||||
bool TetrisView::on_encoder(const EncoderEvent delta) {
|
||||
return check_encoder(delta);
|
||||
if (!gameStarted) {
|
||||
unsigned char old = level;
|
||||
if (delta > 0) {
|
||||
level = (level + 1) % 4;
|
||||
} else if (delta < 0) {
|
||||
(level == 0) ? level = 3 : level--;
|
||||
}
|
||||
if (old != level) {
|
||||
DrawCursor(Black, old);
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
} else if (gameStarted && delta != 0) {
|
||||
Rotate();
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TetrisView::on_key(const KeyEvent key) {
|
||||
return check_key(key);
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
but_RIGHT = (switches_debounced & 0x01) != 0;
|
||||
but_LEFT = (switches_debounced & 0x02) != 0;
|
||||
but_DOWN = (switches_debounced & 0x04) != 0;
|
||||
but_UP = (switches_debounced & 0x08) != 0;
|
||||
but_SELECT = (switches_debounced & 0x10) != 0;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
if (!gameStarted) {
|
||||
OnTasterPressed();
|
||||
} else {
|
||||
Rotate();
|
||||
}
|
||||
} else if (gameStarted) {
|
||||
ReadJoystickForFigure();
|
||||
} else {
|
||||
ReadJoystickForLevel();
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::tetris
|
||||
} // namespace ui::external_app::tetris
|
Loading…
Add table
Add a link
Reference in a new issue