/*
DARTS SCORER by AleDre
Thanks a lot to Anon
Press "A" for...
Press "B" for delete last input
Press "C" for Reset Game
Press "D" for next leg
Press "#" for insert score
Press "*" for correct INPUT
*/
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Keypad.h>
#include <Wire.h>
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8
#define NUM_ZONES 4
#define CLK_PIN 14
#define DATA_PIN 13
#define CS_PIN 12
#define SPEED_TIME 50
#define PAUSE_TIME 500
#define ROTARY_PIN1 26
#define ROTARY_PIN2 33 //27 on my esp
#define ROTARY_BUTTON 34 //25 on my esp
#define BUZZ_PIN 20
#define DEBOUNCE_DELAY 200
#define RESET_DEBOUNCE_DELAY 100
// Set PWM properties
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Keypad config
const byte ROWS = 4;
const byte COLS = 4;
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {21, 19, 18, 5}; // Pin Rows
byte colPins[COLS] = {17, 16, 4, 0}; // Pin Columns
// Keypad Init
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
enum GameState { SELECT_GAME,
ENTER_LEGS,
PLAY_GAME,
GAME_OVER };
GameState gameState = SELECT_GAME;
int lastEncoded = 0;
unsigned long lastDebounceTime = 0;
int availableScores[] = { 180, 301, 501, 701, 901 };
int playerScores[2] = { 0, 0 };
int legsToWin[2] = { 0, 0 };
int legsWon[2] = { 0, 0 };
int dartsThrown[2] = { 0, 0 };
int scoreIndex = 3;
bool scoreLocked = false;
int currentPlayer = 1;
String currentInput = "";
int previousScore = 0;
int lastPlayer = 0;
bool legFinished = false;
int startingPlayer = 1;
int lastScoreInput = 0;
int prevPlayerScores[2] = { 0, 0 };
int prevLegsWon[2] = { 0, 0 };
int prevDartsThrown[2] = { 0, 0 };
int prevCurrentPlayer = 1;
bool isValidScore(int score) {
int invalidScores[] = { 179, 178, 176, 175, 173, 172, 169, 166, 163 };
if (score > playerScores[currentPlayer - 1]) return false;
if (score > 180) return false;
for (int i = 0; i < sizeof(invalidScores) / sizeof(invalidScores[0]); i++) {
if (score == invalidScores[i]) return false;
}
return true;
}
void handleRotaryEncoder() {
int MSB = digitalRead(ROTARY_PIN1);
int LSB = digitalRead(ROTARY_PIN2);
int currentEncoded = (MSB << 1) | LSB;
int sum = (lastEncoded << 2) | currentEncoded;
unsigned long currentTime = millis();
char avScores[10];
char legToWin[10];
if (gameState == SELECT_GAME) {
if (currentTime - lastDebounceTime > DEBOUNCE_DELAY) {
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
scoreIndex = min(scoreIndex + 1, (int)(sizeof(availableScores) / sizeof(availableScores[0]) - 1));
P.displayZoneText(5, "Game?", PA_CENTER, 0, 0, PA_NO_EFFECT);
itoa(availableScores[scoreIndex], avScores, 10);
P.displayZoneText(6, avScores, PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
tone(BUZZ_PIN, 300, 10);
lastDebounceTime = currentTime;
} else if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
scoreIndex = max(scoreIndex - 1, 0);
P.displayZoneText(5, "Game?", PA_CENTER, 0, 0, PA_NO_EFFECT);
itoa(availableScores[scoreIndex], avScores, 10);
P.displayZoneText(6, avScores, PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
tone(BUZZ_PIN, 300, 10);
lastDebounceTime = currentTime;
}
}
} else if (gameState == ENTER_LEGS) {
if (currentTime - lastDebounceTime > DEBOUNCE_DELAY) {
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
legsToWin[0] = legsToWin[1] = min(legsToWin[0] + 1, 9);
itoa(legsToWin[1], legToWin, 10);
P.displayZoneText(6, legToWin, PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayZoneText(5, "Legs?", PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
tone(BUZZ_PIN, 300, 10);
lastDebounceTime = currentTime;
} else if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
legsToWin[0] = legsToWin[1] = max(legsToWin[0] - 1, 1);
itoa(legsToWin[0], legToWin, 10);
P.displayZoneText(6, legToWin, PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayZoneText(5, "Legs?", PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
tone(BUZZ_PIN, 300, 10);
lastDebounceTime = currentTime;
}
}
}
lastEncoded = currentEncoded;
}
void handleButtonPress() {
bool buttonState = digitalRead(ROTARY_BUTTON) == LOW;
if (buttonState && millis() - lastDebounceTime > DEBOUNCE_DELAY) {
if (gameState == SELECT_GAME) {
gameState = ENTER_LEGS;
currentInput = "";
P.displayZoneText(5, "Legs?", PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(6, "#", PA_CENTER, 0, 0, PA_PRINT);
P.displayAnimate();
tone(BUZZ_PIN, 400, 10);
} else if (gameState == ENTER_LEGS && legsToWin[0] > 0) {
gameState = PLAY_GAME;
legsWon[0] = legsWon[1] = 0;
dartsThrown[0] = dartsThrown[1] = 0;
playerScores[0] = playerScores[1] = availableScores[scoreIndex];
currentPlayer = 1;
updateDisplays();
tone(BUZZ_PIN, 500, 10);
}
lastDebounceTime = millis();
}
}
void handleScore() {
String ultimoPlayer = String(lastPlayer);
if (playerScores[currentPlayer - 1] == 0) {
legsWon[lastPlayer - 1]++;
legFinished = true;
int darts = dartsThrown[lastPlayer - 1];
dartsThrown[0] = dartsThrown[1] = 0;
char dartsStr[10];
itoa(darts, dartsStr, 10);
P.displayClear();
P.displayZoneText(0, "", PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayZoneText(1, ultimoPlayer.c_str(), PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
}
}
void playMelody() {
int melody[] = {262, 294, 330, 349, 392, 440, 494}; // Frequenze delle note
int noteDurations[] = {100, 50, 50, 50, 50, 50, 50}; // Durata delle note in ms
int pauseBetweenNotes = 50; // Pausa tra le note in ms
for (int i = 0; i < 7; i++) {
tone(BUZZ_PIN, melody[i], noteDurations[i]);
delay(noteDurations[i] - pauseBetweenNotes); // Pausa tra le note
noTone(BUZZ_PIN); // Arresta la riproduzione della nota prima di riprodurne un'altra
delay(pauseBetweenNotes); // Pausa tra le note
}
}
void playMelodyLeg() {
int melody[] = {262, 392, 262, 392, 262, 393, 494}; // Frequenze delle note
int noteDurations[] = {100, 50, 50, 50, 50, 50, 50}; // Durata delle note in ms
int pauseBetweenNotes = 50; // Pausa tra le note in ms
for (int i = 0; i < 7; i++) {
tone(BUZZ_PIN, melody[i], noteDurations[i]);
delay(noteDurations[i] - pauseBetweenNotes); // Pausa tra le note
noTone(BUZZ_PIN); // Arresta la riproduzione della nota prima di riprodurne un'altra
delay(pauseBetweenNotes); // Pausa tra le note
}
}
void playMelodySet() {
int melody[] = {262, 392, 262, 392, 262, 393, 494}; // Frequenze delle note
int noteDurations[] = {100, 50, 50, 50, 50, 50, 50}; // Durata delle note in ms
int pauseBetweenNotes = 50; // Pausa tra le note in ms
for (int i = 0; i < 7; i++) {
tone(BUZZ_PIN, melody[i], noteDurations[i]);
delay(noteDurations[i] - pauseBetweenNotes); // Pausa tra le note
noTone(BUZZ_PIN); // Arresta la riproduzione della nota prima di riprodurne un'altra
delay(pauseBetweenNotes); // Pausa tra le note
}
}
void handleKeypad() {
char key = customKeypad.getKey();
String correntePlayer = String(currentPlayer);
if (key) {
tone(BUZZ_PIN, 500, 10);
if (key == 'D') {
if (gameState == PLAY_GAME && legFinished) {
legFinished = false;
playerScores[0] = playerScores[1] = availableScores[scoreIndex];
dartsThrown[0] = dartsThrown[1] = 0;
startingPlayer = 3 - startingPlayer;
currentPlayer = startingPlayer;
updateDisplays();
}
} else if (key == '*') {
if (gameState == PLAY_GAME) {
currentInput = "";
displayCurrentInput();
updateDisplays();
}
} else if (key == '#') {
if (gameState == PLAY_GAME && currentInput.length() > 0) {
int score = currentInput.toInt();
if (isValidScore(score)) {
// Memorizza lo stato precedente
prevPlayerScores[0] = playerScores[0];
prevPlayerScores[1] = playerScores[1];
prevLegsWon[0] = legsWon[0];
prevLegsWon[1] = legsWon[1];
prevDartsThrown[0] = dartsThrown[0];
prevDartsThrown[1] = dartsThrown[1];
prevCurrentPlayer = currentPlayer;
previousScore = score;
lastPlayer = currentPlayer;
playerScores[currentPlayer - 1] -= score;
dartsThrown[currentPlayer - 1] += 3;
lastScoreInput = currentInput.toInt();
currentInput = "";
currentPlayer = currentPlayer % 2 + 1;
updateDisplays();
if (playerScores[lastPlayer - 1] == 0) {
legsWon[lastPlayer - 1]++;
legFinished = true;
if (legsWon[lastPlayer - 1] == legsToWin[0]) {
gameState = GAME_OVER;
P.displayClear();
int darts = dartsThrown[lastPlayer - 1];
dartsThrown[0] = dartsThrown[1] = 0; // Reset for the next leg
char dartsStr[10];
char lastScoreStr[10];
itoa(darts, dartsStr, 10);
itoa(lastScoreInput, lastScoreStr, 10);
char displayStr[30]; // Buffer to hold the combined string
snprintf(displayStr, sizeof(displayStr), "%s:%s", dartsStr, lastScoreStr);
P.displayZoneText(1, correntePlayer.c_str(), PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(0, "SET!", PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(6, displayStr, PA_CENTER, 0, 0, PA_PRINT);
P.displayAnimate();
playMelodySet();
} else {
P.displayClear();
int darts = dartsThrown[lastPlayer - 1];
dartsThrown[0] = dartsThrown[1] = 0; // Reset for the next leg
char dartsStr[10];
char lastScoreStr[10];
itoa(darts, dartsStr, 10);
itoa(lastScoreInput, lastScoreStr, 10);
char displayStr[30]; // Buffer to hold the combined string
snprintf(displayStr, sizeof(displayStr), "%s:%s", dartsStr, lastScoreStr);
P.displayZoneText(0, "LEG!", PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(1, correntePlayer.c_str(), PA_CENTER, 0, 0, PA_PRINT);
P.displayZoneText(6, displayStr, PA_CENTER, 0, 0, PA_PRINT);
P.displayAnimate();
playMelodyLeg();
}
}
} else {
currentInput = "";
}
}
} else if (key == 'B') {
if (gameState == PLAY_GAME) {
// Ripristina lo stato precedente
playerScores[0] = prevPlayerScores[0];
playerScores[1] = prevPlayerScores[1];
legsWon[0] = prevLegsWon[0];
legsWon[1] = prevLegsWon[1];
dartsThrown[0] = prevDartsThrown[0];
dartsThrown[1] = prevDartsThrown[1];
currentPlayer = prevCurrentPlayer;
updateDisplays();
}
} else if (key == 'C') {
if (gameState == GAME_OVER || gameState == PLAY_GAME || gameState == ENTER_LEGS || gameState == SELECT_GAME) {
gameState = SELECT_GAME;
currentInput = "";
P.displayClear();
P.displayZoneText(5, "Game", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_OPENING);
P.displayZoneText(6, "Over", PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_OPENING);
P.displayAnimate();
}
} else {
if (gameState == PLAY_GAME && currentInput.length() < 3) {
switch (key) {
case '1':
currentInput += '1';
break;
case '2':
currentInput += '2';
break;
case '3':
currentInput += '3';
break;
case '4':
currentInput += '4';
break;
case '5':
currentInput += '5';
break;
case '6':
currentInput += '6';
break;
case '7':
currentInput += '7';
break;
case '8':
currentInput += '8';
break;
case '9':
currentInput += '9';
break;
case '0':
currentInput += '0';
break;
default:
break;
}
displayCurrentInput();
}
}
}
}
void updateDisplays() {
char legWonP1[10];
char legWonP2[10];
char playerScoresP1[10];
char playerScoresP2[10];
P.displayClear();
itoa(legsWon[0], legWonP1, 10);
if (currentPlayer == 1) {
strcat(legWonP1, ":");
}
P.displayZoneText(1, legWonP1, PA_LEFT, 0, 0, PA_NO_EFFECT); //Player1 LEGS
itoa(playerScores[0], playerScoresP1, 10);
P.displayZoneText(0, playerScoresP1, PA_CENTER, 0, 0, PA_NO_EFFECT); //Player1 SCORE
itoa(legsWon[1], legWonP2, 10);
if (currentPlayer == 2) {
strcat(legWonP2, ":");
}
P.displayZoneText(3, legWonP2, PA_LEFT, 0, 0, PA_NO_EFFECT); //Player2 LEGS
itoa(playerScores[1], playerScoresP2, 10);
P.displayZoneText(2, playerScoresP2, PA_CENTER, 0, 0, PA_NO_EFFECT); //Player2 SCORE
P.displayAnimate();
}
void displayCurrentInput() {
String curScorInput = String(currentInput);
// Se il currentPlayer è 1, visualizza il punteggio corrente su P1
if (currentPlayer == 1) {
P.displayClear(0);
P.displayZoneText(0, curScorInput.c_str(), PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
}
// Se il currentPlayer è 2, visualizza il punteggio corrente su P2
else if (currentPlayer == 2) {
P.displayClear(2);
P.displayZoneText(2, curScorInput.c_str(), PA_CENTER, 0, 0, PA_NO_EFFECT);
P.displayAnimate();
}
}
void showSplash() {
P.displayZoneText(5, "D===-", PA_CENTER, 10, 0, PA_SLICE);
P.displayZoneText(6, "GameOn", PA_CENTER, 10, 0, PA_SLICE);
P.displayAnimate();
playMelody();
}
void showSplash2() {
P.displayZoneText(5, "by", PA_CENTER, 10, 0, PA_SLICE);
P.displayZoneText(6, "AleDre", PA_CENTER, 10, 0, PA_SLICE);
P.displayAnimate();
}
void setup() {
Serial.begin(115200);
pinMode(ROTARY_PIN1, INPUT_PULLUP);
pinMode(ROTARY_PIN2, INPUT_PULLUP);
pinMode(ROTARY_BUTTON, INPUT_PULLUP);
ledcAttachPin(BUZZ_PIN, pwmChannel);
ledcSetup(pwmChannel, freq, resolution);
P.begin(7);
P.setZone(0, 0, 2); //AleDre P1 Score
P.setZone(1, 3, 3); //AleDre P1 Legs
P.setZone(2, 4, 6); //AleDre P2 Score
P.setZone(3, 7, 7); //AleDre P2 Legs
P.setZone(4, 0, 7); //AleDre All Displays
P.setZone(5, 0, 3); //AleDre P1 All Display
P.setZone(6, 4, 7); //AleDre P2 All Display
P.setIntensity(15);
P.setInvert(false);
showSplash();
}
void loop() {
if (gameState == SELECT_GAME || gameState == ENTER_LEGS) {
handleRotaryEncoder();
P.displayAnimate();
handleButtonPress();
P.displayAnimate();
} else if (gameState == PLAY_GAME || gameState == GAME_OVER) {
handleKeypad();
P.displayAnimate();
}
}
Loading
esp32-s2-devkitm-1
esp32-s2-devkitm-1