/*
Block inputs major of 180 points <- ????
When someone win a game and arrive to 0, the display can show the darts thrown
A little buzzer that confirm every digit press, and to play a tune on win
The possibility to clear and reput previous input is great
A game that permit to play high score (21 darts to score as many points as you can)
aledre% — 04/23/2024 10:41 AM
ah ok I think you don't play darts... so when game start the user know that you
start from "501", and if I input 100 as a score (and the display show "100" during typing)
the user know that remain 401, etc... When a player win a game is because the player
goes to 0 and the display can show how many darts were thrown...
usually in dart scorer (almost in italy) when you win a game the scorer multiply
your inputs per 3, ex, if I score 180, 180, 101, 40 the scorer show 12 darts.
If I throw double 20 with first dart, I press number 1 on keyboard and the display
show 10 (in 3 digits show "d10")
*/
// Program to demonstrate the MD_Parola library
// Uses the Arduino Print Class extension with various output types
// MD_MAX72XX library can be found at https://github.com/MajicDesigns/MD_MAX72XX
// Encoder routine https://garrysblog.com/2021/03/20/reliably-debouncing-rotary-encoders-with-arduino-and-esp32/
/*
Project:
Description:
Creation date:
Author: AnonEngineering
License: Beerware
https://garrysblog.com/2021/03/20/reliably-debouncing-rotary-encoders-with-arduino-and-esp32/
*/
/*
To do
point entries to show on the main display too
enter how many legs it takes to win a set, I enter game type and legs to win
when a player win a leg I need to show darts trown and checkout (last score input for go to 0)
we have 2 displays, so in first display can show ex P1 (or P" of course) win! and in second display ex 18d/81 (where 18 are darts trown and 81 checkout)
after ask for score, if player go to 0, ask for darts, always 3, if you checkout it asks "Darts?", then you hit 1 or 2 (or 3)
*/
#include <TM1638plus_Model2.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
// MAX72xx displays
//#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8
#define NUM_ZONES 4
#define CLK_PIN 25
#define DATA_PIN 32
#define CS_PIN 33
#define SPEED_TIME 50
#define PAUSE_TIME 1000
// TM1638 display
#define STROBE_TM 16 // strobe = GPIO connected to strobe line of module
#define CLOCK_TM 17 // clock = GPIO connected to clock line of module
#define DIO_TM 18 // data = GPIO connected to data line of module
const bool SWAP_NYBBLES = false; // default is false, if left out, see note in readme at URL
const bool HIGH_FREQ = true; // default is false, if using a high freq CPU > ~100 MHZ set to true
// Rotary Encoder Inputs
#define EN_PIN_A 26
#define EN_PIN_B 27
#define EN_BTN_PIN 14
#define DEBOUNCE_DELAY 100
#define BUZZ_PIN 23
#define NUM_GAMES 5
// Set PWM properties
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
//int dutyCycle = 200;
const int gameType[NUM_GAMES] = {501, 301, 701, 180, 99}; // 99 = High score
volatile int typeIdx = 0;
// globals
char scoreBuffer[4];
char max72Buffer[16];
char typeBuff[4];
int scores[2];
int legs[2];
bool isLocked = false;
bool player = false;
enum GameState { SELECT_GAME,
ENTER_LEGS,
PLAY_GAME,
GAME_OVER
};
GameState gameState = SELECT_GAME;
char keys[16] = {
'1', '2', '3', 'A',
'4', '5', '6', 'B',
'7', '8', '9', 'C',
'*', '0', '#', 'D'
};
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
TM1638plus_Model2 tm(STROBE_TM, CLOCK_TM , DIO_TM, SWAP_NYBBLES, HIGH_FREQ);
void checkEncoderBtn() {
static int lastState = HIGH;
int state = digitalRead(EN_BTN_PIN);
if (state != lastState) {
lastState = state;
if (state == LOW) {
Serial.println("Play!");
//////////////////////// clear display, then show scores and legs
snprintf(max72Buffer, 16, "%3d %2d %2d %3d", scores[0], legs[0], legs[1], scores[1]);
///////////////////////////////////////////////////////////////ZONES
P.displayZoneText(4, max72Buffer, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
isLocked = true;
} else {
Serial.println("New game");
}
delay(20);
}
}
void checkWin() {
if (scores[0] == 0 || scores[1] == 0) {
if (scores[0] == 0) {
P.displayZoneText(0, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(1, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(2, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(3, "WIN !", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_OPENING); //PA_GROW_UP, PA_GROW_DOWN);
while (!P.getZoneStatus(3))
P.displayAnimate();
legs[0]++;
} else {
P.displayZoneText(0, "WIN !", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_OPENING); //PA_GROW_UP, PA_GROW_DOWN);
P.displayZoneText(1, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(2, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(3, " ", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
while (!P.getZoneStatus(0))
P.displayAnimate();
legs[1]++;
}
delay(3000);
isLocked = false;
scores[0] = gameType[typeIdx];
scores[1] = gameType[typeIdx];
player = 0;
}
}
void playMelody() {
int notes[] = {262, 294, 330, 349, 392, 440, 494, 523, 587}; // C4, D4, E4, F4, G4, A4, B4, C5, D5
for (int i = 0; i < 9; i++) {
tone(BUZZ_PIN, notes[i], 150); // Play each note for 150ms
delay(75); // Delay between notes
}
noTone(BUZZ_PIN); // Stop playing the melody
}
void read_encoder() {
static uint8_t old_AB = 3; // Lookup table index
static int8_t encval = 0; // Encoder value
static const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; // Lookup table
old_AB <<= 2; // Remember previous state
if (digitalRead(EN_PIN_A)) old_AB |= 0x02; // Add current state of pin A
if (digitalRead(EN_PIN_B)) old_AB |= 0x01; // Add current state of pin B
encval += enc_states[( old_AB & 0x0f )];
if ( encval > 3 ) { // Four steps forward, full detent
typeIdx++;
if (typeIdx > NUM_GAMES - 1) typeIdx = 0;
encval = 0;
}
else if ( encval < -3 ) { // Four steps backwards
typeIdx--;
if (typeIdx < 0) typeIdx = NUM_GAMES - 1;
encval = 0;
}
}
void updateZone(int zone, char *value) {
P.displayZoneText(zone, value, PA_CENTER,
SPEED_TIME, PAUSE_TIME, PA_OPENING);
while (!P.getZoneStatus(zone))
P.displayAnimate();
}
// returns button number 0 - 15, only return one per press
int getButtons() {
static bool isReturned = false;
int retVal = -1;
int btnValue = tm.ReadKey16Two();
if (btnValue > 0) {
if (!isReturned) {
isReturned = true;
retVal = log2(btnValue);
//Serial.println(r);
}
} else {
isReturned = false;
}
return retVal;
}
void showSplash() {
P.displayZoneText(5, "GameOn", PA_LEFT, SPEED_TIME, PAUSE_TIME, PA_OPENING, PA_DISSOLVE);
while (!P.getZoneStatus(5)) {
P.displayAnimate();
};
P.displayZoneText(6, "D===-", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_OPENING, PA_DISSOLVE);
while (!P.getZoneStatus(6)) {
P.displayAnimate();
};
P.displayZoneText(4, "D===-", PA_LEFT, 20, 10, PA_SCROLL_LEFT, PA_SCROLL_RIGHT);
while (!P.getZoneStatus(4)) {
P.displayAnimate();
};
/*
P.displayZoneText(4, "Darts!", PA_LEFT, SPEED_TIME,
PAUSE_TIME, PA_SCROLL_LEFT, PA_SCROLL_RIGHT);
while (!P.getZoneStatus(4)) {
P.displayAnimate();
};
//P.displayZoneText(4, "Play darts!", PA_CENTER, 0, 0, PA_PRINT);
//P.displayAnimate();
//delay(3000);
*/
}
void setup()
{
Serial.begin(115200);
tm.displayBegin(); // Init the module
tm.brightness(4);
scores[0] = 0;
scores[1] = 0;
legs[0] = 0;
legs[1] = 0;
pinMode(EN_PIN_A, INPUT_PULLUP);
pinMode(EN_PIN_B, INPUT_PULLUP);
pinMode(EN_BTN_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(EN_PIN_A), read_encoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(EN_PIN_B), read_encoder, CHANGE);
// configure PMW
ledcSetup(pwmChannel, freq, resolution);
ledcAttachPin(BUZZ_PIN, pwmChannel);
P.begin(7);
P.setZone(0, 0, 2);
P.setZone(1, 3, 3);
P.setZone(2, 4, 4);
P.setZone(3, 5, 7);
P.setZone(4, 0, 7);
P.setZone(5, 0, 3); //AleDre P1 All Display
P.setZone(6, 4, 7); //AleDre P2 All Display
P.setInvert(false);
showSplash();
Serial.println("\nSelect game\n");
P.displayClear();
playMelody();
}
void loop() {
static int counter = 0;
int zone = 0;
char serialBuffer[40];
char dispBuffer[9];
char p1Sbuff[4];
char p1Lbuff[2];
char p2Sbuff[4];
char p2Lbuff[2];
if (!isLocked) {
checkEncoderBtn();
sprintf(typeBuff, "%d", gameType[typeIdx]); // converts to decimal base.
P.displayZoneText(3, "Game: ", PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(0, typeBuff, PA_CENTER, 0, 0, PA_PRINT);
P.displayAnimate();
scores[0] = gameType[typeIdx];
scores[1] = gameType[typeIdx];
snprintf(dispBuffer, 9, "PLAY %d", gameType[typeIdx]);
tm.DisplayStr(dispBuffer);
} else {
checkWin();
P.displayZoneText(3, p1Sbuff, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(2, p1Lbuff, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(1, p2Lbuff, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
P.displayZoneText(0, p2Sbuff, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
//P.displayAnimate();
int btnInt = getButtons();
if (btnInt != -1) {
//tone(BUZZ_PIN, 880, 150);
//Serial.println(btnInt);
switch (keys[btnInt]) {
case '*':
Serial.println("Clear");
scoreBuffer[0] = '\0';
counter = 0;
break;
case '#':
//Serial.println("Enter");
scores[player] -= atoi(scoreBuffer);
snprintf(serialBuffer, 40, "Player %d entered %d, score now = %d", player + 1, atoi(scoreBuffer), scores[player]);
Serial.println(serialBuffer);
snprintf(max72Buffer, 16, "%3d %2d %2d %3d", scores[0], legs[0], legs[1], scores[1]);
Serial.println(max72Buffer);
snprintf(p1Sbuff, 4, "%d", scores[0]);
snprintf(p1Lbuff, 2, "%d", legs[0]);
snprintf(p2Sbuff, 4, "%d", scores[1]);
snprintf(p2Lbuff, 4, "%d", legs[1]);
if (player == false) {
updateZone(3, p1Sbuff);
//P.displayZoneText(2, (char)legs[0], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
//P.displayZoneText(1, (char)legs[1], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
//P.displayZoneText(0, scores[1], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
} else {
updateZone(0, p2Sbuff);
//P.displayZoneText(2, (char)legs[0], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
//P.displayZoneText(1, (char)legs[1], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
//P.displayZoneText(0, scores[1], PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT);
}
scoreBuffer[0] = '\0';
counter = 0;
player = !player;
break;
case 'A':
Serial.println("Function A");
break;
case 'B':
Serial.println("Function B");
break;
case 'C':
Serial.println("Function C");
break;
case 'D':
Serial.println("Function D");
break;
default:
scoreBuffer[counter] = keys[btnInt];
scoreBuffer[counter + 1] = '\0';
//Serial.println(scoreBuffer);
snprintf(dispBuffer, 9, "P%d %s", player + 1, scoreBuffer);
counter++;
if (counter >= 3) counter = 0;
}
} else {
snprintf(dispBuffer, 9, "P%d %3s", player + 1, scoreBuffer);
}
tm.DisplayStr(dispBuffer);
}
}
DIO
CLK
STB