mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-20 21:24:22 -05:00
548 lines
17 KiB
C++
548 lines
17 KiB
C++
// 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
|