#include <SPI.h>
#include <TFT_eSPI.h>
#include <math.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite mySprite = TFT_eSprite(&tft);
unsigned long currentTime;
int x_center = tft.width() / 2;
int y_center = tft.height() / 2;
int radius = min(tft.width(), tft.height()) / 2;
uint16_t colors[8] = {TFT_RED, TFT_ORANGE, TFT_SKYBLUE,TFT_GREEN,TFT_CYAN,TFT_BLUE,TFT_GOLD,TFT_MAGENTA};
int num_segments = 6;
int life25 = 25;
int life30 = 30;
int life0 = 0;
int set_life = 30;
int currentlife=0;
bool leader_state=true;
bool base_state=true;
bool force_state=false;
const int NUM_BUTTONS = 9;
const int buttonPins[NUM_BUTTONS] = {4, 5, 6, 7, 8, 3, 15, 16, 17};
int buttonState[NUM_BUTTONS];
int lastButtonState[NUM_BUTTONS];
unsigned long lastDebounceTime[NUM_BUTTONS] = {0};
const unsigned long DEBOUNCE_DELAY = 25;
bool initialSplashScreenDone = false;
unsigned long splashScreenTimerStart = 0;
const unsigned long SPLASH_SCREEN_DURATION = 2000;
int battery_lvl=66;
void setup() {
Serial.begin(115200);
delay(100);
for (int i = 4; i < NUM_BUTTONS; i++) {pinMode(buttonPins[i], INPUT_PULLDOWN); lastButtonState[i] = LOW;}
mySprite.createSprite(tft.width(), tft.height());
tft.init();
Serial.println("TFT initialized successfully!");
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextFont(4);
tft.setTextDatum(MC_DATUM);
tft.drawString("LIFE", tft.width() / 2, (tft.width() - tft.fontHeight()) / 2);
tft.drawString("COUNTER", tft.width() / 2,( tft.width() + tft.fontHeight()) / 2);
splashScreenTimerStart = millis();
}
void battery_icon(){
String battery_string = String(battery_lvl) + "%";
mySprite.setTextColor(TFT_MAGENTA);
mySprite.setTextFont(1);
mySprite.setTextDatum(MC_DATUM);
int battery_lvl_display = map (battery_lvl, 0 , 100, 0 , 38);
mySprite.drawRect(100, 20, 40, 15, TFT_GOLD);
mySprite.fillRect(101, 21, battery_lvl_display, 13, TFT_GREEN);
mySprite.drawString(battery_string, 120 , 28 );
}
void game_over(){
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED );
tft.setTextFont(4);
tft.setTextDatum(MC_DATUM);
tft.drawString("GAME OVER", 120, (240 - tft.fontHeight()) / 2);
tft.drawString("YOU DIED", 120,( 240 + tft.fontHeight()) / 2);
delay(2000);
tft.fillScreen(TFT_BLACK);
}
void display_div_line(){
int num_lines = num_segments / 2;
float angle_increment = 360.0 / (num_lines * 2);
for (int i = 0; i < num_lines; i++) {
float angle_degrees = i * angle_increment;
float angle_radians = angle_degrees * PI / 180.0;
int x1 = x_center + radius * cos(angle_radians);
int y1 = y_center + radius * sin(angle_radians);
float opposite_angle_radians = (angle_degrees + 180.0) * PI / 180.0;
int x2 = x_center + radius * cos(opposite_angle_radians);
int y2 = y_center + radius * sin(opposite_angle_radians);
mySprite.drawLine(x1, y1, x2, y2, TFT_VIOLET);
}
}
void color_segments () {
float angle_increment = 360.0 / num_segments;
for (int i = 0; i < num_segments; i++) {
float start_angle_degrees = i * angle_increment;
float start_angle_radians = start_angle_degrees * PI / 180.0;
float end_angle_degrees = (i + 1) * angle_increment;
float end_angle_radians = end_angle_degrees * PI / 180.0;
int x1 = x_center + radius * cos(start_angle_radians);
int y1 = y_center + radius * sin(start_angle_radians);
int x2 = x_center + radius * cos(end_angle_radians);
int y2 = y_center + radius * sin(end_angle_radians);
mySprite.fillTriangle(x_center, y_center, x1, y1, x2, y2, colors[i]);
}
}
void display_main () {
mySprite.setTextColor(TFT_YELLOW);
mySprite.setTextFont(8);
mySprite.setTextDatum(MC_DATUM);
mySprite.drawNumber(currentlife, tft.width() / 2, tft.width() / 2);
mySprite.setTextFont(2);
int move_pix = 170;
tft.setTextFont(2);
mySprite.drawString("LEADER", (tft.width() - move_pix) / 2, (tft.width() - tft.fontHeight()) / 2);
if (leader_state==true) {mySprite.drawString("ALIVE", (tft.width() - move_pix) / 2,( tft.width() + tft.fontHeight()) / 2);}
else {mySprite.drawString("DEAD", (tft.width() - move_pix) / 2,( tft.width() + tft.fontHeight()) / 2);}
mySprite.drawString("BASE", (tft.width() + move_pix) / 2, (tft.width() - tft.fontHeight()) / 2);
if (base_state==true) {mySprite.drawString("NOT USED", (tft.width() + move_pix)/ 2,( tft.width() + tft.fontHeight()) / 2); }
else {mySprite.drawString("USED", (tft.width() + move_pix)/ 2,( tft.width() + tft.fontHeight()) / 2); }
battery_icon();
if (force_state==true) {force_icon(mySprite, 90, 170, 60, TFT_SKYBLUE, TFT_NAVY);}
else {force_icon(mySprite, 90, 170, 60, TFT_LIGHTGREY, TFT_DARKGREEN);}
}
void life_change (){
for (int i = 0; i < 7; i++) {
int reading;
reading = touchRead(buttonPins[i]) < 40000 ? LOW : HIGH;
delay(10);
if (reading != lastButtonState[i]) {lastDebounceTime[i] = currentTime;}
if ((currentTime - lastDebounceTime[i]) > DEBOUNCE_DELAY) {
if (reading != buttonState[i]) {
buttonState[i] = reading;
if (reading==LOW){
mySprite.fillScreen(TFT_BLACK);
// tft.fillScreen(TFT_DARKGREY);
// mySprite.fillCircle(x_center, y_center, radius, TFT_PURPLE);
// color_segments ();
if (i==0){currentlife++;}
if (i==1){currentlife--;}
if (i==2){currentlife=currentlife+30;}
if (i==3){currentlife=currentlife-3;}
//if (i==2){currentlife=currentlife+5;}
// if (i==3){currentlife=currentlife-5;}
if (i==4){leader_state=!leader_state;}
if (i==5){base_state=!base_state;}
if (i==6){force_state=!force_state;}
if (currentlife >= set_life) { game_over(); currentlife = 0;}
if (currentlife <= -1) {currentlife = 0;}
// color_segments ();
display_div_line();
display_main ();
}
}
}
lastButtonState[i] = reading;
}
}
void force_icon(TFT_eSprite &mySprite, int x_pos, int y_pos, int size, uint16_t primaryColor, uint16_t secondaryColor) {
int center_x = x_pos + size / 2;
int center_y = y_pos + size / 2;
mySprite.drawCircle(center_x, center_y, 29, secondaryColor);
mySprite.drawCircle(center_x, center_y, 25, secondaryColor);
int cutout_size = 3;
for (int i = 0; i < 4; i++) {
float angle = i * PI / 2.0;
int x1 = center_x + (int)(cos(angle) * 27);
int y1 = center_y + (int)(sin(angle) * 27);
int x2 = center_x + (int)(cos(angle + PI / 12.0) * (25 - cutout_size));
int y2 = center_y + (int)(sin(angle + PI / 12.0) * (25 - cutout_size));
int x3 = center_x + (int)(cos(angle - PI / 12.0) * (25 - cutout_size));
int y3 = center_y + (int)(sin(angle - PI / 12.0) * (25 - cutout_size));
mySprite.fillTriangle(x1, y1, x2, y2, x3, y3, primaryColor);
}
int clover_radius = 18;
int clover_offset = 12;
mySprite.fillCircle(center_x, center_y - clover_offset, clover_radius, primaryColor);
mySprite.fillCircle(center_x, center_y + clover_offset, clover_radius, primaryColor);
mySprite.fillCircle(center_x - clover_offset, center_y, clover_radius, primaryColor);
mySprite.fillCircle(center_x + clover_offset, center_y, clover_radius, primaryColor);
int star_outer_radius = 15;
int star_inner_radius = 5;
for (int i = 0; i < 4; i++) {
float angle_degrees = i * 90;
float angle_radians = angle_degrees * PI / 180.0;
float angle_radians_45 = (angle_degrees + 45) * PI / 180.0;
int x1 = center_x + (int)(cos(angle_radians) * star_outer_radius);
int y1 = center_y + (int)(sin(angle_radians) * star_outer_radius);
int x2 = center_x + (int)(cos(angle_radians_45) * star_inner_radius);
int y2 = center_y + (int)(sin(angle_radians_45) * star_inner_radius);
int x3 = center_x - (int)(cos(angle_radians_45) * star_inner_radius);
int y3 = center_y - (int)(sin(angle_radians_45) * star_inner_radius);
mySprite.fillTriangle(center_x, center_y, x1, y1, x2, y2, secondaryColor);
mySprite.fillTriangle(center_x, center_y, x1, y1, x3, y3, secondaryColor);
}
mySprite.fillCircle(center_x, center_y, 7, primaryColor);
mySprite.drawCircle(center_x, center_y, 7, secondaryColor);
}
void loop() {
currentTime = millis();
if (!initialSplashScreenDone) {
if (currentTime - splashScreenTimerStart >= SPLASH_SCREEN_DURATION) {
initialSplashScreenDone = true;
//tft.fillScreen(TFT_BLACK);
// tft.fillScreen(TFT_DARKGREY);
// mySprite.fillCircle(x_center, y_center, radius, TFT_PURPLE);
// color_segments (); //podzielenie na semgnety pokolorowane
display_div_line();//same linie
display_main (); // zycie
}
return;
}
life_change();
mySprite.pushSprite(0, 0);
//mySprite.deleteSprite();
}
// =======================================================================================
// User_Setup.h
// Best Possible Configuration for ESP32-S3 Board with GC9A01 240x240 Round Display
//
// This file customises the TFT_eSPI library to your specific display and MCU.
// Based on successful initialization and common fixes for "no color" issues.
// =======================================================================================
#ifndef USER_SETUP_H
#define USER_SETUP_H
// User-defined setup information (optional, but good for tracking)
#define USER_SETUP_INFO "Custom ESP32-S3 Board with GC9A01 240x240 Setup"
// ---------------------------------------------------------------------------------------
// Section 1: Display Driver and Resolution Configuration
// ---------------------------------------------------------------------------------------
// Define the TFT driver chip. ONLY ONE of these should be uncommented.
#define GC9A01_DRIVER // This is your 240x240, 1.28" round display controller
// Define the display resolution for the selected driver.
// Confirmed for your 240x240 GC9A01 display.
#define TFT_WIDTH 240
#define TFT_HEIGHT 240
// ---------------------------------------------------------------------------------------
// Section 2: SPI Bus Pin Connections (for ESP32-S3 Board)
// ---------------------------------------------------------------------------------------
// These are generally safe and available GPIO pins for ESP32-S3, avoiding common conflicts
// with internal flash/PSRAM (which often use GPIOs 8-12).
// Ensure your physical wiring exactly matches these GPIO numbers on your ESP32-S3 board.
#define TFT_MISO -1 // MISO is generally not needed for writing to display. Set to -1.
#define TFT_MOSI 38 // Connect to the display's MOSI (or SDA/DIN) pin
#define TFT_SCLK 39 // Connect to the display's SCLK (or CLK) pin
#define TFT_CS 40 // Connect to the display's CS (Chip Select) pin
#define TFT_DC 41 // Connect to the display's DC (Data/Command) pin
// Reset pin handling: Set to -1 and physically disconnect the RST wire from the ESP32-S3.
// Many GC9A01 modules have an internal power-on reset.
#define TFT_RST -1 // Set to -1 if display reset pin is not controlled by processor
// Backlight control pin (optional). You confirmed your LCD has no dedicated BL pin.
// Setting to -1 means the library will not attempt to control the backlight via a GPIO.
#define TFT_BL -1 // Set to -1 if backlight is not controlled by processor (e.g., always on when powered)
// ---------------------------------------------------------------------------------------
// Section 3: SPI Bus Speed Configuration
// ---------------------------------------------------------------------------------------
// Set the SPI clock frequency.
// 10 MHz is a good stable starting point. You can try higher (e.g., 27000000 or 40000000)
// for faster updates once the display is confirmed working reliably.
#define SPI_FREQUENCY 10000000 // 10 MHz (conservative and stable)
// Optional: Set a different SPI frequency for reading (not typically needed for GC9A01).
// #define SPI_READ_FREQUENCY 20000000
// Optional: For touch screens (not applicable here unless you add one).
// #define SPI_TOUCH_FREQUENCY 2500000
// ---------------------------------------------------------------------------------------
// Section 4: Color Order and Byte Swapping (Crucial for GC9A01 "no color" issues)
// ---------------------------------------------------------------------------------------
// This setting is often critical for GC9A01 displays to show correct colors.
// It tells the library to swap the byte order of the 16-bit color data (RGB565).
#define TFT_SWAP_BYTES // Swap the byte order of 16-bit pixel data
// If colors are still incorrect (e.g., blue where red should be) after using TFT_SWAP_BYTES,
// you can try uncommenting one of these in addition to TFT_SWAP_BYTES:
// #define TFT_RGB_ORDER TFT_BGR // For displays that expect BGR instead of RGB
// #define TFT_RGB_ORDER TFT_RGB // For displays that expect RGB (usually default, but can be explicit)
// ---------------------------------------------------------------------------------------
// Section 5: Font Loading Configuration (Optional - uncomment what you need to save flash)
// ---------------------------------------------------------------------------------------
// These macros define which fonts are loaded into Flash memory.
// Uncomment only the fonts you intend to use to save valuable Flash space.
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font
#define LOAD_FONT2 // Font 2. Small 16 pixel high font
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font
#define LOAD_FONT6 // Font 6. Large 48 pixel high font
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel high font
#define LOAD_FONT8 // Font 8. Large 75 pixel high font
#define LOAD_GFXFF // FreeFonts. Include access to the Adafruit_GFX free fonts (FF1 to FF48)
#define SMOOTH_FONT // Enable anti-aliased fonts (requires more Flash)
// ---------------------------------------------------------------------------------------
// Section 6: Advanced/Optional Settings (Keep commented unless specifically needed)
// ---------------------------------------------------------------------------------------
// Direct Memory Access (DMA) for faster transfers.
// Keep commented out for initial troubleshooting and stability.
// #define USE_SPI_DMA
// Explicitly define the SPI port to use. HSPI (SPI2) is a common choice for ESP32-S3.
#define USE_HSPI_PORT
// #define USE_VSPI_PORT // Only uncomment this if USE_HSPI_PORT does not work.
// Optional: Invert display rotation (if your display is upside down).
// #define TFT_INVERT_ROTATION
// Optional: Define if you have a touch screen (not applicable here).
// #define TOUCH_CS -1
#endif // USER_SETUP_H