Tetris: Combined cpp files. Helper files into hpp. Dark mode. Encoder on. (#2587)

This commit is contained in:
RocketGod 2025-03-23 13:43:30 -07:00 committed by GitHub
parent 20c64c98bd
commit 4b000c8da6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 653 additions and 921 deletions

View File

@ -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__*/

View File

@ -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__*/

View File

@ -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__*/

View File

@ -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

View File

@ -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

View File

@ -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__ */