diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 06c359b5..c4fc0c4c 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -3,7 +3,11 @@ set(EXTCPPSRC #pacman external/pacman/main.cpp external/pacman/ui_pacman.cpp - + + #tetris + external/tetris/main.cpp + external/tetris/ui_tetris.cpp + #afsk_rx external/afsk_rx/main.cpp external/afsk_rx/ui_afsk_rx.cpp @@ -19,7 +23,7 @@ set(EXTCPPSRC #blespam external/blespam/main.cpp external/blespam/ui_blespam.cpp - + #analogtv external/analogtv/main.cpp external/analogtv/analog_tv_app.cpp @@ -35,13 +39,12 @@ set(EXTCPPSRC #lge external/lge/main.cpp external/lge/lge_app.cpp - + #lcr external/lcr/main.cpp external/lcr/ui_lcr.cpp - - - #lcr + + #jammer external/jammer/main.cpp external/jammer/ui_jammer.cpp @@ -76,4 +79,5 @@ set(EXTAPPLIST gpssim spainter keyfob + tetris ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index f4b52e59..c2449335 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -31,6 +31,7 @@ MEMORY ram_external_app_gpssim(rwx) : org = 0xEEF40000, len = 32k ram_external_app_spainter(rwx) : org = 0xEEF50000, len = 32k ram_external_app_keyfob(rwx) : org = 0xEEF60000, len = 32k + ram_external_app_tetris(rwx) : org = 0xEEF70000, len = 32k } SECTIONS @@ -59,7 +60,7 @@ SECTIONS *(*ui*external_app*font_viewer*); } > ram_external_app_font_viewer - + .external_app_blespam : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_blespam.application_information)); @@ -77,33 +78,33 @@ SECTIONS KEEP(*(.external_app.app_nrf_rx.application_information)); *(*ui*external_app*nrf_rx*); } > ram_external_app_nrf_rx - + .external_app_coasterp : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_coasterp.application_information)); *(*ui*external_app*coasterp*); } > ram_external_app_coasterp - + .external_app_lge : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_lge.application_information)); *(*ui*external_app*lge*); } > ram_external_app_lge - + .external_app_lcr : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_lcr.application_information)); *(*ui*external_app*lcr*); } > ram_external_app_lcr - + .external_app_jammer : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_jammer.application_information)); *(*ui*external_app*jammer*); } > ram_external_app_jammer - + .external_app_gpssim : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_gpssim.application_information)); @@ -123,4 +124,10 @@ SECTIONS *(*ui*external_app*keyfob*); } > ram_external_app_keyfob + .external_app_tetris : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_tetris.application_information)); + *(*ui*external_app*tetris*); + } > ram_external_app_tetris + } diff --git a/firmware/application/external/tetris/Arial12x12.h b/firmware/application/external/tetris/Arial12x12.h new file mode 100644 index 00000000..4dc451ee --- /dev/null +++ b/firmware/application/external/tetris/Arial12x12.h @@ -0,0 +1,29 @@ +/* + * 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 new file mode 100644 index 00000000..38e4986d --- /dev/null +++ b/firmware/application/external/tetris/SPI_TFT_ILI9341.h @@ -0,0 +1,102 @@ +/* + * 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(), +}; + +class SPI_TFT_ILI9341 { + public: + SPI_TFT_ILI9341(int, int, int, int, int, int, std::string) { (void)0; }; + + void claim(__FILE* x) { (void)x; }; + + 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 set_orientation(int x) { (void)x; }; + void set_font(unsigned char* x) { (void)x; }; + + 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]); + }; + + private: +}; + +static void printf(std::string str) { + auto style = (fg_color == White) ? ui::Styles::white : ui::Styles::bg_white; + 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/main.cpp b/firmware/application/external/tetris/main.cpp new file mode 100644 index 00000000..6a2134c5 --- /dev/null +++ b/firmware/application/external/tetris/main.cpp @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#include "ui.hpp" +#include "ui_tetris.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::tetris { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::tetris + +extern "C" { + +__attribute__((section(".external_app.app_tetris.application_information"), used)) application_information_t _application_information_tetris = { + /*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time + /*.externalAppEntry = */ ui::external_app::tetris::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "Tetris", + /*.bitmap_data = */ { + 0xF8, + 0xFF, + 0x88, + 0x88, + 0x88, + 0x88, + 0x88, + 0x88, + 0xF8, + 0xFF, + 0x80, + 0x08, + 0x80, + 0x08, + 0x9F, + 0x08, + 0x91, + 0x0F, + 0x11, + 0x00, + 0x11, + 0x00, + 0xFF, + 0xF1, + 0x11, + 0x91, + 0x11, + 0x91, + 0x11, + 0x91, + 0xFF, + 0xF1, + }, + /*.icon_color = */ ui::Color::orange().v, + /*.menu_location = */ app_location_t::UTILITIES, + + /*.m4_app_tag = portapack::spi_flash::image_tag_noop */ {'\0', '\0', '\0', '\0'}, // optional + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/tetris/mbed.h b/firmware/application/external/tetris/mbed.h new file mode 100644 index 00000000..87947432 --- /dev/null +++ b/firmware/application/external/tetris/mbed.h @@ -0,0 +1,220 @@ +/* + * 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 BECAUSE INITIALIZER CODE WON'T EXECUTE -- 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 new file mode 100644 index 00000000..ab6fac26 --- /dev/null +++ b/firmware/application/external/tetris/tetris.cpp @@ -0,0 +1,578 @@ +// Projekat Tetris +// by Vrnjak Lamija & Selimović Denis +// Elektrotehnički fakultet Sarajevo + +// clang-format off + +//////// PORTAPACK CHANGES HIGHLIGHTED +int main(); +void pause_game(); +//////// PORTAPACK + +#include "mbed.h" +#include "SPI_TFT_ILI9341.h" +#include "Arial12x12.h" + +#define dp23 P0_0 + +//deklaracija display-a +SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT"); + +//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING: +//analogni ulazi za joystick +// AnalogIn VRx(dp11); +// AnalogIn VRy(dp10); +//////// PORTAPACK + +//taster na joysticku za rotaciju +InterruptIn taster(dp9); + +//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING: +// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti +//////// PORTAPACK + +//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 - 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; + // unsigned int randomNumber = 0; + // for(int i = 0; i <= 32; i += 2) { + // randomNumber += ((random.read_u16() % 3) << i); + // wait_us(10); + // } + // return randomNumber; +//////// 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 + display.claim(stdout); + display.set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat + display.set_font((unsigned char*) Arial12x12); +} + + +void ShowScore() { + //pomocna funkcija za prikazivanje score-a + display.fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score + display.locate(200, 35); //valjda je na sredini pravougaonika + printf("%d", score); +} + +void ShowNextFigure() { + //prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure + display.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]; + display.fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]); + display.rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black); + } +} + +//funkcija za crtanje cursora za odabir levela +void DrawCursor(int color, unsigned char lev) { + display.fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color); +} + +// PORTAPACK - ADDED EXTRA LEVEL: +void ShowLevelMenu() { + //ovdje inicijalizujemo display + display.cls(); // brišemo prethodno + display.background(Black); + display.foreground(White); + display.locate(80, 50); + printf("LEVEL 1"); + display.locate(80, 120); + printf("LEVEL 2"); + display.locate(80, 190); + printf("LEVEL 3"); + display.locate(80, 260); + printf("LEVEL 4"); + DrawCursor(White, level); +} + +//////// PORTAPACK - KLUDGED FOR BUTTONS VS JOYSTICK: +void ReadJoystickForLevel(){ + unsigned char old = level; + if(but_UP){ +// upBoundary = 4; + (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 +// downBoundary = 2; + level = (level + 1) % 4; + } + else { +// downBoundary = 1; +// upBoundary = 5; + } + 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() +{ + display.cls(); // brišemo ShowLevelMenu + display.background(White); + display.foreground(Black); + display.fillrect(0, 0, 160, 320, White); + display.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; + display.rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black); + } + } +} + +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(){ +//////// PORTAPACK - NOTE - DEFAULT INITIALIZER CODE DOESN'T GET EXECUTED FOR SOME REASON: + unsigned char r = rand() % 7 + 1; + Initialize(r); + } + + Tetromino(unsigned char colorIndex) { + Initialize(colorIndex); + } + + void Initialize(unsigned char colorIndex) { + Tetromino::colorIndex = colorIndex; + boardX = 0; + boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display + copyCoordinates(X, Y, colorIndex - 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; + display.fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]); + //ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa + +//////// PORTAPACK - HIDE DEFAULT WHITE BLOCK (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION) +if (colorIndex != White) +//////// PORTAPACK + display.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; + display.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){ +//////// PORTAPACK - MOVE DEFAULT WHITE BLOCK TO BOTTOM IMMEDIATELY (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION) +if (colorIndex == White) delta = 19; +//////// PORTAPACK + 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 - KLUDGED FOR BUTTONS VS JOYSTICK: +void ReadJoystickForFigure() { + if(but_LEFT) { +// leftBoundary = 2; + currentTetromino.MoveLeft(); + } + else if(but_RIGHT) { +// rightBoundary = 4; + currentTetromino.MoveRight(); + } + else if(but_UP) { +// downBoundary = 2; +pause_game(); + } + else if(but_DOWN) { +// upBoundary = 4; + currentTetromino.SoftDrop(); + } + else { +// leftBoundary = 1; +// rightBoundary = 5; +// downBoundary = 1; +// upBoundary = 5; + } +} +//////// 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; + display.fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok + display.fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White); + if(board[i][j] != 0) + display.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 +// display.cls(); +// display.background(Black); +// display.foreground(White); +display.background(White); +display.foreground(Black); +//////// PORTAPACK + display.locate(60, 120); + printf("GAME OVER"); + display.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) { +//////// PORTAPACK - NOTE - ATTEMPTED WORKAROUND FOR SKIPPED INITIALIZER CODE - BUT ANY OF THESE CRASHES FIRMWARE AT POWER-UP EVEN IF THERE'S NO TETRIS APP INSTALLED: +// currentTetromino = Tetromino(rand() % 7 + 1); // TEST #1 +// currentTetromino.Initialize(rand() % 7 + 1); // TEST #2 +//////// PORTAPACK + currentTetromino.DrawFigure(); + nextFigure = rand() % 7 + 1; + ShowNextFigure(); + firstTime = false; + } +} + +void PlayGame(){ + InitGame(); + if(!currentTetromino.MoveDown()){ + currentTetromino.OnAttached(); + UpdateBoard(); + ShowScore(); + + currentTetromino = Tetromino(nextFigure); + currentTetromino.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){ + currentTetromino.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(); + display.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 new file mode 100644 index 00000000..fa3ab822 --- /dev/null +++ b/firmware/application/external/tetris/ui_tetris.cpp @@ -0,0 +1,39 @@ +#include "ui_tetris.hpp" + +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 + +TetrisView::TetrisView(NavigationView& nav) + : nav_(nav) { + add_children({&dummy}); +} + +void TetrisView::paint(Painter& painter) { + (void)painter; + + if (!initialized) { + initialized = true; + std::srand(LPC_RTC->CTIME0); + main(); + } +} + +void TetrisView::frame_sync() { + check_fall_timer(); + set_dirty(); +} + +bool TetrisView::on_encoder(const EncoderEvent delta) { + return check_encoder(delta); +} + +bool TetrisView::on_key(const KeyEvent key) { + return check_key(key); +} + +} // namespace ui::external_app::tetris diff --git a/firmware/application/external/tetris/ui_tetris.hpp b/firmware/application/external/tetris/ui_tetris.hpp new file mode 100644 index 00000000..8fc71d69 --- /dev/null +++ b/firmware/application/external/tetris/ui_tetris.hpp @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#ifndef __UI_TETRIS_H__ +#define __UI_TETRIS_H__ + +#include "ui_navigation.hpp" +#include "event_m0.hpp" +#include "message.hpp" +#include "irq_controls.hpp" +#include "ui_styles.hpp" +#include "random.hpp" +#include "lpc43xx_cpp.hpp" +#include "limits.h" + +namespace ui::external_app::tetris { + +class TetrisView : public View { + public: + TetrisView(NavigationView& nav); + + std::string title() const override { return "Tetris"; }; + + void focus() override { dummy.focus(); }; + void paint(Painter& painter) override; + void frame_sync(); + bool on_encoder(const EncoderEvent event) override; + bool on_key(KeyEvent key) override; + + private: + bool initialized = false; + NavigationView& nav_; + + Button dummy{ + {240, 0, 0, 0}, + ""}; + + MessageHandlerRegistration message_handler_frame_sync{ + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->frame_sync(); + }}; +}; + +} // namespace ui::external_app::tetris + +#endif /*__UI_TETRIS_H__*/ diff --git a/firmware/graphics/icon_tetris.png b/firmware/graphics/icon_tetris.png new file mode 100644 index 00000000..50c08bab Binary files /dev/null and b/firmware/graphics/icon_tetris.png differ