diff --git a/firmware/application/external/tetris/Arial12x12.h b/firmware/application/external/tetris/Arial12x12.h deleted file mode 100644 index 4dc451eeb..000000000 --- a/firmware/application/external/tetris/Arial12x12.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 Mark Thompson - * - * 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. - */ - -// dummy include file to avoid changing original source - -#ifndef __UI_Arial12x12_H__ -#define __UI_Arial12x12_H__ - -#define Arial12x12 (0) - -#endif /*__UI_Arial12x12_H__*/ diff --git a/firmware/application/external/tetris/SPI_TFT_ILI9341.h b/firmware/application/external/tetris/SPI_TFT_ILI9341.h deleted file mode 100644 index 8f727f4ef..000000000 --- a/firmware/application/external/tetris/SPI_TFT_ILI9341.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2024 Mark Thompson - * - * 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. - */ - -// "HAL" display layer for Tetris code to run on PortaPack without its original ILI9341 functions - -#ifndef __UI_SPI_TFT_ILI9341_H__ -#define __UI_SPI_TFT_ILI9341_H__ - -ui::Painter painter; - -static int x_pos{0}; -static int y_pos{0}; -static int fg_color; -static int bg_color; - -enum { - White, - Blue, - Yellow, - Purple, - Green, - Red, - Maroon, - Orange, - Black, -}; - -// pp_colors must be in same order as enums above -static const Color pp_colors[] = { - Color::white(), - Color::blue(), - Color::yellow(), - Color::purple(), - Color::green(), - Color::red(), - Color::magenta(), - Color::orange(), - Color::black(), -}; - -// NB: ELIMINATED SPI_TFT_ILI9341 DISPLAY CLASS DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS - -static void claim(__FILE* x) { - (void)x; -}; - -static void cls() { - painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black()); -}; - -static void background(int color) { - bg_color = color; -}; - -static void foreground(int color) { - fg_color = color; -}; - -static void locate(int x, int y) { - x_pos = x; - y_pos = y; -}; - -static void set_orientation(int x) { - (void)x; -}; - -static void set_font(unsigned char* x) { - (void)x; -}; - -static void fillrect(int x1, int y1, int x2, int y2, int color) { - painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]); -}; - -static void rect(int x1, int y1, int x2, int y2, int color) { - painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]); -}; - -static void printf(std::string str) { - auto style = (fg_color == White) ? *ui::Theme::getInstance()->bg_darkest : *ui::Theme::getInstance()->bg_lightest; - painter.draw_string({x_pos, y_pos - 1}, style, str); -}; - -static void printf(std::string str, int v) { - if (str.find_first_of("%") != std::string::npos) { - str.resize(str.find_first_of("%")); // remove %d from end of string - } - printf(str + to_string_dec_uint(v)); -}; - -#endif /*__UI_SPI_TFT_ILI9341_H__*/ diff --git a/firmware/application/external/tetris/mbed.h b/firmware/application/external/tetris/mbed.h deleted file mode 100644 index c74b7b048..000000000 --- a/firmware/application/external/tetris/mbed.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2024 Mark Thompson - * - * 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. - */ - -// "HAL" layer for Tetris code to run on PortaPack without its original mbed OS -// (the dream here was to avoid modifying the original code) - -#ifndef __UI_mbed_H__ -#define __UI_mbed_H__ - -using Callback = void (*)(void); - -#define wait_us(x) (void)0 -#define wait(x) chThdSleepMilliseconds(x * 1000) -#define PullUp 1 - -enum { - dp0, - dp1, - dp2, - dp3, - dp4, - dp5, - dp6, - dp7, - dp8, - dp9, - dp10, - dp11, - dp12, - dp13, - dp14, - dp15, - dp16, - dp17, - dp18, - dp19, - dp20, - dp21, - dp22, - dp23, - dp24, - dp25, -}; - -static bool but_RIGHT; -static bool but_LEFT; -static bool but_UP; -static bool but_DOWN; -static bool but_SELECT; - -// -// AnalogIn Class -- DID NOT WORK DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS -- hacked original code module instead -// -// dp9 = joystick rotate button --> select button -// dp10 = joystick y --> up & down buttons -// dp11 = joystick x --> left & right buttons -// dp13 = random number generator -// -// -// class AnalogIn { -// public: -// AnalogIn(uint32_t analog_input) { -// // FIXME - THIS CODE NEVER GETS EXECUTED! -// analog_input_ = analog_input; -// }; -// -// // Tetris code only uses this function for dp13 - supposed to be a random number -// uint16_t read_u16() { -// return std::rand(); -// }; -// -// // Tetris code uses read() function for direction buttons only -// float read() { -// float retval = 0.5; -// switch (analog_input_) { -// case dp11: -// if (but_LEFT) -// retval = 0.0; -// else if (but_RIGHT) -// retval = 1.0; -// break; -// -// case dp10: -// if (but_UP) -// retval = 0.0; -// else if (but_DOWN) -// retval = 1.0; -// break; -// } -// return retval; -// }; -// -// operator float() { -// return read(); -// }; -// -// private: -// uint32_t analog_input_{INT_MAX}; -// }; - -// -// Timer Class -// (Timer object was used for unneeded button debouncing, so just returning 1000ms to indicate we've waited long enough) -// -class Timer { - public: - // NOTE: INITIALIZER CODE WON'T RUN - Timer() { (void)0; }; - void reset() { (void)0; }; - void start() { (void)0; } - uint32_t read_ms() { return 1000; }; - - private: -}; - -// -// Ticker Class -// (Ticker is timed callback, used for checking "joystick" directional switches and when time to move piece down a row) -// -// NB: Only one callback is supported per Ticker class instantiation -static Callback fall_timer_callback; -static uint32_t fall_timer_timeout; -static uint32_t fall_timer_counter; - -static Callback dir_button_callback; - -static void check_fall_timer() { - if (fall_timer_callback) { - if (++fall_timer_counter >= fall_timer_timeout) { - fall_timer_counter = 0; - fall_timer_callback(); - } - } -} - -class Ticker { - public: - // NOTE: INITIALIZER CODE WON'T RUN - Ticker() { (void)0; }; - - void attach(Callback func, double delay_sec) { - // 0.3 sec is requested only for button check -- kludge to use on_key callback for this one instead of timer - if (delay_sec == 0.3) { - dir_button_callback = func; - } else { - fall_timer_callback = func; - fall_timer_timeout = delay_sec * 60; // timer interrupts at 60 Hz - } - } - - void detach() { - // shouldn't detach both, but don't know how to tell which object is which - dir_button_callback = nullptr; - fall_timer_callback = nullptr; - } - - private: -}; - -// -// InterruptIn Class -// (just used for the Select button) -// -static Callback sel_button_callback; - -static bool check_encoder(const EncoderEvent delta) { - (void)delta; - // TODO: consider adding ability to rotate Tetronimo via encoder too - return false; -} - -static bool check_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 (sel_button_callback) - sel_button_callback(); - } else { - if (dir_button_callback) - dir_button_callback(); - } - return true; -} - -class InterruptIn { - public: - InterruptIn(int reg) { - // NOTE: INITIALIZER CODE WON'T RUN - (void)reg; - }; - void fall(Callback func) { sel_button_callback = func; }; - void mode(int v) { (void)v; }; - - private: -}; - -#endif /*__UI_mbed_H__*/ diff --git a/firmware/application/external/tetris/tetris.cpp b/firmware/application/external/tetris/tetris.cpp deleted file mode 100644 index c6e2fb957..000000000 --- a/firmware/application/external/tetris/tetris.cpp +++ /dev/null @@ -1,548 +0,0 @@ -// Projekat Tetris -// by Vrnjak Lamija & Selimović Denis -// Elektrotehnički fakultet Sarajevo - -// clang-format off - -//////// HACKED FOR PORTAPACK -- CHANGES HIGHLIGHTED -int main(); -void pause_game(); -void Initialize(unsigned char c); -void DeleteFigure(); -void DrawFigure(); -bool InCollisionDown(char delta); -bool InCollisionLeft(); -bool InCollisionRight(); -//////// PORTAPACK - -#include "mbed.h" -#include "SPI_TFT_ILI9341.h" -#include "Arial12x12.h" - -#define dp23 P0_0 - -//////// PORTAPACK - DISABLED MOST CLASSES DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS: -//deklaracija display-a -//SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT"); -// -//analogni ulazi za joystick -// AnalogIn VRx(dp11); -// AnalogIn VRy(dp10); -// -// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti -//////// PORTAPACK - -//taster na joysticku za rotaciju -InterruptIn taster(dp9); - -//ticker za spustanje figure -//timer za debouncing tastera na joysticku -Ticker game, joystick; -Timer debounceTaster; - -unsigned char level = 0; //mora biti tipa usigned char jer inače se može desiti da level bude manji od 0, a i da ne trošimo memoriju -const float delays[4] = {1.2, 0.7, 0.4, 0.25}; //svakih koliko se spusti jedan red, ovo provjeriti da li je presporo ili prebrzo, ovisi o levelu - -//////// PORTAPACK - DELETED UNNEEDED JOYSTICK HYSTERESIS VARIABLES -//char leftBoundary = 1, rightBoundary = 5, downBoundary = 1, upBoundary = 5;// sada je ovo tipa char -//////// PORTAPACK - -unsigned int score = 0; //stavio sam ovo unsigned int za veći opseg, mada je jako teško da se i int premaši, ali nmvz -bool firstTime = true; //ako je prvi put, figura se crta u Tickeru -bool gameStarted = false; -unsigned char nextFigure = 1; //ovo je sad globalna varijabla, da bi mogli unaprijed generisati sljedeću figuru radi prikaza - -//white - no figure -//I - BLUE -//O - YELLOW -//T - PURPLE -//S - GREEN -//Z - RED -//J - MAROON -//L - ORANGE -const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange}; -const short DIMENSION = 16; -const short DIMENSION_NEXT = 12; -short board[20][10] = {0}; //matrica boja, 0 - 7 indeksi boja - -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}}; - -unsigned int GenerateRandomSeed() { -//////// PORTAPACK - USE RTC FOR SEED -return LPC_RTC->CTIME0; -//////// PORTAPACK -} - -void Init() { - //ovo su zajedničke osobine za sve prikaze na display-u - //nikad se ne mijenjaju i pozvat ćemo je jednom prije petlje - claim(stdout); - set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat - set_font((unsigned char*) Arial12x12); -} - - -void ShowScore() { - //pomocna funkcija za prikazivanje score-a - fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score - locate(200, 35); //valjda je na sredini pravougaonika - printf("%d", score); -} - -void ShowNextFigure() { - //prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure - fillrect(165, 70, 235, 120, 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); - } -} - -//funkcija za crtanje cursora za odabir levela -void DrawCursor(int color, unsigned char lev) { - fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color); -} - -// PORTAPACK - ADDED EXTRA LEVEL: -void ShowLevelMenu() { - //ovdje inicijalizujemo display - cls(); // brišemo prethodno - 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); -} -//////// PORTAPACK - -//////// PORTAPACK - USE BUTTONS VS JOYSTICK: -void ReadJoystickForLevel(){ - unsigned char old = level; - if(but_UP){ - (level == 0) ? level = 3 : level--; - } - else if(but_DOWN){ - //ne radi ona prethodna varijanta jer % vraća i negastivni rezultat - //to što ne koristimo unsigned tip ne pomaže jer će doći do overflow-a - level = (level + 1) % 4; - } - DrawCursor(Black, old); //na prethodni level popunimo bojom pozadine - DrawCursor(White, level); //na novi level popunimo bijelom bojom - pozadina je crna - //koristio sam fillrect, jer njega svakako moramo koristiti, jer možda budemo morali da brišemo fillcircle iz biblioteke -} -//////// PORTAPACK - -void EndPlay() { - joystick.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; - } - } -//////// PORTAPACK - FIX TO REINITIALIZE SCREEN -// ShowLevelMenu(); -// joystick.attach(&ReadJoystickForLevel, 0.3); -main(); -//////// PORTAPACK -} - -void StartGame() -{ - cls(); // brišemo ShowLevelMenu - background(White); - foreground(Black); - fillrect(0, 0, 160, 320, White); - fillrect(160, 0, 240, 320, Black); //dio za prikazivanje rezultata će biti crni pravougaonik, a tabla je bijeli - ShowScore(); -} - -void copyCoordinates(short X[], short Y[], unsigned char index) -{ - //umjesto one prošle metode za kopiranje, ova prima index, čisto da nam olakša život - 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); - } - } -} - -//////// PORTAPACK - ELIMINATED CLASSES DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS: -//class Tetromino{ -//private: - short X[4]; - short Y[4]; - short boardX, boardY; - unsigned char colorIndex;//dodao sam colorIndex zasad, jer nema drugog načina da popunimo matricu sa indeksima boja - //ovo je najbezbolnija varijanta što se memorije tiče -//public: -// Tetromino(){ -// unsigned char r = rand() % 7 + 1; -// Initialize(r); -// } - - void Tetromino(unsigned char c) { - Initialize(c); - } - - void Initialize(unsigned char c) { - colorIndex = c; - boardX = 0; - boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display - copyCoordinates(X, Y, c - 1); - } - - void Rotate(){ - short pivotX = X[1], pivotY = Y[1]; - //prvi elemnti u matrici su pivoti oko koje rotiramo - - short newX[4]; //pozicije blokova nakon rotiranja ako sve bude ok - 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++) { - //display je deklarisani display logy iz biblioteke one - //računamo gornji lijevi pixel po x i y - //donji desni dobijemo kad dodamo DIMENZIJU - //stavio sam 16 za početak, možemo se opet skontati na labu - //ovo pretpostavlja da nema margina, mogu se lagano dodati uz neku konstantu kao offset - int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION; - fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]); - //ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa - rect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black); - } - } - - void DeleteFigure() { - for(int i = 0; i < 4; i++) { - //ista logika kao u DrawFigure, samo popunjavamo sve blokove sa bijelim pravougaonicima - short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION; - fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, White); - PutBorders(upperLeftY, upperLeftX); - } - } - - void OnAttached() { - //metoda se poziva kad figura prestanje da se krece - //popunimo matricu indeksom boje - for(int i = 0; i < 4; i++) { - board[boardX + X[i]][boardY + Y[i]] = colorIndex; - //btw board bi mogao biti niz tipa unsigned char, ali to ćemo vidjet kasnije - } - } - - bool MoveDown(char delta = 1){ - 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() { - //ovo je funkcija za soft drop - //obrisemo figuru, postavimo novu poziciju dva reda ispod, nacrtamo figuru - DeleteFigure(); - MoveDown(2); - DrawFigure(); - //treba još vidjeti koje izmjene u tickeru trebaju - score += 2 * (level +1); //prema igrici koju smo igrali, dobije se 14 poena kad se uradi hard drop - ShowScore(); - } - - - bool InCollisionDown(char delta = 1){ - int newX, newY; //da bi bilo citljivije - - 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; - } - //jedna figura je u koliziji ako - //pozicija na koju zelimo da pomjerimo bilo koji blok dotakne dno ili lijevu ili desnu ivicu ekrana - //ili ako je pozicija na koju zelimo da pomjerimo bilo koji od blokova vec zauzeta a da nije dio figure prije pomijeranja - } - - 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; - } -// }; - -// Tetromino currentTetromino; - -//////// PORTAPACK - USE BUTTONS VS JOYSTICK, AND ADDED PAUSE FEATURE: -void ReadJoystickForFigure() { - if(but_LEFT) { - MoveLeft(); - } - else if(but_RIGHT) { - MoveRight(); - } - else if(but_UP) { - pause_game(); - } - else if(but_DOWN) { - SoftDrop(); - } -} -//////// PORTAPACK - -void CheckLines(short &firstLine, short &numberOfLines) -{ - //vraća preko reference prvu liniju koja je popunjena do kraja, kao i takvih linija - firstLine = -1; //postavljen na -1 jer ako nema linija koje treba brisati ispod u UpdateBoard neće se ući u petlju - 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; - }//ako je makar jedna bijela, prekida se brojanje - temp++; - } - if(temp == 10) { //ako je temo došao do 10, niti jedna bijela - puna linija - numberOfLines++; - if(firstLine == -1) firstLine = i; //ovo mijenjamo samo prvi put - } - } -} - -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; //update funkcije za score, još sam vratio ovo na 0 - } - return newIncrement * (level + 1); -} - -void UpdateBoard() -{ - short firstLine, numberOfLines; - //pozivamo funkciju - 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; - short tmp = i - numberOfLines; - fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok - fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White); - if(board[i][j] != 0) - rect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , Black); - } - } - score += UpdateScore(numberOfLines); - } - while(numberOfLines != 0); //ovdje se mijenja globalna varijabla score -} - -bool IsOver() { - for(int i = 0; i < 10; i++) { - if(board[0][i] != 0) return true; - } - return false; -} - -void ShowGameOverScreen() { -//////// PORTAPACK - SKIP CLS -// cls(); -// background(Black); -// foreground(White); -background(White); -foreground(Black); -//////// PORTAPACK - locate(60, 120); - printf("GAME OVER"); - locate(40, 150); - printf("YOUR SCORE IS %d", score); - wait(5); //ovaj prikaz traje 3s (možemo mijenjati) a nakon toga se ponovo prikazuje meni sa levelima -} - -void InitGame() { - if(firstTime) { - Tetromino(rand() % 7 + 1); - DrawFigure(); - nextFigure = rand() % 7 + 1; - ShowNextFigure(); - firstTime = false; - } -} - -void PlayGame(){ - InitGame(); - if(!MoveDown()){ - OnAttached(); - UpdateBoard(); - ShowScore(); - - Tetromino(nextFigure); - DrawFigure(); - nextFigure = rand() % 7 + 1; - ShowNextFigure(); - if(IsOver()) { - //ako je igra završena brišemo sve sa displey-a, prikazujemo poruku i score - //takođe moramo dettach-at ticker - game.detach(); - ShowGameOverScreen(); //prikaz da je kraj igre, ima wait od 3s - EndPlay(); - } - } - -} - -void OnTasterPressed(){ - if(debounceTaster.read_ms() > 200) { - if(gameStarted){ - Rotate(); - } - else{ - joystick.detach(); - gameStarted = true; - StartGame(); //pocinje igra, prikazuje se tabla - joystick.attach(&ReadJoystickForFigure, 0.3); - game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize - } - debounceTaster.reset(); - } -} - -void SetTaster() { - taster.mode(PullUp); //mora se aktivirati pull up otpornik na tasteru joystick-a - taster.fall(&OnTasterPressed); - debounceTaster.reset(); - debounceTaster.start(); -} - -int main() { - srand(GenerateRandomSeed()); - Init(); - ShowLevelMenu(); - joystick.attach(&ReadJoystickForLevel, 0.3); - SetTaster(); - -//////// PORTAPACK: CAN'T HANG HERE IN MAYHEM OR WE WON'T GET ANY CALLBACKS -// while(1); -return 0; -//////// PORTAPACK -} - -//////// PORTAPACK - ADDED PAUSE FEATURE: -void pause_game() { - game.detach(); - joystick.detach(); - locate(180, 200); - printf("PAUSED"); - while ((get_switches_state().to_ulong() & 0x10) == 0); // wait for SELECT button to resume - printf(" "); - joystick.attach(&ReadJoystickForFigure, 0.3); - game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize -}; -//////// PORTAPACK \ No newline at end of file diff --git a/firmware/application/external/tetris/ui_tetris.cpp b/firmware/application/external/tetris/ui_tetris.cpp index b236b2382..5e31e1205 100644 --- a/firmware/application/external/tetris/ui_tetris.cpp +++ b/firmware/application/external/tetris/ui_tetris.cpp @@ -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 \ No newline at end of file diff --git a/firmware/application/external/tetris/ui_tetris.hpp b/firmware/application/external/tetris/ui_tetris.hpp index 44bb77885..c917b8b7b 100644 --- a/firmware/application/external/tetris/ui_tetris.hpp +++ b/firmware/application/external/tetris/ui_tetris.hpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2024 Mark Thompson + * 2025 updates by RocketGod (https://betaskynet.com/) * * This file is part of PortaPack. * @@ -22,6 +23,7 @@ #ifndef __UI_TETRIS_H__ #define __UI_TETRIS_H__ +#include "ui.hpp" #include "ui_navigation.hpp" #include "event_m0.hpp" #include "message.hpp" @@ -29,16 +31,113 @@ #include "random.hpp" #include "lpc43xx_cpp.hpp" #include "limits.h" +#include "ui_widget.hpp" namespace ui::external_app::tetris { +enum { + White, + Blue, + Yellow, + Purple, + Green, + Red, + Maroon, + Orange, + Black, +}; + +extern const Color pp_colors[]; +extern Painter painter; +extern bool but_RIGHT; +extern bool but_LEFT; +extern bool but_UP; +extern bool but_DOWN; +extern bool but_SELECT; + +void cls(); +void background(int color); +void foreground(int color); +void locate(int x, int y); +void fillrect(int x1, int y1, int x2, int y2, int color); +void rect(int x1, int y1, int x2, int y2, int color); +void printf(std::string str); +void printf(std::string str, int v); + +#define wait(x) chThdSleepMilliseconds(x * 1000) + +using Callback = void (*)(void); + +class Ticker { + public: + Ticker() = default; + void attach(Callback func, double delay_sec); + void detach(); +}; + +extern Ticker game; +extern Ticker joystick; + +extern unsigned char level; +extern const float delays[4]; +extern unsigned int score; +extern bool firstTime; +extern bool gameStarted; +extern unsigned char nextFigure; +extern short board[20][10]; +extern const int colors[8]; +extern const short DIMENSION; +extern const short DIMENSION_NEXT; +extern short figuresX[7][4]; +extern short figuresY[7][4]; + +unsigned int GenerateRandomSeed(); +void Init(); +void ShowScore(); +void ShowNextFigure(); +void DrawCursor(int color, unsigned char lev); +void ShowLevelMenu(); +void ReadJoystickForLevel(); +void EndPlay(); +void StartGame(); +void copyCoordinates(short X[], short Y[], unsigned char index); +bool BottomEdge(int x); +bool LeftEdge(int y); +bool RightEdge(int y); +bool OutOfBounds(int y, int x); +void PutBorders(short x, short y); +void Tetromino(unsigned char c); +void Initialize(unsigned char c); +void Rotate(); +void DrawFigure(); +void DeleteFigure(); +void OnAttached(); +bool MoveDown(char delta); +void MoveLeft(); +void MoveRight(); +void SoftDrop(); +bool InCollisionDown(char delta); +bool InCollisionLeft(); +bool InCollisionRight(); +void ReadJoystickForFigure(); +void CheckLines(short& firstLine, short& numberOfLines); +unsigned int UpdateScore(short numOfLines); +void UpdateBoard(); +bool IsOver(); +void ShowGameOverScreen(); +void InitGame(); +void PlayGame(); +void OnTasterPressed(); +void pause_game(); + class TetrisView : public View { public: TetrisView(NavigationView& nav); + void on_show() override; - std::string title() const override { return "Tetris"; }; + std::string title() const override { return "Tetris"; } - void focus() override { dummy.focus(); }; + void focus() override { dummy.focus(); } void paint(Painter& painter) override; void frame_sync(); bool on_encoder(const EncoderEvent event) override; @@ -61,4 +160,4 @@ class TetrisView : public View { } // namespace ui::external_app::tetris -#endif /*__UI_TETRIS_H__*/ +#endif /* __UI_TETRIS_H__ */ \ No newline at end of file