/* Silver Dollar Game - see http://www.technoblogy.com/show?4KA5 David Johnson-Davies - www.technoblogy.com - 8th December 2023 ATtiny84 @ 8 MHz (internal oscillator; BOD disabled) CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #include #include // Constants // Port bit positions const int S1 = 0; // PB0 const int S2 = 1; // PB1 const int S3 = 2; // PB2 const int S4 = 4; // PA4 const int S5 = 7; // PA7 const int Speaker = 5; // PA5 const int Go = 6; // PA6 const int Dollars = 5; // Number of dollars const int SleepTimeout = 30 * 1000; // In milliseconds ie 30 seconds // Global variables volatile int Timer; // Delay timer, in ms volatile int Timeout; // Sleep timer, in ms uint8_t LED[4][4] = {{ 0, 10, 11, 12 }, { 9, 0, 7, 8 }, { 5, 6, 0, 4 }, { 2, 1, 3, 0 }}; uint8_t Dollar[Dollars+1]; // Positions of the dollars on the track int LEDs = 0b101010101010; // Each bit corresponds to one LED uint8_t Flash = 0; // Flash LED at this position // Buttons ********************************************** void SetupButtons () { // Set pullups on inputs PORTA = 1<>Go & 1) == 1) return 0; while ((PINA>>Go & 1) == 0); Delay(100); return 1; } // Returns button number 1 to 5, or 0 if no button. int CheckButton () { uint8_t bits = (PINA & 0x80)>>3 | (PINA & 0x10)>>1 | (PINB & 0x07); if (bits == 0x1F) return 0; // No button pressed int i; for (i=1; i<=5; i++) { if ((bits & 1) == 0) break; bits = bits >> 1; } while (((PINA & 0x80)>>3 | (PINA & 0x10)>>1 | (PINB & 0x07)) != 0x1F); Delay(100); return i; } // Display multiplexer ********************************************** // Set up Timer/Counter0 to multiplex the display void SetupDisplay () { TCCR0A = 2< 1000Hz } void DisplayOn () { TIMSK0 = 1<>(led-1) & 1; bits = bits | bit< 0); } // Sound effects ********************************************** // C C# D D# E F F# G G# A A# B uint8_t Scale[] = {239,226,213,201,190,179,169,160,151,142,134,127}; void SetupSpeaker () { DDRA = 1<> 1; if (temp == 1) RandomSeed = RandomSeed ^ 0xD34; return RandomSeed; } int PopCount(uint16_t x) { x = (x & 0x5555) + ((x>>1) & 0x5555); x = (x & 0x3333) + ((x>>2) & 0x3333); x = (x & 0x0f0f) + ((x>>4) & 0x0f0f); return (x & 0x00ff) + (x>>8); } void UpdatePosition () { LEDs = 0; for (int i=0; i> 10 == 0 || PopCount(pos) != Dollars); // Convert to dollar positions int dol = 0; for (int i=12; i>0; i--) { if (pos>>(i-1) & 1 == 1) Dollar[dol++] = i; } } void RandomPossiblePosition () { do { RandomPosition(); } while (NimSum() == 0); UpdatePosition(); } bool CanMove (int i) { return (Dollar[i-1] - Dollar[i] - 1) > 0; } void MoveButton (int button) { Flash = 0; Dollar[button-1]--; UpdatePosition(); Click(); Flash = Dollar[button-1]; } int FindBestMove () { int allmoves = 0, wins = 0, windollar = 0, winmove = 0, anydollar = 0, anymove = 0; for (int i=1; i<=Dollars; i++) { // For each dollar int moves = Dollar[i-1] - Dollar[i] - 1; int start = Dollar[i-1]; for (int m=1; m<=moves; m++) { Dollar[i-1] = start - m; allmoves++; if ((PseudoRandom() % allmoves) == 0) { anydollar = i; anymove = m; } if (NimSum() == 0) { wins++; if ((PseudoRandom() % wins) == 0) { windollar = i; winmove = m; } } } Dollar[i-1] = start; } if (wins > 0) return windollar<<8 | winmove; else return anydollar<<8 | anymove; } void ComputerMove () { // Count all zero-sum moves int found= FindBestMove(); int dollar = found >> 8; int moves = found & 0xFF; // Wait Delay(1000); // Flash dollar Flash = Dollar[dollar-1]; for (int i=0; i>Go & 1) == 0); // Wait for Go to be released Delay(100); } // Pin Change Interrupt Request 0 - for waking from sleep ISR(PCINT0_vect) { } void ResetTimeout () { cli(); Timeout = SleepTimeout; sei(); } void MaybeSleep () { cli(); int now = Timeout; sei(); if (now > 0) return; Sleep(); // Come here when wake up ResetTimeout(); } void Cylon () { int i = 0; ResetTimeout(); do { // Cylon effect and set random start LEDs = 1<<(11-i) | 1<