#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#define I2C_ADDRESS 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const uint8_t BTN_UP = 7;
const uint8_t BTN_DOWN = 9;
const uint8_t BTN_LEFT = 10;
const uint8_t BTN_RIGHT = 8;
const uint8_t BTN_START = 4;
const uint8_t BTN_INFO = 5;
enum GameState { //lista możliwych trybów
STATE_TITLE, // Gra wykonuje jeden tryb naraz bo widzieć co może robić a czego nie
STATE_PLAYER_TURN,
STATE_ENEMY_TURN,
STATE_MESSAGE,
STATE_GAME_OVER,
STATE_MOVE_INFO
};
GameState gameState = STATE_TITLE; //zmienna przechowująca bieżący stan gry
enum PokeType { //lista możliwych typów pokemonów
TYPE_WATER,
TYPE_GHOST,
TYPE_FIRE,
TYPE_PSYCHIC,
TYPE_DRAGON,
TYPE_ELECTRIC,
TYPE_ICE,
TYPE_NORMAL,
TYPE_STEEL
};
enum Species { //lista możliwych pokemonów
PK_LAPRAS,
PK_GENGAR,
PK_RAIKOU,
PK_MAWILE,
PK_LUGIA,
PK_LATIAS,
PK_MEWTWO,
PK_ZAPDOS,
PK_MOLTRES,
PK_ARTICUNO,
PK_MEW,
PK_ARCEUS
};
struct MoveData { //pełen opis ataku pokeomna
const __FlashStringHelper* name;
PokeType type;
uint8_t power;
bool heals;
uint8_t healAmount;
};
struct Fighter { //pełen opis pokemona
uint8_t species;
PokeType type;
int hp;
int maxHp;
};
Fighter playerTeam[6];
Fighter enemyTeam[6];
uint8_t playerActive = 0; //zmienna mowiąca który twój pokemon jest teraz aktywny
uint8_t enemyActive = 0; //zmienna mowiąca który pokemon przeciwnika jest teraz aktywny
uint8_t selectedMove = 0; //zmienna mowiąca który atak jest zaznaczony
bool needRedraw = true; //zmiena stwierdzająca czy należy narysować gre od nowa
bool enemyNeedsTurn = false; //zminna mowiąca czy przeciwnik ma się ruszyć po danej akcji
unsigned long lastInputTime = 0;
const unsigned long inputDelay = 280; //zmienna która robi delay by jedno klikniecie przycisku nie było widzane
// jako kliniecie wielokrotne tego samego przycisku
char messageLine1[24];
char messageLine2[24];
unsigned long messageStart = 0;
unsigned long messageDuration = 900;
const __FlashStringHelper* typeName(PokeType t) { //funkcja która wypisuje typ pokomona jako text
switch (t) {
case TYPE_WATER: return F("WATER");
case TYPE_GHOST: return F("GHOST");
case TYPE_FIRE: return F("FIRE");
case TYPE_PSYCHIC: return F("PSY");
case TYPE_DRAGON: return F("DRAGON");
case TYPE_ELECTRIC: return F("ELEC");
case TYPE_ICE: return F("ICE");
case TYPE_STEEL: return F("STEEL");
default: return F("NORM");
}
}
const __FlashStringHelper* strongVsText(PokeType t) { //funckja która zwraca na co jest dany atak jest mocny
switch (t) {
case TYPE_WATER: return F("STRONG: FIRE");
case TYPE_GHOST: return F("STRONG: PSY");
case TYPE_FIRE: return F("STRONG: ICE");
case TYPE_PSYCHIC: return F("STRONG: GHOST");
case TYPE_DRAGON: return F("STRONG: DRAGON");
case TYPE_ELECTRIC: return F("STRONG: WATER");
case TYPE_ICE: return F("STRONG: DRAGON");
case TYPE_STEEL: return F("STRONG: ICE");
case TYPE_NORMAL: return F("STRONG: NONE");
default: return F("STRONG: NONE");
}
}
const __FlashStringHelper* weakVsText(PokeType t) {
switch (t) {
case TYPE_WATER: return F("WEAK: ELEC");
case TYPE_GHOST: return F("WEAK: NORM");
case TYPE_FIRE: return F("WEAK: WATER");
case TYPE_PSYCHIC: return F("WEAK: PSY");
case TYPE_DRAGON: return F("WEAK: ICE");
case TYPE_ELECTRIC: return F("WEAK: DRAGON");
case TYPE_ICE: return F("WEAK: FIRE");
case TYPE_STEEL: return F("WEAK: FIRE");
case TYPE_NORMAL: return F("WEAK: NONE");
default: return F("WEAK: NONE");
}
}
const __FlashStringHelper* speciesName(uint8_t s) {
switch (s) {
case PK_LAPRAS: return F("LAPRAS");
case PK_GENGAR: return F("GENGAR");
case PK_RAIKOU: return F("RAIKOU");
case PK_MAWILE: return F("MAWILE");
case PK_LUGIA: return F("LUGIA");
case PK_LATIAS: return F("LATIAS");
case PK_MEWTWO: return F("MEWTWO");
case PK_ZAPDOS: return F("ZAPDOS");
case PK_MOLTRES: return F("MOLTRES");
case PK_ARTICUNO: return F("ARTICUNO");
case PK_MEW: return F("MEW");
case PK_ARCEUS: return F("ARCEUS");
default: return F("UNKNOWN");
}
}
PokeType speciesType(uint8_t s) { //funckcja która przypisuje typ do danego pokeomna
switch (s) {
case PK_LAPRAS: return TYPE_WATER;
case PK_GENGAR: return TYPE_GHOST;
case PK_RAIKOU: return TYPE_ELECTRIC;
case PK_MAWILE: return TYPE_STEEL;
case PK_LUGIA: return TYPE_PSYCHIC;
case PK_LATIAS: return TYPE_DRAGON;
case PK_MEWTWO: return TYPE_PSYCHIC;
case PK_ZAPDOS: return TYPE_ELECTRIC;
case PK_MOLTRES: return TYPE_FIRE;
case PK_ARTICUNO: return TYPE_ICE;
case PK_MEW: return TYPE_PSYCHIC;
case PK_ARCEUS: return TYPE_NORMAL;
default: return TYPE_NORMAL;
}
}
int speciesMaxHP(uint8_t s) { //funckja która przypisuje max hp do danego pokemona
switch (s) {
case PK_LAPRAS: return 70;
case PK_GENGAR: return 50;
case PK_RAIKOU: return 80;
case PK_MAWILE: return 45;
case PK_LUGIA: return 80;
case PK_LATIAS: return 105;
case PK_MEWTWO: return 125;
case PK_ZAPDOS: return 110;
case PK_MOLTRES: return 110;
case PK_ARTICUNO: return 110;
case PK_MEW: return 100;
case PK_ARCEUS: return 140;
default: return 100;
}
}
MoveData getMove(uint8_t species, uint8_t idx) { //funkcja która przypisuje każdemu pokemonowi 4 ataki
MoveData m;
m.name = F("MOVE");
m.type = TYPE_NORMAL;
m.power = 15;
m.heals = false;
m.healAmount = 0;
switch (species) {
case PK_LAPRAS:
if (idx == 0) { m.name = F("SURF"); m.type = TYPE_WATER; m.power = 22; }
else if (idx == 1) { m.name = F("ICEBEAM"); m.type = TYPE_ICE; m.power = 20; }
else if (idx == 2) { m.name = F("PSYWAVE"); m.type = TYPE_PSYCHIC; m.power = 17; }
else if (idx == 3) { m.name = F("SLAM"); m.type = TYPE_NORMAL; m.power = 16; }
break;
case PK_GENGAR:
if (idx == 0) { m.name = F("SHADOW"); m.type = TYPE_GHOST; m.power = 23; }
else if (idx == 1) { m.name = F("THUNDER"); m.type = TYPE_ELECTRIC; m.power = 18; }
else if (idx == 2) { m.name = F("PSYBEAM"); m.type = TYPE_PSYCHIC; m.power = 16; }
else if (idx == 3) { m.name = F("PUNCH"); m.type = TYPE_NORMAL; m.power = 15; }
break;
case PK_RAIKOU:
if (idx == 0) { m.name = F("THUNDER"); m.type = TYPE_ELECTRIC; m.power = 25; }
else if (idx == 1) { m.name = F("CRUNCH"); m.type = TYPE_GHOST; m.power = 18; }
else if (idx == 2) { m.name = F("AURORA"); m.type = TYPE_ICE; m.power = 17; }
else if (idx == 3) { m.name = F("QATK"); m.type = TYPE_NORMAL; m.power = 16; }
break;
case PK_MAWILE:
if (idx == 0) { m.name = F("IRONHEAD"); m.type = TYPE_STEEL; m.power = 22; }
else if (idx == 1) { m.name = F("FIREFNG"); m.type = TYPE_FIRE; m.power = 18; }
else if (idx == 2) { m.name = F("ICEFNG"); m.type = TYPE_ICE; m.power = 18; }
else if (idx == 3) { m.name = F("BITE"); m.type = TYPE_GHOST; m.power = 16; }
break;
case PK_LUGIA:
if (idx == 0) { m.name = F("AEROBLAS"); m.type = TYPE_PSYCHIC; m.power = 27; }
else if (idx == 1) { m.name = F("HYDROPMP"); m.type = TYPE_WATER; m.power = 25; }
else if (idx == 2) { m.name = F("ICEWIND"); m.type = TYPE_ICE; m.power = 18; }
else if (idx == 3) { m.name = F("SWIFT"); m.type = TYPE_NORMAL; m.power = 16; }
break;
case PK_LATIAS:
if (idx == 0) { m.name = F("DRAGON"); m.type = TYPE_DRAGON; m.power = 20; }
else if (idx == 1) { m.name = F("PSYCHIC"); m.type = TYPE_PSYCHIC; m.power = 21; }
else if (idx == 2) { m.name = F("THUNDER"); m.type = TYPE_ELECTRIC; m.power = 18; }
else if (idx == 3) { m.name = F("SWIFT"); m.type = TYPE_NORMAL; m.power = 15; }
break;
case PK_MEWTWO:
if (idx == 0) { m.name = F("PSYSTRKE"); m.type = TYPE_PSYCHIC; m.power = 29; }
else if (idx == 1) { m.name = F("SHADOW"); m.type = TYPE_GHOST; m.power = 22; }
else if (idx == 2) { m.name = F("ICEBEAM"); m.type = TYPE_ICE; m.power = 20; }
else if (idx == 3) { m.name = F("RECOVER"); m.type = TYPE_NORMAL; m.power = 0; m.heals = true; m.healAmount = 20; }
break;
case PK_ZAPDOS:
if (idx == 0) { m.name = F("THUNDER"); m.type = TYPE_ELECTRIC; m.power = 25; }
else if (idx == 1) { m.name = F("HEATWVE"); m.type = TYPE_FIRE; m.power = 18; }
else if (idx == 2) { m.name = F("DRILLPCK"); m.type = TYPE_NORMAL; m.power = 18; }
else if (idx == 3) { m.name = F("PSYWAVE"); m.type = TYPE_PSYCHIC; m.power = 16; }
break;
case PK_MOLTRES:
if (idx == 0) { m.name = F("FLAMETHR"); m.type = TYPE_FIRE; m.power = 24; }
else if (idx == 1) { m.name = F("SUNBEAM"); m.type = TYPE_ELECTRIC; m.power = 17; }
else if (idx == 2) { m.name = F("SKYATK"); m.type = TYPE_NORMAL; m.power = 21; }
else if (idx == 3) { m.name = F("PSYBEAM"); m.type = TYPE_PSYCHIC; m.power = 15; }
break;
case PK_ARTICUNO:
if (idx == 0) { m.name = F("ICEBEAM"); m.type = TYPE_ICE; m.power = 22; }
else if (idx == 1) { m.name = F("WATERPLS"); m.type = TYPE_WATER; m.power = 18; }
else if (idx == 2) { m.name = F("GUST"); m.type = TYPE_NORMAL; m.power = 14; }
else if (idx == 3) { m.name = F("MINDRD"); m.type = TYPE_PSYCHIC; m.power = 12; }
break;
case PK_MEW:
if (idx == 0) { m.name = F("PSYCHIC"); m.type = TYPE_PSYCHIC; m.power = 21; }
else if (idx == 1) { m.name = F("THUNDER"); m.type = TYPE_ELECTRIC; m.power = 18; }
else if (idx == 2) { m.name = F("ICEBEAM"); m.type = TYPE_ICE; m.power = 19; }
else if (idx == 3) { m.name = F("MEGAPNC"); m.type = TYPE_NORMAL; m.power = 18; }
break;
case PK_ARCEUS:
if (idx == 0) { m.name = F("JUDGMNT"); m.type = TYPE_NORMAL; m.power = 30; }
else if (idx == 1) { m.name = F("PSYPWR"); m.type = TYPE_PSYCHIC; m.power = 20; }
else if (idx == 2) { m.name = F("ICEBLD"); m.type = TYPE_ICE; m.power = 20; }
else if (idx == 3) { m.name = F("RECOVER"); m.type = TYPE_NORMAL; m.power = 0; m.heals = true; m.healAmount = 24; }
break;
}
bool isSuperEffective(PokeType attackType, PokeType defenderType) { //funkcja która sprawdz czy dany atak jest super efektywny
if (attackType == TYPE_WATER && defenderType == TYPE_FIRE) return true;
if (attackType == TYPE_FIRE && defenderType == TYPE_ICE) return true;
if (attackType == TYPE_ELECTRIC && defenderType == TYPE_WATER) return true;
if (attackType == TYPE_ICE && defenderType == TYPE_DRAGON) return true;
if (attackType == TYPE_GHOST && defenderType == TYPE_PSYCHIC) return true;
if (attackType == TYPE_PSYCHIC && defenderType == TYPE_GHOST) return true;
if (attackType == TYPE_DRAGON && defenderType == TYPE_DRAGON) return true;
if (attackType == TYPE_STEEL && defenderType == TYPE_ICE) return true;
return false;
}
bool isNotVeryEffective(PokeType attackType, PokeType defenderType) { //funckja która sprawdza czy dany atak był słaby na dany typ pokemona
if (attackType == TYPE_FIRE && defenderType == TYPE_WATER) return true;
if (attackType == TYPE_WATER && defenderType == TYPE_ELECTRIC) return true;
if (attackType == TYPE_ELECTRIC && defenderType == TYPE_DRAGON) return true;
if (attackType == TYPE_ICE && defenderType == TYPE_FIRE) return true;
if (attackType == TYPE_GHOST && defenderType == TYPE_NORMAL) return true;
if (attackType == TYPE_PSYCHIC && defenderType == TYPE_PSYCHIC) return true;
if (attackType == TYPE_STEEL && defenderType == TYPE_FIRE) return true;
return false;
}
// funkcja która przelicza podstawową siłe ataku pokemona *150% jeśli jest super effekt lub *50% jeśli jest słaby
int applyTypeEffectiveness(int basePower, PokeType attackType, PokeType defenderType) {
if (isSuperEffective(attackType, defenderType)) return basePower + (basePower / 2);
if (isNotVeryEffective(attackType, defenderType)) {
int reduced = basePower / 2;
if (reduced < 1) reduced = 1;
return reduced;
}
return basePower;
}
//funkcja która sprawdza czy przycisk jest całyczas wciśniety
bool buttonPressed(uint8_t pin) {
return digitalRead(pin) == LOW;
}
//funckja która opóźnia wcisniecie jednego przycisku wielo krotnie
//czyli by gra nie regowała na wielie razy wcisnietego przycisku pomimo że wcisneliśmy go jeden raz
bool buttonJustPressed(uint8_t pin) {
if (millis() - lastInputTime < inputDelay) return false;
if (buttonPressed(pin)) {
lastInputTime = millis();
return true;
}
return false;
}
//funkcja która pokazuje napis po atak o obrażeniach zadanych przeciwnikowi
void setMessage(const char* l1, const char* l2, unsigned long durationMs = 900) {
strncpy(messageLine1, l1, sizeof(messageLine1) - 1);
messageLine1[sizeof(messageLine1) - 1] = '\0';
strncpy(messageLine2, l2, sizeof(messageLine2) - 1);
messageLine2[sizeof(messageLine2) - 1] = '\0';
messageStart = millis();
messageDuration = durationMs;
gameState = STATE_MESSAGE;
needRedraw = true;
}
//funkcja która tworzy obie drużyny pokemonów na start walki oraz przypisuje im hp
void initTeams() {
uint8_t p[6] = {PK_LAPRAS, PK_GENGAR, PK_RAIKOU, PK_MAWILE, PK_LUGIA, PK_LATIAS};
uint8_t e[6] = {PK_MOLTRES, PK_MEWTWO, PK_ZAPDOS, PK_ARTICUNO, PK_MEW, PK_ARCEUS};
for (uint8_t i = 0; i < 6; i++) {
playerTeam[i].species = p[i];
playerTeam[i].type = speciesType(p[i]);
playerTeam[i].maxHp = speciesMaxHP(p[i]);
playerTeam[i].hp = playerTeam[i].maxHp;
enemyTeam[i].species = e[i];
enemyTeam[i].type = speciesType(e[i]);
enemyTeam[i].maxHp = speciesMaxHP(e[i]);
enemyTeam[i].hp = enemyTeam[i].maxHp;
}
playerActive = 0;
enemyActive = 0;
selectedMove = 0;
}
//funkcja która resetuje całą walke i rysuje ekran od nowa
void resetBattle() {
initTeams();
enemyNeedsTurn = false;
gameState = STATE_PLAYER_TURN;
needRedraw = true;
}
// funkcja która liczy ile pokemonów jeszcze zyje w twojej druzynie
int aliveCount(Fighter team[]) {
int c = 0;
for (uint8_t i = 0; i < 6; i++) if (team[i].hp > 0) c++;
return c;
}
// funkcja ta słuzy do zamiany pokemona jesli którys zginie
bool advanceToNextAlive(Fighter team[], uint8_t &idx) {
for (uint8_t i = idx + 1; i < 6; i++) {
if (team[i].hp > 0) { idx = i; return true; }
}
return false;
}
// funkcja odpowiada za rysowanie paska zycia
void drawHPBarWithText(int x, int y, int w, int h, int hp, int maxHp) {
display.drawRect(x, y, w, h, SH110X_WHITE);
int fillW = 0;
if (hp > 0) {
fillW = ((long)(w - 2) * hp) / maxHp;
if (fillW < 1) fillW = 1;
}
if (fillW > 0) display.fillRect(x + 1, y + 1, fillW, h - 2, SH110X_WHITE);
char hpText[12];
snprintf(hpText, sizeof(hpText), "%d/%d", hp, maxHp);
int textX = x + 8;
int textY = y + 2;
if ((textX + 28) < (x + 1 + fillW)) display.setTextColor(SH110X_BLACK);
else display.setTextColor(SH110X_WHITE);
display.setCursor(textX, textY);
display.print(hpText);
display.setTextColor(SH110X_WHITE);
}
// funkcja odpowiada za naryzwanie portretu pokemona przeciwnika
void drawEnemySprite(int x, int y) {
display.drawCircle(x + 12, y + 10, 9, SH110X_WHITE);
display.drawLine(x + 3, y + 10, x + 21, y + 10, SH110X_WHITE);
display.fillCircle(x + 9, y + 7, 1, SH110X_WHITE);
display.fillCircle(x + 15, y + 7, 1, SH110X_WHITE);
display.drawLine(x + 8, y + 15, x + 16, y + 15, SH110X_WHITE);
display.drawLine(x + 6, y + 2, x + 4, y, SH110X_WHITE);
display.drawLine(x + 18, y + 2, x + 20, y, SH110X_WHITE);
}
//funckcja rysująca panel przeciwnika (pasek życia, nazwa przeciwnego pokoemona oraz jego typ)
void drawTopPanel() {
Fighter &e = enemyTeam[enemyActive];
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
display.setCursor(2, 0); display.print(speciesName(e.species));
display.setCursor(2, 8); display.print(typeName(e.type));
display.setCursor(104, 0); display.print(F("E")); display.print(aliveCount(enemyTeam));
drawHPBarWithText(44, 2, 52, 12, e.hp, e.maxHp);
}
//funkcja rysuje panel naszego pokemona (pasek życia, nazwa przeciwnego pokoemona oraz jego typ)
void drawBottomPanel() {
Fighter &p = playerTeam[playerActive];
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
display.setCursor(2, 18); display.print(speciesName(p.species));
display.setCursor(2, 26); display.print(typeName(p.type));
display.setCursor(104, 18); display.print(F("Y")); display.print(aliveCount(playerTeam));
drawHPBarWithText(44, 20, 52, 12, p.hp, p.maxHp);
}
// funkcja która torzy kontrast kolorów dla ataku zaznaczonego
void drawMoveCell(int x, int y, int w, int h, bool selected, const MoveData &m) {
if (selected) {
display.fillRect(x, y, w, h, SH110X_WHITE);
display.drawRect(x, y, w, h, SH110X_WHITE);
display.setTextColor(SH110X_BLACK);
} else {
display.drawRect(x, y, w, h, SH110X_WHITE);
display.setTextColor(SH110X_WHITE);
}
display.setTextSize(1);
display.setCursor(x + 4, y + 4);
if (m.name) display.print(m.name);
else display.print(F("MOVE"));
}
//funkcja która pobiera 4 ataki pokemona i rysuje komórke w której je umieszcza
void drawMoveMenu() {
Fighter &p = playerTeam[playerActive];
MoveData m0 = getMove(p.species, 0);
MoveData m1 = getMove(p.species, 1);
MoveData m2 = getMove(p.species, 2);
MoveData m3 = getMove(p.species, 3);
drawMoveCell(0, 36, 63, 14, selectedMove == 0, m0);
drawMoveCell(65, 36, 63, 14, selectedMove == 1, m1);
drawMoveCell(0, 50, 63, 14, selectedMove == 2, m2);
drawMoveCell(65, 50, 63, 14, selectedMove == 3, m3);
}
// funckja która rysuje ekran startowy
void drawTitleScreen() {
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(2);
display.setCursor(18, 2);
display.println(F("POKEMON"));
display.setTextSize(1);
display.setCursor(12, 22); display.println(F("TEAM BATTLE 6 VS 6"));
display.setCursor(6, 38); display.println(F("START = BEGIN"));
display.setCursor(4, 50); display.println(F("INFO BTN = PIN 5"));
display.display();
}
// funkcja która rysuje cały ekran walki
void drawBattleScreen() {
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
drawTopPanel();
drawBottomPanel();
drawEnemySprite(101, 8);
drawMoveMenu();
display.display();
}
// funkcja która rysuje ekran komunikatu obrażen lub leczenia po ataku
void drawMessageScreen() {
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
drawTopPanel();
drawBottomPanel();
drawEnemySprite(101, 8);
display.drawRect(2, 36, 124, 28, SH110X_WHITE);
display.setCursor(8, 44); display.print(messageLine1);
display.setCursor(8, 54); display.print(messageLine2);
display.display();
}
//funkcja która rysuje ekran o aktualnie zaznaczonym ruchu
void drawMoveInfoScreen() {
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
Fighter &p = playerTeam[playerActive];
MoveData m = getMove(p.species, selectedMove);
display.drawRect(0, 0, 128, 64, SH110X_WHITE);
display.setCursor(6, 6); display.print(F("MOVE INFO"));
display.setCursor(6, 18); if (m.name) display.print(m.name); else display.print(F("MOVE"));
display.setCursor(6, 30); display.print(F("TYPE: ")); display.print(typeName(m.type));
display.setCursor(6, 42); display.print(strongVsText(m.type));
display.setCursor(6, 52); display.print(weakVsText(m.type));
display.display();
}
//funkcja która rysuje ekran końcowy
void drawGameOverScreen() {
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(2);
if (aliveCount(playerTeam) <= 0) {
display.setCursor(20, 8); display.println(F("YOU LOSE"));
} else {
display.setCursor(24, 8); display.println(F("YOU WIN"));
}
display.setTextSize(1);
display.setCursor(12, 34); display.print(F("YOU LEFT: ")); display.println(aliveCount(playerTeam));
display.setCursor(12, 44); display.print(F("ENEMY LEFT: ")); display.println(aliveCount(enemyTeam));
display.setCursor(10, 56); display.println(F("START = AGAIN"));
display.display();
}
// funkcja centralna gdy zmina needRedraw == True sprawdza game state i wywołuje odpowiednia funkcje rysowania
void drawCurrentScreen() {
if (!needRedraw) return;
needRedraw = false;
switch (gameState) {
case STATE_TITLE: drawTitleScreen(); break;
case STATE_PLAYER_TURN:
case STATE_ENEMY_TURN: drawBattleScreen(); break;
case STATE_MESSAGE: drawMessageScreen(); break;
case STATE_GAME_OVER: drawGameOverScreen(); break;
case STATE_MOVE_INFO: drawMoveInfoScreen(); break;
}
}
// funkcja która obsługuje atak gracza
void playerAttack() {
Fighter &p = playerTeam[playerActive];
Fighter &e = enemyTeam[enemyActive];
MoveData m = getMove(p.species, selectedMove);
if (m.heals) {
p.hp += m.healAmount;
if (p.hp > p.maxHp) p.hp = p.maxHp;
snprintf(l1, sizeof(l1), "YOU USED HEAL");
snprintf(l2, sizeof(l2), "+%d HP", m.healAmount);
setMessage(l1, l2, 850);
return;
}
int finalDamage = applyTypeEffectiveness(m.power, m.type, e.type);
e.hp -= finalDamage;
if (e.hp < 0) e.hp = 0;
snprintf(l1, sizeof(l1), "DMG %d", finalDamage);
if (isSuperEffective(m.type, e.type)) snprintf(l2, sizeof(l2), "SUPER!");
else if (isNotVeryEffective(m.type, e.type)) snprintf(l2, sizeof(l2), "WEAK...");
else snprintf(l2, sizeof(l2), "NORMAL");
setMessage(l1, l2, 950);
}
// funkcja która wybiera atak przeciwnika
void enemyAttack() {
Fighter &e = enemyTeam[enemyActive];
Fighter &p = playerTeam[playerActive];
MoveData m = getMove(e.species, random(0, 4));
if (m.heals) {
e.hp += m.healAmount;
if (e.hp > e.maxHp) e.hp = e.maxHp;
snprintf(l1, sizeof(l1), "ENEMY HEALS");
snprintf(l2, sizeof(l2), "+%d HP", m.healAmount);
setMessage(l1, l2, 850);
return;
}
int finalDamage = applyTypeEffectiveness(m.power, m.type, p.type);
p.hp -= finalDamage;
if (p.hp < 0) p.hp = 0;
snprintf(l1, sizeof(l1), "ENEMY DMG %d", finalDamage);
if (isSuperEffective(m.type, p.type)) snprintf(l2, sizeof(l2), "SUPER!");
else if (isNotVeryEffective(m.type, p.type)) snprintf(l2, sizeof(l2), "WEAK...");
else snprintf(l2, sizeof(l2), "NORMAL");
setMessage(l1, l2, 950);
}
// funckja która sprawdza czy reset został wcisniety jeśli tak to wywołuje rest battle który resetuje walkę
void handleTitle() {
if (buttonJustPressed(BTN_START)) resetBattle();
}
//te funkcje przesuwają znacznik po siatce ataków
void moveSelectionUp() { if (selectedMove >= 2) selectedMove -= 2; }
void moveSelectionDown() { if (selectedMove <= 1) selectedMove += 2; }
void moveSelectionLeft() { if (selectedMove == 1) selectedMove = 0; else if (selectedMove == 3) selectedMove = 2; }
void moveSelectionRight() { if (selectedMove == 0) selectedMove = 1; else if (selectedMove == 2) selectedMove = 3; }
//funkcja która przetwarza wcisnięcie klawisza na wywołanie odpowiedniej funkcji np przesuniecia zaznaczenia ataku
void handlePlayerTurn() {
if (buttonJustPressed(BTN_UP)) { moveSelectionUp(); needRedraw = true; }
if (buttonJustPressed(BTN_DOWN)) { moveSelectionDown(); needRedraw = true; }
if (buttonJustPressed(BTN_LEFT)) { moveSelectionLeft(); needRedraw = true; }
if (buttonJustPressed(BTN_RIGHT)) { moveSelectionRight(); needRedraw = true; }
if (buttonJustPressed(BTN_INFO)) {
gameState = STATE_MOVE_INFO;
needRedraw = true;
return;
}
if (buttonJustPressed(BTN_START)) {
enemyNeedsTurn = true;
playerAttack();
}
}
//funkcja która wywołuje atak przeciwnika
void handleEnemyTurn() {
enemyNeedsTurn = false;
enemyAttack();
}
//funkcja która obsługuje przebieg walki
//1 czeka aż mini komunikat ataku
//2 sprawdza czy któaś z drużyn nie przegrała
//3 jeśli przeciwnik padł szuka kolejnego pokomona
//4 jeśli nasz pokomon umarł szuka zastępstwa dla nas
//5 jeśli zaden nie padł przechodzi do tury przeciwnika
void handleMessage() {
if (millis() - messageStart < messageDuration) return;
if (aliveCount(playerTeam) <= 0 || aliveCount(enemyTeam) <= 0) {
gameState = STATE_GAME_OVER;
needRedraw = true;
return;
}
if (enemyTeam[enemyActive].hp <= 0) {
if (!advanceToNextAlive(enemyTeam, enemyActive)) {
gameState = STATE_GAME_OVER;
needRedraw = true;
return;
}
gameState = STATE_PLAYER_TURN;
selectedMove = 0;
needRedraw = true;
return;
}
if (playerTeam[playerActive].hp <= 0) {
if (!advanceToNextAlive(playerTeam, playerActive)) {
gameState = STATE_GAME_OVER;
needRedraw = true;
return;
}
gameState = STATE_PLAYER_TURN;
selectedMove = 0;
needRedraw = true;
return;
}
gameState = enemyNeedsTurn ? STATE_ENEMY_TURN : STATE_PLAYER_TURN;
needRedraw = true;
}
// obsługuje wejście na ekran końca gry
void handleGameOver() {
if (buttonJustPressed(BTN_START)) {
gameState = STATE_TITLE;
needRedraw = true;
}
}
// obsługuje wejscie na ekran informacji
void handleMoveInfo() {
if (buttonJustPressed(BTN_INFO) || buttonJustPressed(BTN_START)) {
gameState = STATE_PLAYER_TURN;
needRedraw = true;
}
}
// ta funkcja wykonuje się tylko raz i przydziela przyciskom konkretne piny oraz obraca ekran
void setup() {
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_INFO, INPUT_PULLUP);
randomSeed(analogRead(A0));
delay(250);
display.begin(I2C_ADDRESS, true);
display.setRotation(2);
display.clearDisplay();
display.display();
initTeams();
needRedraw = true;
}
//pentla działajaca bez końca
void loop() {
drawCurrentScreen();
switch (gameState) {
case STATE_TITLE: handleTitle(); break;
case STATE_PLAYER_TURN: handlePlayerTurn(); break;
case STATE_ENEMY_TURN: handleEnemyTurn(); break;
case STATE_MESSAGE: handleMessage(); break;
case STATE_GAME_OVER: handleGameOver(); break;
case STATE_MOVE_INFO: handleMoveInfo(); break;
}
}