/*
* Torn.cpp / Torn.ino
*
* Code by: Gray Mack
* License: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
* https://creativecommons.org/licenses/by-nc-sa/4.0/
* Created: //2020
* One Line Description:
* Board:
* Select Board: Arduino Mega or Mega 2560
* Other Hardware:
* Detailed Description:
*
* Rev History:
*
* 03/10/2011 initial code creation
* 03/11/2011 dorkbot release, blinky lights, still some bugs
* 03/15/2011 Some refactoring and cleanup, bug fixes, still single player
* 03/16/2011 replace use of servo library with PWM servo driver due to conflict in wire library
* making servo very jittery
*
* 12/18/2020 Version 2.0 reboot
* 12/29/2020 Completed pin layout and initial unit tests of buttons, joysticks, midi, led-displays
* //2020 ...
*/
// ----[ configuration ]------------------------------------
//#define CONFIGURATION_1 true //
#define CONSOLE_BAUD 9600
#define CONSOLE_START_MESSAGE "Torn V 2.0 by Gray Mack"
// ----[ included libraries ]------------------------------------
#include <Arduino.h>
#include <Keypad.h> // 3.0 2015-09-18 http://playground.arduino.cc/Code/Keypad Authors: Mark Stanley, Alexander Brevig
#include "GridBalancer.h"
#include "LightTracer.h"
#include "ScoreKeeper.h"
#include "MidiSound.h"
// other includes used in other modules
//#include "TM1637Display_Gray.h" // Author: [email protected]. Modified for faster behavior
// potential faster tm1637 library https://github.com/sufzoli/TM16XX
// tm1637 datasheet http://j5d2v7d7.stackpathcdn.com/wp-content/uploads/2015/10/TM1637_datasheet.pdf
//#include <SPI.h>
//#include <Adafruit_GFX.h>
// vs1053 datasheet https://cdn-shop.adafruit.com/datasheets/vs1053.pdf
// fancier midi library https://github.com/razrotenberg/Midier
// see youtube Mike Rainbow DIY VS1053 MIDI sound module
// green board schematic https://www.compel.ru/infosheet/LCTECH/VS1053%20MP3%20Module%20Development%20Board%20%5BOn-board%20voice%20recording%5D
// ----[ pin definitions ]------------------------------------
#define PIN_CONSOLE_RX 0
#define PIN_CONSOLE_TX 1
#define PIN_COIN_BUTTON_LED 2
#define PIN_MUSIC_BUTTON_LED 3
#define PIN_MIDI_RESET 6
#define PIN_SEVEN_SEGMENT_CLOCK 8
#define PIN_SEVEN_SEGMENT_DATA 9
#define PIN_PIEZO_SPEAKER 12 // alternate audio
#define PIN_BUILT_IN_LED LED_BUILTIN // (D4 built in LED)
#define PIN_MIDI_OUT_TX2 16
#define PIN_TRACER_LED0 22 // -+
#define PIN_TRACER_LED1 23 // |
#define PIN_TRACER_LED2 24 // |
#define PIN_TRACER_LED3 25 // } PORTA
#define PIN_TRACER_LED4 26 // |
#define PIN_TRACER_LED5 27 // |
#define PIN_TRACER_LED6 28 // |
#define PIN_TRACER_LED7 29 // /
#define PIN_TRACER_LED15 30 // -+
#define PIN_TRACER_LED14 31 // |
#define PIN_TRACER_LED13 32 // |
#define PIN_TRACER_LED12 33 // } PORTC (reverse order)
#define PIN_TRACER_LED11 34 // |
#define PIN_TRACER_LED10 35 // |
#define PIN_TRACER_LED9 36 // |
#define PIN_TRACER_LED8 37 // /
#define PIN_TRACER_FIRST PIN_TRACER_LED0
#define PIN_TRACER_LAST PIN_TRACER_LED8
#define PIN_BUTTONS_R0 38
#define PIN_BUTTONS_R1 39
#define PIN_BUTTONS_R2 40
#define PIN_BUTTONS_R3 41
#define PIN_BUTTONS_C0 42
#define PIN_BUTTONS_C1 43
#define PIN_BUTTONS_C2 44
#define PIN_BUTTONS_C3 45
#define PIN_MAX72XX_8x8_LEFT_CS 48
#define PIN_MAX72XX_8x8_RIGHT_CS 49
#define PIN_MAX72XX_8X8_MOSI 51
#define PIN_MAX72XX_8X8_SCLK 52
#define PIN_JOYSTICK_U 62 // A8
#define PIN_JOYSTICK_D 63 // A9
#define PIN_JOYSTICK_L 64 // A10
#define PIN_JOYSTICK_R 65 // A11
#define PIN_JOYSTICK_LEFT_COM 66 // A12
#define PIN_JOYSTICK_RIGHT_COM 67 // A13
#define PIN_SPECIAL_BUTTONS 68 // A14
// ----[ constants ]------------------------------------
const uint8_t MAX_BALANCERS = 2;
const uint8_t MAX_TRACERS = 5;
const uint8_t SECONDS_TO_WAIT_FOR_PLAYERS = 5;
const uint8_t SEVEN_SEGMENT_BRIGHTNESS = 7; // 0 - 7max
const uint8_t PLAY_BUTTONS_ROWS = 4;
const uint8_t PLAY_BUTTONS_COLS = 4;
const char ButtonKeys[PLAY_BUTTONS_ROWS][PLAY_BUTTONS_COLS] = {
{0,4,8,12},
{1,5,9,13},
{2,6,10,14},
{3,7,11,15}
};
byte PlayButtonsRowPins[PLAY_BUTTONS_ROWS] = {PIN_BUTTONS_R0, PIN_BUTTONS_R1, PIN_BUTTONS_R2, PIN_BUTTONS_R3}; //connect to the row pinouts of the kpd
byte PlayButtonsColPins[PLAY_BUTTONS_COLS] = {PIN_BUTTONS_C0, PIN_BUTTONS_C1, PIN_BUTTONS_C2, PIN_BUTTONS_C3}; //connect to the column pinouts of the kpd
const uint8_t JOYSTICKS_ROWS = 3;
const uint8_t JOYSTICKS_COLS = 4;
const char JoystickKeys[JOYSTICKS_ROWS][JOYSTICKS_COLS] = {
{'U','D','L','R'},
{'u','d','l','r'},
{'1','2','x','x'},
};
byte PlayJoystickRowPins[JOYSTICKS_ROWS] = {PIN_JOYSTICK_LEFT_COM, PIN_JOYSTICK_RIGHT_COM, PIN_SPECIAL_BUTTONS}; //connect to the row pinouts of the kpd
byte PlayJoystickColPins[JOYSTICKS_COLS] = {PIN_JOYSTICK_U, PIN_JOYSTICK_D, PIN_JOYSTICK_L, PIN_JOYSTICK_R}; //connect to the column pinouts of the kpd
// how long does each level last from level 1 - 3 10, 30, 10, 30
const unsigned int CumulativeTimeInEachLevelSeconds[] = { 10, 40, 50, 80, 0 };
// ----[ types ]-------------------------------------------
enum PlayLevels : int8_t {
PL_PowerupSequence = -3,
PL_AttractMode = -2,
PL_InstrumentMode = -1,
PL_WaitingForOtherPlayerToJoin = 0,
PL_PlayBasicExtinguish = 1
};
PlayLevels operator++(PlayLevels f) { return PlayLevels(int(f)+1); }
// ----[ helper class ]------------------------------------
// ----[ global variables ]------------------------------------
bool GameOver;
bool TwoPlayer;
PlayLevels LevelOfPlay;
unsigned long StartTime;
int TotalScore;
LightTracer Tracers[MAX_TRACERS];
GridBalancer* Balancers[MAX_BALANCERS]; // = { GridBalancer(1), GridBalancer(2) };
Keypad PlayerButtons = Keypad(makeKeymap(ButtonKeys), PlayButtonsRowPins, PlayButtonsColPins, PLAY_BUTTONS_ROWS, PLAY_BUTTONS_COLS);
Keypad PlayerJoysticks = Keypad(makeKeymap(JoystickKeys), PlayJoystickRowPins, PlayJoystickColPins, JOYSTICKS_ROWS, JOYSTICKS_COLS);
ScoreKeeper Score(PIN_SEVEN_SEGMENT_CLOCK, PIN_SEVEN_SEGMENT_DATA, SEVEN_SEGMENT_BRIGHTNESS);
MidiSound Sound;
uint16_t TracerLightsPackedArray;
// ----[ prototypes ]------------------------------------
void PowerupSequence(void);
void BeginAttractMode(void);
void AttractMode(void);
void BeginSoundGizmoMode(void);
void SoundGizmoMode(void);
void BeginWaitForPlayerStart(void);
void WaitForPlayerStart(void);
void BeginGame(void);
void RunGame(void);
void HandleLevelOfPlayChange(void);
void ServiceSerialCommands(void);
void digitalWriteTracerLights(uint8_t light, uint8_t value);
void ShowTracerLights(void);
void DebugStatus(void);
void AnnounceLevelOfPlay(int newLevel);
void UnitTestButtons(void);
// ----[ code ]------------------------------------
void setup()
{
Serial.begin(CONSOLE_BAUD);
Serial.println(CONSOLE_START_MESSAGE);
pinMode(PIN_COIN_BUTTON_LED, OUTPUT);
pinMode(PIN_MUSIC_BUTTON_LED, OUTPUT);
for(int buttonLights=PIN_TRACER_FIRST; buttonLights<=PIN_TRACER_LAST; buttonLights++)
{
pinMode(buttonLights, OUTPUT);
}
pinMode(PIN_MAX72XX_8x8_LEFT_CS, OUTPUT);
digitalWrite(PIN_MAX72XX_8x8_LEFT_CS, LOW);
Balancers[0] = new GridBalancer(PIN_MAX72XX_8x8_LEFT_CS);
pinMode(PIN_MAX72XX_8x8_RIGHT_CS, OUTPUT);
digitalWrite(PIN_MAX72XX_8x8_RIGHT_CS, LOW);
Balancers[1] = new GridBalancer(PIN_MAX72XX_8x8_RIGHT_CS);
Sound.Init(PIN_MIDI_RESET, PIN_MIDI_OUT_TX2);
LevelOfPlay = PL_PowerupSequence;
PowerupSequence();
Score.FourDash();
BeginAttractMode();
UnitTestButtons();
//Balancers[0]->UnitTest1();
//Sound.UnitTest1();
}
void loop()
{
ServiceSerialCommands();
if(!GameOver)
RunGame();
else if(LevelOfPlay == PL_AttractMode)
AttractMode();
else if(LevelOfPlay == PL_InstrumentMode)
SoundGizmoMode();
else if(LevelOfPlay == PL_WaitingForOtherPlayerToJoin)
WaitForPlayerStart();
DebugStatus();
}
// ----[ function ]------------------------------------
// ----[ function ]------------------------------------
void PowerupSequence()
{
Score.PowerupSequence();
Balancers[0]->PowerupSequence();
Balancers[1]->PowerupSequence();
Serial.print(F("Button Lights PowerupSequence 1"));
for(int buttonLights=0; buttonLights<16; buttonLights++)
{
digitalWriteTracerLights(buttonLights, 1);
delay(100);
digitalWriteTracerLights(buttonLights, 0);
}
Serial.println(F(", 2"));
for(int lights=0; lights<16; lights++)
{
TracerLightsPackedArray = 0;
bitSet(TracerLightsPackedArray, lights);
ShowTracerLights();
delay(100);
}
TracerLightsPackedArray = 0;
ShowTracerLights();
Sound.PowerupSequence();
}
// ----[ function ]------------------------------------
void BeginAttractMode()
{
LevelOfPlay = PL_AttractMode;
GameOver = true;
TwoPlayer = false;
TotalScore = 0;
}
// ----[ function ]------------------------------------
void AttractMode()
{
static unsigned long LastAttractModeTimer = 0;
const unsigned long AttractModeTimeInterval = 100;
static uint8_t PulseButtonLight = 0;
static int8_t PulseDirection = 4;
if(millis() - LastAttractModeTimer > AttractModeTimeInterval)
{
LastAttractModeTimer = millis();
PulseButtonLight += PulseDirection;
if(PulseButtonLight > 250) PulseDirection = -abs(PulseDirection);
if(PulseButtonLight < 5) PulseDirection = abs(PulseDirection);
analogWrite(PIN_COIN_BUTTON_LED, PulseButtonLight);
}
// check for start button
if(PlayerJoysticks.getKeys())
{
for(int j=0; j<LIST_MAX; j++)
{
if(PlayerJoysticks.key[j].kstate == PRESSED)
{
if(PlayerJoysticks.key[j].kchar == '1')
{
Serial.println(" Start Button Pushed");
BeginWaitForPlayerStart();
}
else if(PlayerJoysticks.key[j].kchar == '1')
{
Serial.println(" Sound Button Pushed");
BeginSoundGizmoMode();
}
}
}
}
}
// ----[ function ]------------------------------------
void BeginSoundGizmoMode()
{
LevelOfPlay = PL_InstrumentMode;
}
// ----[ function ]------------------------------------
void SoundGizmoMode()
{
// C#6 D6 D#6 E6 F6 F#6 G6 G#6 A6 A#6 B6 C6
const uint8_t Notes[] { 84, 86, 87, 88, 89, 90, 91, 92, 93, 94, 96, 0, 0, 0, 0, 84 };
uint8_t chan = 0;
if(PlayerButtons.getKeys())
{
for(int b=0; b<LIST_MAX; b++) // scan the list
{
if(PlayerButtons.key[b].stateChanged) // keys that changed
{
char btn = PlayerButtons.key[b].kchar;
char note = (btn < 16) ? Notes[(int8_t)btn] : 0;
if(PlayerButtons.key[b].kstate == PRESSED)
{
Serial.print("Button "); Serial.println((int)btn);
digitalWriteTracerLights(btn, 1);
Sound.MidiNoteOn(chan, note, 100);
}
else if(PlayerButtons.key[b].kstate == RELEASED)
{
Sound.MidiNoteOff(chan, note, 100);
digitalWriteTracerLights(btn, 0);
}
}
}
}
}
// ----[ function ]------------------------------------
void BeginWaitForPlayerStart()
{
LevelOfPlay = PL_WaitingForOtherPlayerToJoin;
Score.SetCountdown(SECONDS_TO_WAIT_FOR_PLAYERS);
//StartTime = millis();
}
// ----[ function ]------------------------------------
void WaitForPlayerStart()
{
int knockSecondsFromCountdown = 0;
if(PlayerJoysticks.getKeys())
{
for(int j=0; j<LIST_MAX; j++)
{
if(PlayerJoysticks.key[j].kstate == PRESSED)
{
if(PlayerJoysticks.key[j].kchar == '1')
{
knockSecondsFromCountdown = 1;
}
}
}
}
if(Score.CountDownClock(knockSecondsFromCountdown))
{
BeginGame();
}
}
// ----[ function ]------------------------------------
void BeginGame()
{
GameOver = false;
LevelOfPlay = PL_PlayBasicExtinguish;
StartTime = millis();
TotalScore = 0;
for(int t = 0; t < MAX_TRACERS; t++)
{
Tracers[t].KillTracer();
}
}
// ----[ function ]------------------------------------
void RunGame()
{
/*
MoveTracers();
TracerSounds();
HandleButtons();
ReadSticks();
HandleBalancers();
RefreshScore();
*/
HandleLevelOfPlayChange();
ServiceSerialCommands();
}
// ----[ function ]------------------------------------
void HandleLevelOfPlayChange()
{
if(GameOver || LevelOfPlay < 0) return;
unsigned long LevelDoneTimeSeconds = (unsigned long)(CumulativeTimeInEachLevelSeconds[LevelOfPlay]) * 1000UL;
if((millis() - StartTime) < LevelDoneTimeSeconds) return;
AnnounceLevelOfPlay(++LevelOfPlay);
}
// ----[ function ]------------------------------------
void ServiceSerialCommands()
{
// listen for a command and execute the action. TX1,RX1
// perhaps a jumper will set the master.
// Need to be able to announce AboutToStartGame/WaitingForPlayerToJoin->, <-AcceptPlayer2, StartGame->
}
// ----[ function ]------------------------------------
void digitalWriteTracerLights(uint8_t light, uint8_t value)
{
if(light < 8)
{
digitalWrite(PIN_TRACER_LED0+light, value);
}
else if(light < 16)
{
digitalWrite(PIN_TRACER_LED8-(light-8), value);
}
else
{
Serial.println(F("digitalWriteTracerLights: Unexpected light"));
}
}
// ----[ function ]------------------------------------
void ShowTracerLights()
{
PORTA = TracerLightsPackedArray & 0xFF;
PORTC = (TracerLightsPackedArray >> 8) & 0xFF;
}
// ----[ function ]------------------------------------
void DebugStatus()
{
const int DEBUG_STATUS_EVERY_MS = 1000;
static unsigned long StatusMillis = 0;
if(millis() - StatusMillis < DEBUG_STATUS_EVERY_MS) return;
StatusMillis = millis();
Serial.print(" Level: ");
switch(LevelOfPlay)
{
case PL_PowerupSequence:
Serial.print("Powerup ");
break;
case PL_AttractMode:
Serial.print("Attract ");
break;
case PL_InstrumentMode:
Serial.print("SoundGiz");
break;
case PL_WaitingForOtherPlayerToJoin:
Serial.print("WaitPlyr");
break;
default:
Serial.print(" "); Serial.print(LevelOfPlay);
break;
}
Serial.print(" Time: "); Serial.print((millis() - StartTime)/1000);
Serial.print(" Score: "); Serial.println(TotalScore);
}
// ----[ function ]------------------------------------
void AnnounceLevelOfPlay(int newLevel)
{
Serial.print("------------[ Level "); Serial.print(newLevel); Serial.println(" ]------------");
// mp3 player announce or midi jingle?
}
// ----[ test function ]------------------------------------
void UnitTestButtons()
{
Serial.println("UnitTestButtons");
char currentLeftStick = ' ';
char currentRightStick = ' ';
while(1)
{
if(PlayerButtons.getKeys())
{
for(int b=0; b<LIST_MAX; b++) // scan the list
{
if(PlayerButtons.key[b].stateChanged) // keys that changed
{
if(PlayerButtons.key[b].kstate == PRESSED)
{
int btn = PlayerButtons.key[b].kchar;
Serial.print("Button "); Serial.print(btn); Serial.print(" pushed "); Serial.println((millis()/10)%10000);
//digitalWriteTracerLights(btn, HIGH);
TracerLightsPackedArray = 0;
bitSet(TracerLightsPackedArray, btn);
ShowTracerLights();
delay(50);
//digitalWriteTracerLights(btn, LOW);
TracerLightsPackedArray = 0;
ShowTracerLights();
}
}
}
}
if(PlayerJoysticks.getKeys())
{
for(int j=0; j<LIST_MAX; j++)
{
KeyState ks = PlayerJoysticks.key[j].kstate;
char event = PlayerJoysticks.key[j].kchar;
if(ks == PRESSED)
{
Serial.print(" Joy ");
switch(event)
{
case 'U': case 'D': case 'L': case 'R':
Serial.print(" L-");
currentLeftStick = event;
break;
case 'u': case 'd': case 'l': case 'r':
Serial.print(" R-");
currentRightStick = event;
break;
default:
Serial.print(" P-");
break;
}
Serial.println(event);
}
else if(ks == RELEASED)
{
switch(event)
{
case 'U': case 'D': case 'L': case 'R':
currentLeftStick = ' ';
break;
case 'u': case 'd': case 'l': case 'r':
currentRightStick = ' ';
break;
}
}
}
}
const unsigned long STICK_TIME = 250;
static unsigned long StickLogMillis;
if(millis() - StickLogMillis > STICK_TIME)
{
StickLogMillis = millis();
if(currentLeftStick != ' ') { Serial.print(" L-"); Serial.println(currentLeftStick); }
if(currentRightStick != ' ') { Serial.print(" R-"); Serial.println(currentRightStick); }
}
}
}