Last active
July 12, 2022 20:35
-
-
Save hyperdriveguy/de37869528070128e659aaaf388c5ca7 to your computer and use it in GitHub Desktop.
Arduino RNG interface for keypad and I2C LCD
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (c) Carson Bush 2022 | |
// This program is free software: you can redistribute it and/or modify it under the terms | |
// of the GNU General Public License as published by the Free Software Foundation, either | |
// version 3 of the License, or (at your option) any later version. | |
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
// See the GNU General Public License for more details. | |
// You should have received a copy of the GNU General Public License along with this program. | |
// If not, see <https://www.gnu.org/licenses/>/. | |
#include <LiquidCrystal_I2C.h> | |
#include <Keypad.h> | |
#define LCD_WIDTH 16 | |
#define LCD_PART 1 / LCD_WIDTH | |
// Keypad constants | |
#define ROWS 4 | |
#define COLS 4 | |
const char keys[ROWS][COLS] = { | |
{'1','2','3','A'}, | |
{'4','5','6','B'}, | |
{'7','8','9','C'}, | |
{'*','0','#','D'} | |
}; | |
// Keypad pinouts | |
byte rowPins[ROWS] = {9, 8, 7, 6}; | |
byte colPins[COLS] = {5, 4, 3, 2}; | |
// Custom characters | |
#define ARROW_RIGHT_CHR 0 | |
#define ARROW_LEFT_CHR 1 | |
#define DICE_CHR 2 | |
#define PROG_BEGIN_CHR 3 | |
#define PROG_MID_EMPTY_CHR 4 | |
#define PROG_MID_FULL_CHR 5 | |
#define PROG_END_EMPTY_CHR 6 | |
#define PROG_END_FULL_CHR 7 | |
// Classes for keypad and LCD | |
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); | |
// I2C address is 0x27 | |
LiquidCrystal_I2C lcd(0x27, LCD_WIDTH, 2); | |
// The command buffer is a constant of 15 + null terminator because the lcd is 16 wide | |
char command_buffer[LCD_WIDTH] = ""; | |
byte command_buffer_index = 0; | |
void loadingScreen(byte progress, byte target) { | |
float percent_done = (float)progress / (float)target; | |
int progress_bar_len = (int)round(percent_done * LCD_WIDTH); | |
lcd.clear(); | |
lcd.print("Rolling "); | |
lcd.print(progress); | |
for (byte i = 0; i < progress % 3; i++){ | |
lcd.write(' '); | |
} | |
lcd.write(DICE_CHR); | |
lcd.setCursor(0, 1); | |
lcd.write(PROG_BEGIN_CHR); | |
for (byte i = 1; i < progress_bar_len; i++) { | |
lcd.write(PROG_MID_FULL_CHR); | |
} | |
for (byte i = progress_bar_len; i < LCD_WIDTH - 1; i++) { | |
lcd.write(PROG_MID_EMPTY_CHR); | |
} | |
if (progress_bar_len >= LCD_WIDTH) { | |
lcd.write(PROG_END_FULL_CHR); | |
} | |
else { | |
lcd.write(PROG_END_EMPTY_CHR); | |
} | |
} | |
unsigned int* makeRoll(byte num_dice, unsigned int sides) { | |
// num_dice to uses a byte to prevent OOM | |
unsigned int* rolls = (unsigned int*)malloc(num_dice * sizeof(unsigned int)); | |
if (num_dice == 0 || sides == 0) { | |
rolls[0] = 0; | |
} | |
else if (sides == 1) { | |
rolls[0] = 1; | |
} | |
else { | |
//Serial.println("Doing RNG..."); | |
randomSeed(analogRead(5) * analogRead(2)); | |
for (byte i = 0; i < num_dice; i++) { | |
if (num_dice > 1) { | |
// Progress update | |
loadingScreen(i, num_dice); | |
char key = keypad.getKey(); | |
if (key == '*') { | |
lcd.clear(); | |
lcd.print("Cancelled Rolls"); | |
rolls[0] = 0; | |
delay(800); | |
break; | |
} | |
} | |
// Make seed from unconnected garbage input | |
// This reads from A0 through A3 | |
randomSeed(analogRead(i % 4) * analogRead(random(1, 4) % 4)); | |
// Sleep to help seeds differ | |
delay(random(1, 1000)); | |
rolls[i] = random(1, sides + 1); | |
//Serial.println(rolls[i]); | |
} | |
} | |
return rolls; | |
} | |
void displayHome() { | |
//Serial.println("Initializing home screen"); | |
lcd.clear(); | |
lcd.home(); | |
lcd.print("Dice Roller "); | |
lcd.write(DICE_CHR); | |
lcd.setCursor(0, 1); | |
lcd.write(ARROW_RIGHT_CHR); | |
command_buffer[0] = 0; | |
command_buffer_index = 0; | |
} | |
void writeBufferLCD(char to_write) { | |
// Handle backspace | |
if (to_write == 'B') { | |
if (command_buffer_index == 0) { | |
return; | |
} | |
command_buffer_index--; | |
command_buffer[command_buffer_index] = 0; | |
lcd.setCursor(command_buffer_index + 1, 1); | |
lcd.write(' '); | |
return; | |
} | |
// Early return if buffer is full and not deleting a char | |
if (command_buffer_index >= 15) { | |
return; | |
} | |
// Write dice symbol instead of "D" | |
if (to_write == 'D') { | |
to_write = DICE_CHR; | |
} | |
// Normal buffer and lcd write | |
command_buffer[command_buffer_index] = to_write; | |
command_buffer_index++; | |
// Write null byte after new char | |
command_buffer[command_buffer_index] = 0; | |
lcd.setCursor(1, 1); | |
lcd.print(command_buffer); | |
} | |
void addDigit(unsigned int* addend, char to_add) { | |
*addend *= 10; | |
*addend += (to_add - 48); | |
} | |
struct dice_attr_s { | |
byte num_dice; | |
unsigned int sides; | |
}; | |
struct dice_attr_s* getRollSpecs() { | |
unsigned int num_dice = 0; | |
unsigned int sides = 0; | |
bool encounter_div = false; | |
for (int i = 0; i < LCD_WIDTH; i++) { | |
char parse_char = command_buffer[i]; | |
//Serial.print("Parsing char: "); | |
//Serial.println(parse_char); | |
// End on null char | |
if (parse_char == 0) { | |
break; | |
} | |
// Error checking for format | |
if (isAlpha(parse_char)) { | |
//Serial.println("Exiting due to error correction"); | |
num_dice = 0; | |
break; | |
} | |
// Add digits to proper values | |
if (encounter_div) { | |
if (parse_char == DICE_CHR) { | |
break; | |
} | |
//Serial.print("Adding "); | |
//Serial.print(parse_char); | |
//Serial.print(" to "); | |
//Serial.println(sides); | |
addDigit(&sides, parse_char); | |
//Serial.print("After: "); | |
//Serial.println(sides); | |
} | |
else if (parse_char == DICE_CHR) { | |
encounter_div = true; | |
//Serial.println("Encountered divider"); | |
} | |
else { | |
//Serial.print("Adding "); | |
//Serial.print(parse_char); | |
//Serial.print(" to "); | |
//Serial.println(num_dice); | |
addDigit(&num_dice, parse_char); | |
//Serial.print("After: "); | |
//Serial.println(num_dice); | |
} | |
} | |
// Error check against single number input | |
if (sides == 0) { | |
num_dice = 0; | |
} | |
if (sides == 65535) { | |
sides = 65534; | |
} | |
dice_attr_s* dice_spec = (dice_attr_s*)malloc(sizeof(dice_attr_s)); | |
dice_spec->num_dice = num_dice; | |
dice_spec->sides = sides; | |
return dice_spec; | |
} | |
void printResult(dice_attr_s* dice_spec, uint rolled, byte index) { | |
//Serial.println("Doing LCD info drop"); | |
//Serial.println("printResult locals:"); | |
//Serial.print(dice_spec->num_dice); | |
//Serial.print('/'); | |
//Serial.println(dice_spec->sides); | |
lcd.clear(); | |
if (index > 0) { | |
lcd.write(ARROW_LEFT_CHR); | |
} | |
lcd.setCursor(1, 0); | |
lcd.print(dice_spec->num_dice); | |
lcd.write(DICE_CHR); | |
lcd.print(dice_spec->sides); | |
lcd.write(' '); | |
lcd.write(DICE_CHR); | |
lcd.print(index + 1); | |
if (index < dice_spec->num_dice - 1) { | |
lcd.setCursor(15, 0); | |
lcd.write(ARROW_RIGHT_CHR); | |
} | |
lcd.setCursor(0, 1); | |
// Easter egg for one sided die rolls | |
if (dice_spec->sides == 1) { | |
lcd.print("OK DADDY"); | |
return; | |
} | |
else { | |
lcd.print(rolled); | |
} | |
// Critical Positive and Negatives | |
if (rolled == dice_spec->sides) { | |
lcd.print(" YEET"); | |
} | |
if (rolled == 1) { | |
lcd.print(" OOF"); | |
} | |
} | |
void rollResultsScreen() { | |
// Make rolls | |
dice_attr_s* dice_spec = (dice_attr_s*)malloc(sizeof(dice_attr_s)); | |
dice_spec = getRollSpecs(); | |
if (dice_spec->num_dice == 0) { | |
return; | |
} | |
//Serial.print(dice_spec->num_dice); | |
//Serial.print('/'); | |
//Serial.println(dice_spec->sides); | |
uint* dice_rolls = (uint*)malloc(dice_spec->num_dice * sizeof(uint)); | |
dice_rolls = makeRoll(dice_spec->num_dice, dice_spec->sides); | |
if (dice_rolls[0] == 0) { | |
return; | |
} | |
byte die_index = 0; | |
bool exit = false; | |
do { | |
if (die_index >= dice_spec->num_dice) { | |
die_index = 0; | |
} | |
printResult(dice_spec, dice_rolls[die_index], die_index); | |
input_wait: | |
char key = keypad.getKey(); | |
switch (key) { | |
case '*': | |
exit = true; | |
break; | |
case '#': | |
// Make seed from unconnected garbage input | |
randomSeed(analogRead(1)); | |
dice_rolls = makeRoll(dice_spec->num_dice, dice_spec->sides); | |
if (dice_rolls[0] == 0) { | |
return; | |
} | |
die_index = 0; | |
break; | |
case 'A': | |
if (dice_spec->num_dice == 1) { | |
goto input_wait; | |
} | |
die_index++; | |
break; | |
case 'B': | |
if (dice_spec->num_dice == 1) { | |
goto input_wait; | |
} | |
if (die_index == 0) { | |
die_index = dice_spec->num_dice; | |
} | |
die_index--; | |
break; | |
default: | |
goto input_wait; | |
} | |
} while (!exit); | |
free(dice_spec); | |
free(dice_rolls); | |
} | |
void setup() { | |
//Serial.begin(9600); | |
//Serial.println("Started Serial"); | |
//Serial.println("Doing LCD init"); | |
lcd.init(); | |
lcd.backlight(); | |
// Make custom characters | |
byte diceChar[8] = { | |
0b00000, | |
0b11111, | |
0b10101, | |
0b11011, | |
0b10101, | |
0b11111, | |
0b00000, | |
0b00000 | |
}; | |
byte arrowRightChar[8] = { | |
0b10000, | |
0b11000, | |
0b01100, | |
0b00110, | |
0b00110, | |
0b01100, | |
0b11000, | |
0b10000 | |
}; | |
byte arrowLeftChar[8] = { | |
0b00001, | |
0b00011, | |
0b00110, | |
0b01100, | |
0b01100, | |
0b00110, | |
0b00011, | |
0b00001 | |
}; | |
byte progbarBegin[8] = { | |
0b00000, | |
0b00000, | |
0b01111, | |
0b11111, | |
0b11111, | |
0b01111, | |
0b00000, | |
0b00000 | |
}; | |
byte progbarEndEmpty[8] = { | |
0b00000, | |
0b00000, | |
0b11110, | |
0b00001, | |
0b00001, | |
0b11110, | |
0b00000, | |
0b00000 | |
}; | |
byte progbarEndFull[8] = { | |
0b00000, | |
0b00000, | |
0b11110, | |
0b11111, | |
0b11111, | |
0b11110, | |
0b00000, | |
0b00000 | |
}; | |
byte progbarMidEmpty[8] = { | |
0b00000, | |
0b00000, | |
0b11111, | |
0b00000, | |
0b00000, | |
0b11111, | |
0b00000, | |
0b00000 | |
}; | |
byte progbarMidFull[8] = { | |
0b00000, | |
0b00000, | |
0b11111, | |
0b11111, | |
0b11111, | |
0b11111, | |
0b00000, | |
0b00000 | |
}; | |
lcd.createChar(DICE_CHR, diceChar); | |
lcd.createChar(ARROW_RIGHT_CHR, arrowRightChar); | |
lcd.createChar(ARROW_LEFT_CHR, arrowLeftChar); | |
lcd.createChar(PROG_BEGIN_CHR, progbarBegin); | |
lcd.createChar(PROG_END_EMPTY_CHR, progbarEndEmpty); | |
lcd.createChar(PROG_END_FULL_CHR, progbarEndFull); | |
lcd.createChar(PROG_MID_EMPTY_CHR, progbarMidEmpty); | |
lcd.createChar(PROG_MID_FULL_CHR, progbarMidFull); | |
displayHome(); | |
//Serial.println("Waiting for input..."); | |
} | |
void loop() { | |
char key = keypad.getKey(); | |
switch (key) { | |
case 0: | |
break; | |
case '*': | |
displayHome(); | |
break; | |
case 'A': | |
command_buffer[0] = '1'; | |
command_buffer[1] = DICE_CHR; | |
command_buffer[2] = '2'; | |
command_buffer[3] = '0'; | |
command_buffer[4] = 0; | |
rollResultsScreen(); | |
break; | |
case '#': | |
//Serial.println("Showing roll results..."); | |
rollResultsScreen(); | |
displayHome(); | |
break; | |
case 'C': | |
//Serial.println("Shortcut not implemented"); | |
break; | |
default: | |
//Serial.println("Writing to buffer..."); | |
writeBufferLCD(key); | |
//Serial.print("Wrote: "); | |
//Serial.print(command_buffer); | |
//Serial.println(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment