Skip to content

Instantly share code, notes, and snippets.

@hyperdriveguy
Last active July 12, 2022 20:35
Show Gist options
  • Save hyperdriveguy/de37869528070128e659aaaf388c5ca7 to your computer and use it in GitHub Desktop.
Save hyperdriveguy/de37869528070128e659aaaf388c5ca7 to your computer and use it in GitHub Desktop.
Arduino RNG interface for keypad and I2C LCD
// (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