obus/src/controller/controller.ino

243 lines
5.6 KiB
Arduino
Raw Normal View History

2020-08-20 21:51:33 +02:00
#include <obus_can.h>
2020-08-31 12:43:55 +02:00
#include <TM1638plus.h>
2020-08-10 18:02:17 +02:00
2020-08-10 18:02:17 +02:00
#define STATE_INACTIVE 0
#define STATE_HELLO 1
#define STATE_GAME 2
2020-08-20 21:13:19 +02:00
#define OBUS_MAX_STRIKES 3 // Number of strikes allowed until game over
2020-08-20 17:47:32 +02:00
#define OBUS_GAME_DURATION 10 // Duration of the game in seconds
2020-08-10 18:02:17 +02:00
2020-08-20 21:51:33 +02:00
#define OBUS_MAX_MODULES 16
#define OBUS_DISC_DURATION 5 // Duration of discovery round in seconds
#define OBUS_UPDATE_INTERVAL 500 // Number of milliseconds between game updates
2020-08-10 22:40:04 +02:00
2020-08-20 17:47:32 +02:00
#define OBUS_GAME_DURATION_MS ((uint32_t) OBUS_GAME_DURATION*1000)
2020-08-20 21:51:33 +02:00
#define OBUS_DISC_DURATION_MS ((uint32_t) OBUS_DISC_DURATION*1000)
2020-08-20 17:47:32 +02:00
2020-08-20 21:13:19 +02:00
#define DIVIDE_CEIL(dividend, divisor) ((dividend + (divisor - 1)) / divisor)
#define MAX_AMOUNT_PUZZLES 256 // The ID of a puzzle is uint8
2020-08-10 18:02:17 +02:00
uint8_t state = STATE_INACTIVE;
2020-08-26 21:13:16 +02:00
struct obus_can::module connected_modules_ids[OBUS_MAX_MODULES];
2020-08-10 21:23:09 +02:00
uint8_t nr_connected_modules;
uint8_t nr_connected_puzzles;
2020-08-20 17:47:32 +02:00
uint8_t strikes;
2020-08-10 21:23:09 +02:00
2020-08-20 21:13:19 +02:00
// Bitvector for checking if game is solved or not
// 32 bits per uint32 bitvector field
#define N_UNSOLVED_PUZZLES DIVIDE_CEIL(MAX_AMOUNT_PUZZLES, 32)
uint32_t unsolved_puzzles[N_UNSOLVED_PUZZLES];
2020-08-20 21:13:19 +02:00
// Timers
2020-08-20 17:47:32 +02:00
uint32_t hello_round_start;
uint32_t game_start;
uint32_t last_update;
2020-08-10 18:02:17 +02:00
2020-08-26 21:13:16 +02:00
struct obus_can::module this_module = {
2020-08-20 17:47:32 +02:00
.type = OBUS_TYPE_CONTROLLER,
.id = OBUS_CONTROLLER_ID
};
2020-08-31 12:43:55 +02:00
// For the display/button chip
#define STROBE_TM 4
#define CLOCK_TM 6
#define DIO_TM 7
#define HI_FREQ false // If using a high freq CPU > ~100 MHZ set to true.
TM1638plus tm(STROBE_TM, CLOCK_TM , DIO_TM, HI_FREQ);
2020-08-10 18:02:17 +02:00
void setup() {
Serial.begin(9600);
2020-08-26 21:13:16 +02:00
obus_can::init();
state = STATE_INACTIVE;
2020-08-31 12:43:55 +02:00
tm.displayBegin();
2020-08-10 22:40:04 +02:00
}
2020-08-20 21:13:19 +02:00
bool check_solved() {
for (uint8_t i = 0; i < N_UNSOLVED_PUZZLES; i++) {
if (unsolved_puzzles[i] != 0) {
return false;
2020-08-18 16:15:09 +02:00
}
}
2020-08-20 21:13:19 +02:00
return true;
}
void add_module_to_bit_vector(uint8_t module_id) {
2020-08-18 16:15:09 +02:00
uint8_t byte_index = module_id >> 3;
uint8_t bit_index = module_id & 0x07;
unsolved_puzzles[byte_index] |= 0x1 << bit_index;
}
void solve_module_in_bit_vector(uint8_t module_id) {
2020-08-18 16:15:09 +02:00
uint8_t byte_index = module_id >> 3;
uint8_t bit_index = module_id & 0x07;
unsolved_puzzles[byte_index] &= ~(0x1 << bit_index);
}
2020-08-10 21:23:09 +02:00
void start_hello() {
state = STATE_HELLO;
2020-08-18 16:15:09 +02:00
hello_round_start = millis();
nr_connected_modules = 0;
nr_connected_puzzles = 0;
2020-08-18 16:15:09 +02:00
// Zero bit vectors
2020-08-20 21:13:19 +02:00
for (uint8_t i = 0; i < N_UNSOLVED_PUZZLES; i++) {
2020-08-18 16:15:09 +02:00
unsolved_puzzles[i] = 0;
}
2020-08-10 21:23:09 +02:00
2020-08-26 21:13:16 +02:00
obus_can::send_c_hello(this_module);
2020-08-20 21:13:19 +02:00
Serial.println(F(" Start of discovery round"));
2020-08-31 12:43:55 +02:00
tm.displayText("dISCOvEr");
2020-08-10 21:23:09 +02:00
}
2020-08-26 21:13:16 +02:00
uint16_t full_module_id(struct obus_can::module mod) {
2020-08-20 21:51:33 +02:00
return \
((uint16_t) mod.type << 8) | \
(uint16_t) mod.id;
}
2020-08-10 20:46:41 +02:00
void receive_hello() {
2020-08-26 21:13:16 +02:00
struct obus_can::message msg;
2020-08-20 17:47:32 +02:00
uint32_t current_time = millis();
2020-08-18 16:15:09 +02:00
2020-08-26 21:13:16 +02:00
if (obus_can::receive(&msg)) {
2020-08-20 21:13:19 +02:00
if (msg.msg_type == OBUS_MSGTYPE_M_HELLO) {
Serial.print(" Registered module ");
2020-08-20 17:47:32 +02:00
Serial.println(full_module_id(msg.from));
2020-08-18 16:15:09 +02:00
2020-08-20 21:51:33 +02:00
if (nr_connected_modules < OBUS_MAX_MODULES) {
connected_modules_ids[nr_connected_modules] = msg.from;
nr_connected_modules++;
if (msg.from.type == OBUS_TYPE_PUZZLE) {
nr_connected_puzzles++;
2020-08-20 21:51:33 +02:00
add_module_to_bit_vector(full_module_id(msg.from));
}
2020-08-18 16:15:09 +02:00
}
2020-08-26 21:13:16 +02:00
obus_can::send_c_ack(this_module);
2020-08-20 21:13:19 +02:00
Serial.println(" ACK");
2020-08-18 16:15:09 +02:00
}
2020-08-20 17:47:32 +02:00
} else if (current_time - hello_round_start > OBUS_DISC_DURATION_MS) {
if (nr_connected_puzzles == 0) {
hello_round_start = current_time;
Serial.println(" No puzzle modules found, restarting discovery round");
}
2020-08-20 21:13:19 +02:00
Serial.println(" End of discovery round");
initialize_game();
2020-08-18 16:15:09 +02:00
}
2020-08-10 20:46:41 +02:00
}
2020-08-10 22:40:04 +02:00
void initialize_game() {
2020-08-20 17:47:32 +02:00
strikes = 0;
2020-08-18 16:15:09 +02:00
game_start = millis();
2020-08-10 22:40:04 +02:00
2020-08-20 17:47:32 +02:00
last_update = game_start;
state = STATE_GAME;
2020-08-20 21:13:19 +02:00
Serial.println(" Game started");
2020-08-20 17:47:32 +02:00
2020-08-26 21:13:16 +02:00
obus_can::send_c_gamestart(this_module, OBUS_GAME_DURATION_MS, strikes, OBUS_MAX_STRIKES);
2020-08-10 22:40:04 +02:00
}
2020-08-10 22:40:04 +02:00
void receive_module_update() {
2020-08-26 21:13:16 +02:00
struct obus_can::message msg;
2020-08-20 17:47:32 +02:00
2020-08-26 21:13:16 +02:00
if (obus_can::receive(&msg)) {
2020-08-20 17:47:32 +02:00
switch (msg.msg_type) {
case OBUS_MSGTYPE_M_STRIKE:
// TODO check idempotency ID
strikes++;
break;
case OBUS_MSGTYPE_M_SOLVED:
solve_module_in_bit_vector(full_module_id(msg.from));
break;
default:
Serial.print(F("W Ignoring msg "));
Serial.println(msg.msg_type);
break;
2020-08-18 16:15:09 +02:00
}
2020-08-20 17:47:32 +02:00
2020-08-18 16:15:09 +02:00
}
2020-08-10 22:40:04 +02:00
}
2020-08-10 22:40:04 +02:00
void game_loop() {
2020-08-20 17:47:32 +02:00
uint32_t current_time = millis();
uint32_t time_elapsed = current_time - game_start;
uint32_t time_left =
OBUS_GAME_DURATION_MS < time_elapsed ? 0 : OBUS_GAME_DURATION_MS - time_elapsed;
// We cannot check for '<= 0' in an uint type so we check the terms prior to subtraction
2020-08-18 16:15:09 +02:00
receive_module_update();
if (check_solved()) {
2020-08-20 21:13:19 +02:00
Serial.println(" Game solved");
2020-08-26 21:13:16 +02:00
obus_can::send_c_solved(this_module, time_left, strikes, OBUS_MAX_STRIKES);
2020-08-18 16:15:09 +02:00
state = STATE_INACTIVE;
2020-08-31 12:43:55 +02:00
tm.displayText("dISArmEd");
2020-08-18 16:15:09 +02:00
return;
2020-08-20 21:13:19 +02:00
}
if (time_left == 0) {
Serial.println(" Time's up");
2020-08-26 21:13:16 +02:00
obus_can::send_c_timeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
2020-08-18 16:15:09 +02:00
state = STATE_INACTIVE;
2020-08-31 12:43:55 +02:00
tm.displayText("boom");
2020-08-18 16:15:09 +02:00
return;
2020-08-20 21:13:19 +02:00
}
if (strikes >= OBUS_MAX_STRIKES) {
Serial.println(" Strikeout");
2020-08-26 21:13:16 +02:00
obus_can::send_c_strikeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
2020-08-18 16:15:09 +02:00
state = STATE_INACTIVE;
2020-08-31 12:43:55 +02:00
tm.displayText("boom");
2020-08-18 16:15:09 +02:00
return;
}
2020-08-20 17:47:32 +02:00
if (last_update + OBUS_UPDATE_INTERVAL <= current_time) {
2020-08-26 21:13:16 +02:00
obus_can::send_c_state(this_module, time_left, strikes, OBUS_MAX_STRIKES);
2020-08-18 16:15:09 +02:00
last_update = current_time;
2020-08-31 12:43:55 +02:00
int totalsec = (current_time + 100) / 1000;
int minutes = totalsec / 60;
char buffer[10];
snprintf(buffer, 10, "%06d.%02d", minutes, totalsec % 60);
tm.displayText(buffer);
2020-08-18 16:15:09 +02:00
}
2020-08-10 22:40:04 +02:00
}
2020-08-10 18:02:17 +02:00
void loop() {
switch (state) {
case STATE_INACTIVE:
start_hello();
break;
case STATE_HELLO:
receive_hello();
break;
case STATE_GAME:
2020-08-18 16:15:09 +02:00
game_loop();
break;
2020-08-18 16:15:09 +02:00
}
2020-08-10 18:02:17 +02:00
}