mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-04-01 06:22:15 +00:00
600 lines
14 KiB
C++
600 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2024 Mark Thompson
|
|
* 2025 updates by RocketGod (https://betaskynet.com/)
|
|
*
|
|
* This file is part of PortaPack.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "ui_tetris.hpp"
|
|
|
|
namespace ui::external_app::tetris {
|
|
|
|
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} {
|
|
add_children({&dummy});
|
|
}
|
|
|
|
void TetrisView::on_show() {
|
|
}
|
|
|
|
void TetrisView::paint(Painter& painter) {
|
|
(void)painter;
|
|
if (!initialized) {
|
|
initialized = true;
|
|
main();
|
|
}
|
|
}
|
|
|
|
void TetrisView::frame_sync() {
|
|
check_fall_timer();
|
|
set_dirty();
|
|
}
|
|
|
|
bool TetrisView::on_encoder(const EncoderEvent 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) {
|
|
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
|