reorganizing stuff from shed

This commit is contained in:
equant 2022-01-10 08:23:17 -07:00
parent 8b60e2336e
commit 61be7fe036
5 changed files with 510 additions and 4 deletions

View File

@ -0,0 +1,281 @@
#include <stdio.h>
#include <string.h>
#include <Arduino.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Apple2Idiot.h>
#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<char *>();
}
} 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<char *>();
}
} 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 *>());
char* pch = NULL;
pch = strtok((char *)doc["board"].as<char *>(), "\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.
}

View File

@ -0,0 +1,66 @@
#ifndef A2I_CHESS_H
#define A2I_CHESS_H
#include <Arduino.h>
#include <HTTPClient.h>
#include <A2I_commands.h>
#include <Apple2Idiot.h>
#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

View File

@ -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

View File

@ -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 <WiFi.h>
#include <Apple2Idiot.h>
//#include <ArduinoJson.h>
//#include <HTTPClient.h>
#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<N_APPS; i++) {
if (command_byte == app_ids[i]) {
// We were sent an app id.
Serial.println("We were sent an app id");
if (current_app_id != command_byte) {
// We are talking to a new app
Serial.println("We are talking to a new app");
a2i.write_data(ESP_COMMAND_ADDRESS, ACK);
current_app_id = command_byte;
}
a2i.write_data(APPLE_COMMAND_ADDRESS, ACK);
a2i.write_data(ESP_COMMAND_ADDRESS, EOT);
lastMainLoopTime = millis();
return;
}
}
/* If we got here, we need to pass the command to app's class to be handled */
if (current_app_id == chess_app.appId) {
Serial.println("Received a command for Chess()");
chess_app.handleCommand(command_byte);
a2i.write_data(APPLE_COMMAND_ADDRESS, ACK);
//a2i.write_data(ESP_COMMAND_ADDRESS, EOT); // notify Apple IIe we are done processing command byte
}
}
lastMainLoopTime = millis();
}
}