From 61be7fe036853537bd37dfd485a180cde8ff859c Mon Sep 17 00:00:00 2001 From: equant Date: Mon, 10 Jan 2022 08:23:17 -0700 Subject: [PATCH] reorganizing stuff from shed --- examples/template/a2i_chess.cpp | 281 +++++++++++++++++++++ examples/template/a2i_chess.h | 66 +++++ examples/template/chess_commands.h | 38 +++ examples/template/template.ino | 129 +++++++++- examples/weather/{ => arduino}/weather.ino | 0 5 files changed, 510 insertions(+), 4 deletions(-) create mode 100644 examples/template/a2i_chess.cpp create mode 100644 examples/template/a2i_chess.h create mode 100644 examples/template/chess_commands.h rename examples/weather/{ => arduino}/weather.ino (100%) diff --git a/examples/template/a2i_chess.cpp b/examples/template/a2i_chess.cpp new file mode 100644 index 0000000..c568396 --- /dev/null +++ b/examples/template/a2i_chess.cpp @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include +#include "chess_commands.h" +#include "a2i_chess.h" + + +void Chess::init(Apple2Idiot *a2ip, HTTPClient *httpp) { + a2i = a2ip; + http = httpp; + //strcpy(game_string, "a2a3e7e5e2e4"); +} + +byte Chess::handleCommand(byte command) { + switch(command) { + case CHESS_MAKE_MOVE: { + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("CHESS() MAKE_MOVE"); + String move_string; + move_string = a2i->read_string_from_ram(SHARED_RAM_START_ADDRESS); + Serial.println("Received move: ["+move_string+"]"); + byte result = makeMove(move_string); + int address_counter = a2i->write_string_to_shared_ram(last_ai_move, SHARED_RAM_START_ADDRESS); + getBoard(); + for (int i=0; i<9; i++) { + address_counter = a2i->write_string_to_shared_ram(game_board[i], address_counter + 1); + } + a2i->write_data(ESP_COMMAND_ADDRESS, result); + a2i->read_ram(11); + break; + } + case CHESS_NEW_GAME: { + a2i->write_data(ESP_COMMAND_ADDRESS, ACK); // notify Apple IIe we are processing command byte + Serial.println("CHESS_NEW_GAME"); + strcpy(game_string, ""); + break; + } + default: { + return COMMAND_NOT_FOUND; + } + } +} + +byte Chess::makeMove(String move_string) { + /* Apple says MAKE_MOVE and sends "a2a3" + * + * The basic flow... + * + * validateMove() We check that it's valid. + * If it is we accept the move and add it to game_string + * getGameStatus() Then we check the status and return (did someone win or lose?) + * getAIMove() Then we get the AI move. + * Then we check the status and return (did someone win or lose?) + */ + if (validateMove(move_string) == CHESS_VALID_MOVE) { + strcat(game_string, move_string.c_str()); + } else { + return CHESS_INVALID_MOVE; + } + char* game_status; + //b = getGameStatus(game_string); + game_status = getGameStatus(game_string); + Serial.print("after player move game_status:"); Serial.println(game_status); + + if (strcmp(game_status, "in_progress") == 0) { + //char* ai_move; + //ai_move = getAIMove(); + //strcat(game_string, ai_move); + //last_ai_move = getAIMove(); + strcpy(last_ai_move, getAIMove()); + strcat(game_string, last_ai_move); + game_status = getGameStatus(game_string); + Serial.print("after AI move game_status:"); Serial.println(game_status); + if (strcmp(game_status, "in_progress") == 0) { return STATUS_IN_PROGRESS; } + else if (strcmp(game_status, "black_won") == 0) { return STATUS_BLACK_WON; } + else if (strcmp(game_status, "white_won") == 0) { return STATUS_WHITE_WON; } + else if (strcmp(game_status, "white_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "black_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "stalemate") == 0) { return STATUS_STALEMATE; } + else if (strcmp(game_status, "insufficient_material") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "fifty_rule_move") == 0) { return STATUS_FIFTY_RULE_MOVE; } + else if (strcmp(game_status, "threefold_repitition") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "unknown") == 0) { return STATUS_UNKNOWN; } else { return STATUS_ERROR; } + } + else if (strcmp(game_status, "black_won") == 0) { return STATUS_BLACK_WON; } + else if (strcmp(game_status, "white_won") == 0) { return STATUS_WHITE_WON; } + else if (strcmp(game_status, "white_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "black_won_resign") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "stalemate") == 0) { return STATUS_STALEMATE; } + else if (strcmp(game_status, "insufficient_material") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "fifty_rule_move") == 0) { return STATUS_FIFTY_RULE_MOVE; } + else if (strcmp(game_status, "threefold_repitition") == 0) { return STATUS_UNHANDLED; } + else if (strcmp(game_status, "unknown") == 0) { return STATUS_UNKNOWN; } else { return STATUS_ERROR; } +} + +char* Chess::getGameStatus(char* game_string) { + Serial.print("getGameStatus() "); Serial.println(game_status); + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/status/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F(" deserializeJson() failed: ")); + Serial.println(error.f_str()); + return "error"; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + //return doc["gameStatus"]; + return (char *)doc["gameStatus"].as(); + } + } else { + Serial.println("Error on HTTP request"); + return "error"; + } + // Don't know how we could get here without it being an error. + + return "error"; +} + +char* Chess::getAIMove() { + Serial.print("getAIMove() "); Serial.println(game_status); + + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/next_best/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F(" deserializeJson() failed: ")); + Serial.println(error.f_str()); + return "jsonerror"; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + //return doc["gameStatus"]; + return (char *)doc["bestNext"].as(); + } + } else { + Serial.println("Error on HTTP request"); + return "geterror"; + } + // Don't know how we could get here without it being an error. + + return "unknownerror"; +} + +byte Chess::validateMove(String move_string) { + Serial.print("validateMove() "); Serial.println(move_string); + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/valid_move/%s%s", api_entry_point, game_string, move_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return ERR; + } else { + Serial.println("----------------------"); + serializeJsonPretty(doc, Serial); + Serial.println(); + Serial.println("----------------------"); + bool validMove = doc["validMove"]; + if (validMove) { + // good move + Serial.println(" Valid move!"); + return CHESS_VALID_MOVE; + } else { + Serial.println(" Invalid move!"); + return CHESS_INVALID_MOVE; + } + } + } else { + Serial.println("Error on HTTP request"); + return ERR; + } + // Don't know how we could get here without it being an error. + return ERR; +} + +void Chess::removeSubstr (char *string, char *sub) { + char *match = string; + int len = strlen(sub); + while ((match = strstr(match, sub))) { + *match = '\0'; + strcat(string, match+len); + match++; + } +} + +void Chess::getBoard() { + Serial.println("getBoard() "); + char api_request[MAX_STR_LEN]; + sprintf(api_request, "%s/board_string/%s", api_entry_point, game_string); + Serial.print(" "); Serial.println(api_request); + http->begin(api_request); + int httpCode = http->GET(); //Make the request + delay(10); + if (httpCode > 0) { //Check for the returning code + Serial.println(" Success on HTTP request"); + String payload = http->getString(); + //Serial.println("++++++++++++++++++++++++"); + //Serial.println(payload); + //Serial.println("++++++++++++++++++++++++"); + StaticJsonDocument<400> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + } else { + //Serial.println("----------------------"); + //serializeJsonPretty(doc, Serial); + //Serial.println(); + //Serial.println("----------------------"); + //Serial.println(" Break up the board..."); + //Serial.print(" BOARD:"); Serial.println((char *)doc["board"].as()); + char* pch = NULL; + pch = strtok((char *)doc["board"].as(), "\n"); + //Serial.print(" pch:");Serial.println(pch); + int row_count = 0; + while (pch != NULL) { + char board_line[30]; + strcpy(board_line, pch); + removeSubstr(board_line, "[37m"); + removeSubstr(board_line, "[0m"); + removeSubstr(board_line, "\n"); + removeSubstr(board_line, "\e"); // remove escape (ASCII 27) + removeSubstr(board_line, "\e"); // remove escape (ASCII 27) + //Serial.print("(");Serial.print(row_count);Serial.print(")"); + //Serial.print(board_line); Serial.println("|"); + strcpy(game_board[row_count], board_line); // valid + pch = strtok(NULL, "\n"); + row_count++; + } + Serial.println(); + for (int i=0; i<9; i++) { + Serial.print("[");Serial.print(game_board[i]);Serial.println("]"); + } + } + } else { + Serial.println("Error on HTTP request"); + } + // Don't know how we could get here without it being an error. +} diff --git a/examples/template/a2i_chess.h b/examples/template/a2i_chess.h new file mode 100644 index 0000000..bb691b0 --- /dev/null +++ b/examples/template/a2i_chess.h @@ -0,0 +1,66 @@ +#ifndef A2I_CHESS_H +#define A2I_CHESS_H + +#include +#include +#include +#include +#include "chess_commands.h" + + +#define MAX_GAME_SIZE 110 * 4 // This is probably not enough, but it's fine for development. + // https://chess.stackexchange.com/questions/2506/what-is-the-average-length-of-a-game-of-chess + // times four because one move is "e7e5" + +class Chess { + + public: + byte appId = APP_CHESS; // This is "registered" with A2I_commands.h which is part of Apple2Idiot.h + // This id is sent from the Apple to the ESP to tell the esp what app + // is currently active. The main loop of the ESP sketch then knows to use + // this class to respond to incoming commands from the Apple. + + + char game_string[MAX_GAME_SIZE]; // This is probably not enough, but it's fine for development. + + char game_status[25]; + char last_player_move[5]; // "a2a4" + char last_ai_move[5]; // "8g76" + + //char game_board[9][20]; + char game_board[9][22] = { + "8 r n b q k b n r", + "7 p p p p p p p p", + "6 . . . . . . . .", + "5 . . . . . . . .", + "4 . . . . . . . .", + "3 . . . . . . . .", + "2 P P P P P P P P", + "1 R N B Q K B N R", + " a b c d e f g h" + }; + + void init(Apple2Idiot *a2ip, HTTPClient *httpp); + char* getGameStatus(char* game_status); + byte makeMove(String move_string); + byte handleCommand(byte command); + byte validateMove(String move_string); + char* getAIMove(); + void getBoard(); + + private: + + Apple2Idiot *a2i; + HTTPClient *http; + + void removeSubstr (char *string, char *sub); + const char api_entry_point[32] = "http://chess-api.herokuapp.com"; + /* Remember, flexible array won't work + * in a class, so don't try to do this... + * const char foo[] = "hello world"; + */ + +}; + +#endif + diff --git a/examples/template/chess_commands.h b/examples/template/chess_commands.h new file mode 100644 index 0000000..7721c90 --- /dev/null +++ b/examples/template/chess_commands.h @@ -0,0 +1,38 @@ +#ifndef A2I_CHESS_COMMANDS_H +#define A2I_CHESS_COMMANDS_H + +/* Apple II <-> ESP Commands */ +#define CHESS_NEW_GAME 10 +#define CHESS_GET_AI_MOVE 20 +#define CHESS_GET_GAME_STATUS 22 +#define CHESS_GET_BOARD 23 +#define CHESS_MAKE_MOVE 21 + +/* Responses */ +#define CHESS_INVALID_MOVE 123 +#define CHESS_VALID_MOVE 124 + +/* + * Responses from API... + * "in_progress" + * "black_won" + * "white_won" + * "white_won_resign" + * "black_won_resign" + * "stalemate" + * "insufficient_material" + * "fifty_rule_move" + * "threefold_repitition" + * "unknown" + */ + +#define STATUS_IN_PROGRESS 200 +#define STATUS_BLACK_WON 201 +#define STATUS_WHITE_WON 202 +#define STATUS_STALEMATE 205 +#define STATUS_FIFTY_RULE_MOVE 207 +#define STATUS_UNKNOWN 209 +#define STATUS_UNHANDLED 210 +#define STATUS_ERROR 211 + +#endif diff --git a/examples/template/template.ino b/examples/template/template.ino index 5e97980..83cbd8a 100644 --- a/examples/template/template.ino +++ b/examples/template/template.ino @@ -1,22 +1,143 @@ /* - -Use this program with the Apple2idIOT card and the basic programs RRAM, WRAM and CMDROT to read/write and rot13 -a single string contained within the dual port ram on the card. - +Nathanial Hendler +2021 +github.com/equant */ // Load Wi-Fi library #include #include +//#include +//#include +#include "credentials.h" +#include "a2i_chess.h" + +#define AUTO_CONNECT_TO_WIFI 1 +#define AUTO_CONNECT_TIMEOUT 5 Apple2Idiot a2i = Apple2Idiot(); +//HTTPClient http; + +/*################################################ +# Applications we're going to support # +# +# An Apple ][ running this card may want to run +# several different "apps" that utilize the card. +# These "apps" are classes (instantiated below) +# which the main loop uses to handle/manage +# communication with the Apple. For example, the +# card may want to handle requests to support +# programs on the Apple ][ such as a wifi +# selector, a chess game, and a weather lookup +# app. +################################################*/ + +Chess chess_app = Chess(); + +#define N_APPS 1 +byte app_ids[N_APPS] = {APP_CHESS}; + +/*******************/ +/* Variables */ +/*******************/ + +byte current_app_id; + +const long mainLoopInterval = 100; // millis +//const long mainLoopInterval = 10000; // millis +unsigned long lastMainLoopTime = 0; +byte lastAppleCommand = 0; + +/*################################################ +# Setup # +################################################*/ void setup() { Serial.begin(115200); + a2i.init(); + //chess_app.init(&a2i, &http); + +#ifdef AUTO_CONNECT_TO_WIFI + Serial.println(""); + Serial.print("Starting wifi, connecting to: "); + Serial.println(WIFI_SSID); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + byte wifi_loop_count = 0; + while (WiFi.status() != WL_CONNECTED) { + delay(600); + wifi_loop_count++; + Serial.print("."); + if (wifi_loop_count > AUTO_CONNECT_TIMEOUT) { + break; + } + } + if (WiFi.status() == WL_CONNECTED) { + Serial.println(""); + Serial.println("WiFi connected successfully"); + Serial.print("Got IP: "); + Serial.println(WiFi.localIP()); //Show ESP32 IP on serial + } else { + Serial.println(""); + Serial.println("WiFi connection failed."); + } +#else + // Set WiFi to station mode and disconnect from an AP if it was previously connected + Serial.println("Wifi autoconnect not enabled. No wifi connection attempted."); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); +#endif + + Serial.println("Setup done"); + current_app_id = app_ids[0]; + } +/*################################################ +# Main # +################################################*/ void loop() { + if ((millis() - lastMainLoopTime) > mainLoopInterval) { + byte command_byte = a2i.read_data(APPLE_COMMAND_ADDRESS); + if (command_byte == RAM_BUSY) { + Serial.println("Command Read: RAM BUSY"); + } + else if (command_byte != lastAppleCommand){ + lastAppleCommand = command_byte; + byte result = 0; + Serial.print("Handling command_byte: "); + Serial.println(command_byte); + + /* Do we need to switch app context? */ + + for (int i; i