// This is version 0.1 of the server code
// it was made using platformio and squareline studio, it doesn't work in arduino IDE as it currently is
#include <esp32_smartdisplay.h> // lib required to use the display
#include <ui/ui.h> // the ui made with SquarelineStudio
#include <Audio.h> // to enable beeps
#include <SoftwareSerial.h> // to enable serial communication, due to mess at gitHUB this is currecntly directly in the /src and src/curclular_queue directories
#include <SD.h>
Audio *audio;
const long ledinterval = 1000; // blink pause time for the LED 1000ms = 1s
ulong next_millis; // helping variable for the blink timer
unsigned long previousMillis = 0; // helping variable for the blink timer
unsigned long currentMillis = millis();
int players[16]; // array of the blasters/players
int playerid; // id of the blaster/player
int ledswitch = 0; // required for the LED blink timer
int numplayers; // used to hold the numbe rof players read from the dropdown selectbox
int status=0; // current state of the program
int colors[3]; // array to hold colors for the player state buttons
int s1=0; // int to hold numbers of players in status 1
int s2=0; // int to hold numbers of players in status 2
int percent=0; // into to hold percentage of players who joined, used for the status bar
byte gd=0; // gamedata byte
byte gd2=0; // gamedata byte2 with checksum
#define SD_CS 5
int SDready=0;
File logFile;
// define some colors for the player status "buttons"
lv_color_t color = lv_color_hex(0x505050);
lv_color_t red = lv_color_hex(0xAA0000);
lv_color_t yellow = lv_color_hex(0xAAAA00);
lv_color_t green = lv_color_hex(0x009000);
// set the GPIO pins for 433MHz communication
SoftwareSerial hc12(35,22);
// this function builds the game data byte
// where each option corresponds to 2 bits
// [GAME MODE][SHOTS PER MAG][RESPAWNS][BATTLE TIME]
// 00 00 00 00
void makegdbyte(int sb,int sel) {
switch (sel) {
case 0: bitClear(gd,7-sb*2); bitClear(gd,6-sb*2);break;
case 1: bitClear(gd,7-sb*2); bitSet(gd,6-sb*2); break;
case 2: bitSet(gd,7-sb*2); bitClear(gd,6-sb*2);break;
case 3: bitSet(gd,7-sb*2); bitSet(gd,6-sb*2); break;
}
}
void watchgame() {
// is HC12 433Mhz serial communication available?
if (hc12.available()) {
}
}
// function read the SD path and look for existing files to create a new id for the new logfile
int getnewfileid(File dir, int numTabs)
{
int oldid=0;
while (true)
{
File filename = dir.openNextFile();
if (! filename) { break; }
// check for files that contain "attag"
char *ptr = strstr(filename.name(), "attag");
if (ptr != nullptr) {
ptr+=strlen("attag");
Serial.println(atoi(ptr));
if (oldid<atoi(ptr)) oldid=atoi(ptr);
}
filename.close();
}
return (oldid+1);
}
void startgame() {
Serial.print("starting game..STATUS:");
Serial.println(status);
// switch to ingame screen and go to watchgame
_ui_screen_change(&ui_Ingame, LV_SCR_LOAD_ANIM_MOVE_LEFT, 500, 0, &ui_Ingame_screen_init);
// write logfile only if SD card is available
if (SDready==1) {
File path = SD.open("/");
// logfile for the game stored on the SD card
// logFile = SD.open("fileName", FILE_APPEND);
char filename[] = "00000000.csv";
sprintf(filename,"/attag%02d.csv", getnewfileid(path,0));
// open the logfile for writing
logFile = SD.open(filename, FILE_WRITE);
// if the logfile is ready for writing
if (logFile) {
// write a header with the game data to the csv
char buf[16];
logFile.println("Game mode;Number of players;Shots per mag;Respawns;Battle time;Friendly fire");
lv_dropdown_get_selected_str(ui_Dropdown2, buf, sizeof(buf)); logFile.print(buf);logFile.print(";");
lv_dropdown_get_selected_str(ui_Dropdown1, buf, sizeof(buf)); logFile.print(buf);logFile.print(";");
lv_dropdown_get_selected_str(ui_Dropdown3, buf, sizeof(buf)); logFile.print(buf);logFile.print(";");
lv_dropdown_get_selected_str(ui_Dropdown4, buf, sizeof(buf)); logFile.print(buf);logFile.print(";");
lv_dropdown_get_selected_str(ui_Dropdown5, buf, sizeof(buf)); logFile.print(buf);logFile.print(";");
logFile.println(lv_obj_has_state(ui_FF, LV_STATE_CHECKED));
// logFile.close();
}
} // end if SDready
status=3;
watchgame();
}
void dohandshake() {
// is HC12 433Mhz serial communication available?
if (hc12.available()) {
Serial.println("at handshake, received HC12");
// RGB colors for the LED
colors[0]=25;colors[1]=0;colors[2]=0;
// read the incoming message and out it into a byte
byte received = hc12.read();
// checks if the received byte got a 111 tail = very basic attag identification
int check=bitRead(received,0)+bitRead(received,1)+bitRead(received,2);
// get the playerid
int playerid = (received >> 4);
// what is the player status?
int playerstatus=bitRead(received,3);
Serial.print("senderID:"); Serial.print(playerid); Serial.print(" / "); Serial.println(playerid,BIN);
Serial.print("received:"); Serial.print(received); Serial.print(" / "); Serial.println(received,BIN);
Serial.print("checked:"); Serial.println(check);
// if check is 3, sum up the first 3 bits read from right
// && status = 0 = blaster calling with "I am here"
if (check==3 && playerstatus==0) {
lv_obj_has_state(ui_FF, LV_STATE_CHECKED);
// set the color of the display LED RGB
colors[0]=25;colors[1]=25;colors[2]=25;
color=yellow;
// if the player already was at status 2 but started a new connection
// in case someone switched off the blaster or it lost power somehow
// decrease the number of players set to status 2
if (players[playerid]==2) {
s2=s2-1;
}
// set the player status to 1, unless it already was on 1
// actually this is an unused variable
if (players[playerid]!=1) {
players[playerid]=1;
s1=s1+1;
}
// once the first player asks for join broadcast message the game data
// that way some of the players join straight with ready status
// so less total messages are required
hc12.write(gd);
hc12.write(gd2);
} else if (check==3 && playerstatus==1) {
// set the color of the display LED RGB
colors[0]=0;colors[1]=25;colors[2]=0;
color=green;
// if the player had previous status 1 then remove one from the counter
if (players[playerid]==1) {
s1=s1-1;
}
// set the player status to 2, unless it already was on 2
if (players[playerid]!=2) {
players[playerid]=2;
s2=s2+1;
}
}
char numplayerstxt[10];
lv_dropdown_get_selected_str(ui_Dropdown1, numplayerstxt, sizeof(numplayerstxt));
// calculate the status bar progress percentage and update it
percent=(100/numplayers*s2);
lv_bar_set_value(ui_Bar1, percent, LV_ANIM_OFF);
Serial.print(" Numplayers:");Serial.print(numplayers);
Serial.print(" S1:");Serial.print(s1);
Serial.print(" S2:");Serial.print(s2);
Serial.print(" Prozent:"); Serial.println(percent);
// paint the player buttons yellow or green depending on status
// the buttons don't yet have any other function besides showing the status
// but maybe they can be used for a link to a data page or whatever in a future version
switch(playerid) {
case 1: lv_obj_set_style_bg_color(ui_Button1, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 2: lv_obj_set_style_bg_color(ui_Button2, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 3: lv_obj_set_style_bg_color(ui_Button3, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 4: lv_obj_set_style_bg_color(ui_Button4, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 5: lv_obj_set_style_bg_color(ui_Button5, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 6: lv_obj_set_style_bg_color(ui_Button6, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 7: lv_obj_set_style_bg_color(ui_Button7, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 8: lv_obj_set_style_bg_color(ui_Button8, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 9: lv_obj_set_style_bg_color(ui_Button9, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 10: lv_obj_set_style_bg_color(ui_Button10, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 11: lv_obj_set_style_bg_color(ui_Button11, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 12: lv_obj_set_style_bg_color(ui_Button12, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 13: lv_obj_set_style_bg_color(ui_Button13, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 14: lv_obj_set_style_bg_color(ui_Button13, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 15: lv_obj_set_style_bg_color(ui_Button15, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
case 16: lv_obj_set_style_bg_color(ui_Button16, color, LV_PART_MAIN | LV_STATE_DEFAULT);break;
}
// done with collecting players? proceed to game launch
if (s2==numplayers) {
Serial.println("going to set status=2");
colors[0]=0;colors[1]=75;colors[2]=0;
status=2;
startgame();
}
}
// blink the LED
if (currentMillis - previousMillis >= ledinterval) {
previousMillis = currentMillis;
if (ledswitch == 0) {
smartdisplay_led_set_rgb(colors[0], colors[1], colors[2]);
colors[0]=25;colors[1]=0;colors[2]=0;
ledswitch=1;
} else {
smartdisplay_led_set_rgb(0, 0, 0);
ledswitch=0;
}
}
}
// doing the handshake between blasters and server
void gotostatus1(lv_event_t *e) {
char buf[32];
// get the game mode from selectbox and print it on handhsake screen
lv_dropdown_get_selected_str(ui_Dropdown2, buf, sizeof(buf));
lv_textarea_set_text(ui_TextArea1, "Game mode: ");
lv_textarea_add_text(ui_TextArea1, buf);
// get the number of players from selectbox and print it on handhsake screen
lv_dropdown_get_selected_str(ui_Dropdown1, buf, sizeof(buf));
lv_textarea_add_text(ui_TextArea1, "\nNumber of players: ");
lv_textarea_add_text(ui_TextArea1, buf);
// get the number of players and convert to int
numplayers=atoi(buf);
// get the number of shots per mag from selectbox and print it on handhsake screen
lv_dropdown_get_selected_str(ui_Dropdown3, buf, sizeof(buf));
lv_textarea_add_text(ui_TextArea1, "\nShots per mag: ");
lv_textarea_add_text(ui_TextArea1, buf);
// get the number of respawns from selectbox and print it on handhsake screen
lv_dropdown_get_selected_str(ui_Dropdown4, buf, sizeof(buf));
lv_textarea_add_text(ui_TextArea1, "\nRespawns: ");
lv_textarea_add_text(ui_TextArea1, buf);
// get the battle time from selectbox and print it on handhsake screen
lv_dropdown_get_selected_str(ui_Dropdown5, buf, sizeof(buf));
lv_textarea_add_text(ui_TextArea1, "\nBattle time: ");
lv_textarea_add_text(ui_TextArea1, buf);
int gd_gamemode=lv_dropdown_get_selected(ui_Dropdown1);
int gd_shots=lv_dropdown_get_selected(ui_Dropdown3);
int gd_respawns=lv_dropdown_get_selected(ui_Dropdown4);
int gd_time=lv_dropdown_get_selected(ui_Dropdown5);
gd=gd_gamemode << 6 | gd_shots << 4 | gd_respawns << 2 | gd_time;
// generate a basic checksum for the gamedata
int csum=gd_gamemode+gd_shots+gd_respawns+gd_time;
gd2=lv_obj_has_state(ui_FF, LV_STATE_CHECKED) <<6 | csum;
Serial.print(lv_obj_has_state(ui_FF, LV_STATE_CHECKED));
Serial.print(" all the game data, well almost all:");
Serial.println(gd,BIN);
Serial.print("checksumbyte:");
Serial.println(gd2,BIN);
status=1;
}
void setup()
{
delay(250);
Serial.begin(115200);
hc12.begin(9600);
// Serial.begin(9600);
Serial.println("launching...");
Serial.setDebugOutput(true);
// check if a SD card is abailable
if (SD.begin(SD_CS)) {
SDready=1;
}
smartdisplay_init();
auto disp = lv_disp_get_default();
lv_disp_set_rotation(disp, LV_DISP_ROT_90);
smartdisplay_lcd_set_brightness_cb(NULL,10000);
ui_init();
// hide the red SD card icon if SD card is available
if (SDready==1) {
lv_obj_set_x(ui_Image5,320);
}
}
void loop()
{
// Serial.println(SDready);
// waiting for players calling doing the handshake
switch (status) {
case 1: dohandshake(); break;
case 2: startgame(); break;
case 3: watchgame(); break;
}
lv_timer_handler();
}