Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
John Bradnam
Published © GPL3+

Skeet Shooting Game

An electronic skeet shooting game with 9 levels of play. It includes features such as score and shot count.

IntermediateFull instructions provided8 hours926
Skeet Shooting Game

Things used in this project

Hardware components

ATtiny85
Microchip ATtiny85
×1
MAX7219 IC
×1
Linear Regulator (7805)
Linear Regulator (7805)
78M05 TO-252 SMD variant
×1
Capacitor 100 nF
Capacitor 100 nF
1206 SMD
×3
Capacitor 10 µF
Capacitor 10 µF
1206 Ceramic SMD
×1
Capacitor 220 µF
Capacitor 220 µF
10V 7343 SMD
×1
TECHNIK SMD power socket
×1
10 LEDs Bar Array, Green
10 LEDs Bar Array, Green
Blue (https://www.ebay.com.au/itm/381523946953)
×2
10 LEDs Bar Array, Green
10 LEDs Bar Array, Green
(Red, Yellow, Green and Blue (https://www.ebay.com.au/itm/382542287112)
×2
TSS-307EWA 9mm 7 Segment Red Common Cathode Display
×1
Resistor 1206 SMD
1 x 22K, 1 x 4K7, 2 x 1K
×4
12mm x 12mm Tactile switch
×1
Passive Buzzer 47
×1
IC & Component Socket, 28 Contacts
IC & Component Socket, 28 Contacts
×1
IC & Component Socket, 8 Contacts
IC & Component Socket, 8 Contacts
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)
AVR ISP programmer

Story

Read more

Custom parts and enclosures

Case

0.2mm layer height, 20% infill, no supports

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

ShootV1.ino

C/C++
/*
Shoot Game V1
Schematic, Board design, Code: John Bradnam (jbrad2089@gmail.com)

DIG_0 - LED 1 .. 8
DIG_1 - LED 9 .. 16
DIG_2 - LED 17 .. 34
DIG_3 - LED 25 .. 32
DIG_4 - LED 33 .. 40
DIG_5 - Level 7 Segment
DIG_6 - Score Units 7 Segment
DIG_7 - Score Tens 7 Segment

Segment order for each row is SEG_DP, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G

PB0 - LOAD
PB1 - DATA
PB2 - CLOCK
PB3 - SPEAKER
PB4 - SWITCH (Analog)
*/

#include "display.h"
#include "buttons.h"
#include "music.h"

#define LOAD PB0
#define DATA PB1
#define CLOCK PB2
#define SPEAKER PB3
#define SWITCHES PB4

//Animation speed
#define DELAY_TIME 50

enum ModeEnum { SELECT_LEVEL, PLAY, SCORE };
ModeEnum currentMode = SELECT_LEVEL;
int level = 1;
int score = 0;
int bullets = 0;

//-------------------------------------------------------------------------------------

// Initialise hardware
void setup() 
{
  
  randomSeed(analogRead(millis()));
  
  //Setup display
  initDisplay(DATA, CLOCK, LOAD);
  //Setup switches
  initButtons(SWITCHES);
  //Setup buzzer
  initMusic(SPEAKER);

  //Animation startup sequence
  showSplashScreen();

  //Show the start
  currentMode = SELECT_LEVEL;
  level = 1;
  showLevel(level, true);
}

//--------------------------------------------------------------------------------------

//Main Loop
void loop() 
{
 
  switch (currentMode)
  {
    case SELECT_LEVEL:
      //Select new level and press SHOOT when ready
      switch (ButtonPressed())
      {
        case LEVEL:
          //Increase level
          level++;
          if (level > 9)
          {
            level = 1; 
          }
          showLevel(level, true);
          break;

        case SHOOT:
          currentMode = PLAY;
          showCountDown(50, 1000);
          break;
      }
      break;

    case PLAY:
      //Start a game
      bullets = 24;
      score = 0;
      while (bullets > 0)
      {
        switch (level)
        {
          case 1: sequentialMovement(4, 50, 51, true); break;
          case 2: sequentialMovement(3, 40, 41, true); break;
          case 3: sequentialMovement(2, 30, 31, true); break;
          case 4: sequentialMovement(1, 30, 31, true); break;
          case 5: sequentialMovement(3, 20, 100, true); break;
          case 6: sequentialMovement(2, 20, 100, true); break;
          case 7: sequentialMovement(1, 20, 100, true); break;
          case 8: randomMovement(2, 600, 601); break;
          case 9: randomMovement(2, 100, 600); break;
        }
        delay(10);
      }

      //Out of bullets      
      currentMode = SCORE;
      clearAllTargetLeds();
      flashScore(0, 10, 200);
      playSound(110,500); 
      if (score > 9)
      {
        //playWinMusic();
      }
      else
      {
        //playLoseMusic();
      }
      showScore(score, true);
      showLevel(level, false); 
      break;

    case SCORE:
      //Game over, wait for SHOOT button
      if (ButtonPressed() == SHOOT)
      {
        currentMode = SELECT_LEVEL;
        showScore(0, false);
        showLevel(level, true); 
      }
      break;

  }
  
  delay(100);
}

//--------------------------------------------------------------------------------------

//Show a sequential movement of lights.
// numOfLeds - The number of LEDs on at any one time (1 to 4)
// deltaMin - minumium time between frames
// deltaMax - maximum time between frames
// alternate - whether sequence alternates
void sequentialMovement(int numOfLeds, int deltaMin, int deltaMax, bool alternate)
{
  int len = max(min(numOfLeds, 4), 1);
  int delta = 0;
  
  int head = -1;
  int tail = (head - len);
  while (tail < 40 && bullets > 0)
  {
    delta = random(deltaMin, (tail > 14 && tail <= 22) ? deltaMin : deltaMax);
    showTargetLed(delta, tail, tail + len);
    tail++;
    head++;
  }
  if (alternate)
  {
    head = 40;
    tail = head + len;
    while (tail >=0 && bullets > 0)
    {
      delta = random(deltaMin, (head >= 18 && head < 26) ? deltaMin : deltaMax);
      showTargetLed(delta, head + len, head);
      tail--;
      head--;
    }
  }
}

//--------------------------------------------------------------------------------------

//Show a random movement of lights.
// numOfLeds - The number of LEDs on at any one time (1 to 4)
// deltaMin - minumium time between frames
// deltaMax - maximum time between frames
void randomMovement(int numOfLeds, int deltaMin, int deltaMax)
{
  int len = max(min(numOfLeds, 4), 1);
  int delta = 0;
  for(int i = 0; i < 40; i++)
  {
    int tail = random(0, 40 + len) - len;
    delta = random(deltaMin, deltaMax);
    showTargetLed(delta, tail, tail + len);
    if (bullets == 0)
    {
      break;
    }
  }
}

//--------------------------------------------------------------------------------------

//Show a series of animations
void showSplashScreen()
{
  for (int i = 5; i > 0; i--)
  {
    if (i & 1)
    {
      animateInsideOut(i * 10);
    }
    else
    {
      animateOutsideIn(i * 10);
    }
  }
}

//--------------------------------------------------------------------------------------

//Show Count down to start
// delta - time in mS between counts
void showCountDown(int animDelay, int countDelay)
{
  //Animate the LEDs
  animateInsideOut(animDelay);

  //Count down from 3
  for (int i = 3; i > 0; i--)
  {
    showLevel(i, true);
    delay(countDelay);
  }
  showLevel(0, false);
}

//--------------------------------------------------------------------------------------

//Display LEDs and check for shoot button
// delta - time in mS to show LEDs
// tail - first LED to light up
// head - last LED to light up
void showTargetLed(int delta, int tail, int head)
{
  //Ensure head is always greater than tail
  if (tail > head)
  {
    int t = tail;
    tail = head;
    head = t;
  }

  
  int led = tail;
  while (led < head)
  {
    showTargetLed(led, true);
    led++;
  }

  //Keep testing while LED is on
  unsigned long timeout = millis() + delta;
  while (millis() < timeout)
  {
    _delay_ms(1);
    int hits = -1;
    //Test for hit on any RED led
    if (readButtonState() == SHOOT)
    {
      //debounce
      _delay_ms(10);
      if (readButtonState() == SHOOT)
      {
        hits = 0;
        led = tail;
        while (led < head)
        {
          //Test if any red LEDs are hit
          if (led >= 18 && led <= 21)
          {
            hits++;
          }
          led++;
        }

        bullets = bullets - 1;
        showScore(bullets, true);
      }
    }

    //Manage any hits
    if (hits > 0)
    {
      showLevel(hits, true);
      playHitTone();
      score = score + hits;
    }
    else if (hits == 0)
    {
      showLevel(hits, false);
      playMissTone();
    }

    //Wait until button is released
    while (hits >= 0 && readButtonState() == SHOOT)
    {
    }

  } //while

  //Turn off LEDs
  led = tail;
  while (led < head)
  {
    showTargetLed(led, false);
    led++;
  }

}

Buttons.h

C/C++
/*
Shoot - Button support
Schematic, Board design, Code: John Bradnam (jbrad2089@gmail.com)
*/

#pragma once

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>

/*
Pin Definitions
---------------
PB0 - LOAD
PB1 - DATA
PB2 - CLOCK
PB3 - SPEAKER
PB4 - SWITCHES (ADC2) => SHOOT - 0V (0), LEVEL - 2.5V (127), NO_BUTTON - 5V (255)
*/

enum ButtonEnum { NO_BUTTON, LEVEL, SHOOT };
#define EPSILON 50
#define ADC_NO_BUTTON 255
#define ADC_LEVEL 127
#define ADC_SHOOT 0

// Initialise Buttons
void initButtons(uint8_t pin);

// Tests if any of the buttons have been pressed and released
//  returns the button that was pressed
ButtonEnum ButtonPressed();

// Read the button pin and return current state
//  returns either NO_BUTTON, LEVEL or SHOOT
ButtonEnum readButtonState();

// Read analog pin
//  returns a number between 0 and 255
int avrAnalogRead();

//------------------------------------------------------------------------------------------------------------------

// Initialise Buttons (PB4 - ADC2 pin)
//   pin - not used, hard coded to use ADC2 (PB4)
void initButtons(uint8_t pin)
{
  //Setup analog pin for switches
  ADMUX = 0b00100010;   //sets VCC as VREF, sets ADC2 (PB4) as input channel, and left adjusts
  ADCSRA = 0b10000011;  //turn on ADC, keep ADC single conversion mode,
                        //and set division factor-8 for 125kHz ADC clock
}

//--------------------------------------------------------

// Tests if any of the buttons have been pressed and released
//  returns the button that was pressed
ButtonEnum ButtonPressed()
{
	ButtonEnum button = NO_BUTTON;
	int btn = readButtonState();
  if (btn != ADC_NO_BUTTON)
	{
		_delay_ms(10);
	  if (readButtonState() == btn)
		{
			while (readButtonState() == btn)
      {
      }
			button = btn;
		}
	}
	return button;
}

//--------------------------------------------------------

// Read the button pin and return current state
//  returns either NO_BUTTON, LEVEL or SHOOT
ButtonEnum readButtonState()
{
  int state = avrAnalogRead();
  if (abs(state - ADC_SHOOT) <= EPSILON)
  {
    return SHOOT;
  }
  else if (abs(state - ADC_LEVEL) <= EPSILON)
  {
    return LEVEL;
  }
  else
  {
    return NO_BUTTON;
  }
}

//--------------------------------------------------------

// Read analog pin
//  returns a number between 0 and 255
int avrAnalogRead()
{
  ADCSRA |= (1 << ADSC);         //start conversion
  while (ADCSRA & (1 << ADSC))
  {
    _delay_ms(1);
  }
  return ADCH;              //store data in analogData variable
}

Display.h

C/C++
/*
Shoot - Display support
Schematic, Board design, Code: John Bradnam (jbrad2089@gmail.com)
*/

#pragma once

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <LedControl.h>

/*
Pin Definitions
---------------
PB0 - LOAD
PB1 - DATA
PB2 - CLOCK
PB3 - SPEAKER
PB4 - SWITCHES (ADC2) - 0V (0), LEVEL - 2.5V (127), NO_BUTTON - 5V (255)

DIG_0 - LED 1 .. 8
DIG_1 - LED 9 .. 16
DIG_2 - LED 17 .. 34
DIG_3 - LED 25 .. 32
DIG_4 - LED 33 .. 40
DIG_5 - Level 7 Segment
DIG_6 - Score Units 7 Segment
DIG_7 - Score Tens 7 Segment

Segment order for each row is SEG_DP, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G
*/

//Because there is no default constructor for he LedControl, we can't create an instance of it
//without passing some parameters. This instance will be replaced in initDisplay and the garbage
//collector can clean up this instance.
LedControl _lc = LedControl(11, 13, 12);

//Initilaise MAX7219. This drives all LEDs via a MAX7219.
void initDisplay(uint8_t dataPin, uint8_t clockPin, uint8_t loadPin);

//Clear all target LEDs
void clearAllTargetLeds();

// Turn on or off a target LED
// pos - 0..39
// on - true is on
void showTargetLed(int pos, bool on);

// Show level value
// value - 0..9
// on - true is on
void showLevel(int value, bool on);

// Show Score value
// value - 0..99
// on - true is on
void showScore(int value, bool on);

//Flashes a score on the display
// value - number to flash
// repeat - number of times to flash
// delta - time in mS between each state
void flashScore(int value, int repeat, int delta);

// Show a number between 0 and 999 across all displays
// pos - 0..39
// on - true is on
void showValue(int value, bool on);

//Display a animation from inside to outside
// delta - time in mS between frames
void animateInsideOut(int delta);

//Display a animation from outside to inside
// delta - time in mS between frames
void animateOutsideIn(int delta);

//------------------------------------------------------------------------------------------------------------------

//Initilaise MAX7219. This drives all LEDs via a MAX7219.
void initDisplay(uint8_t dataPin, uint8_t clockPin, uint8_t loadPin)
{
  _lc = LedControl(dataPin,clockPin,loadPin,1);
  
  //Setup LEDs
  _lc.shutdown(0,false);     //Wakeup call
  _lc.setScanLimit(0, 7);    //Number of digits
  _lc.setIntensity(0, 15);    //Brightness
  _lc.clearDisplay(0);

}

//-------------------------------------------------------

//Clear all target LEDs
void clearAllTargetLeds()
{
  for (int i = 0; i < 40; i++)
  {
    showTargetLed(i, false);
  }
}

//-------------------------------------------------------

// Turn on or off a target LED
// pos - 0..39
// on - true is on
void showTargetLed(int pos, bool on)
{
	if (pos >= 0 && pos < 40)
	{
		int seg = pos & 0x07;
		int dig = pos >> 3;
		_lc.setLed(0, dig, seg, on);
	}
}

//-------------------------------------------------------

// Show level value
// value - 0..9
// on - true is on
void showLevel(int value, bool on)
{
  value = min(value, 9);
  if (on)
  {
    _lc.setDigit(0,5,value,false);
  }
  else 
  {
    _lc.setChar(0,5,' ', false);
  }
}

//-------------------------------------------------------

// Show Score value
// value - 0..99
// on - true is on
void showScore(int value, bool on)
{
  value = min(value, 99);
  int tens = value / 10;
  int units = value % 10;
  if (on)
  {
    _lc.setDigit(0,6,units,false);
  }
  else 
  {
    _lc.setChar(0,6,' ', false);
  }
  if (on && tens != 0)
  {
    _lc.setDigit(0,7,tens,false);
  }
  else
  {
    _lc.setChar(0,7,' ', false);
  }
}

//-------------------------------------------------------

// Show a number between 0 and 999 across all displays
// pos - 0..39
// on - true is on
void showValue(int value, bool on)
{
  value = min(value, 999);
  int hundreds = value / 100;
  int tens = (value % 100) / 10;
  int units = value % 10;
  if (on)
  {
    _lc.setDigit(0,6,units,false);
  }
  else 
  {
    _lc.setChar(0,6,' ', false);
  }
  if (on && (tens != 0 || hundreds != 0))
  {
    _lc.setDigit(0,7,tens,false);
  }
  else
  {
    _lc.setChar(0,7,' ', false);
  }
  if (on && hundreds != 0)
  {
    _lc.setDigit(0,5,hundreds,false);
  }
  else
  {
    _lc.setChar(0,5,' ', false);
  }
}

//--------------------------------------------------------------------------------------

//Flashes a score on the display
// value - number to flash
// repeat - number of times to flash
// delta - time in mS between each state
void flashScore(int value, int repeat, int delta)
{
  for (int x=0; x < repeat;x++)
  {
    showScore(value, (x & 1) != 0);
    delay(delta);
  }
  showScore(value, true);
}


//--------------------------------------------------------------------------------------

//Display a animation from inside to outside
// delta - time in mS between frames
void animateInsideOut(int delta)
{
  for (int i = 0; i < 40; i++)
  {
    if (i < 20)
    {
      showTargetLed(i + 20, true);
      showTargetLed(19 - i, true);
    }
    else
    {
      showTargetLed((i - 20) + 20, false);
      showTargetLed(19 - (i - 20), false);
    }
    delay(delta);
  }
}

//--------------------------------------------------------------------------------------

//Display a animation from outside to inside
// delta - time in mS between frames
void animateOutsideIn(int delta)
{
  for (int i = 0; i < 40; i++)
  {
    showTargetLed(i, (i < 20));
    showTargetLed(39-i, (i < 20));
    delay(delta);
  }
}

Music.h

C/C++
#pragma once

uint8_t _speakerPin;

// Initialise Music player
void initMusic(uint8_t speakerPin);

//Play a sound of a frequency in Hz for a duration in mS
void playSound(double freqHz, int durationMs);

//Play turn tone
void playHitTone();

//Play turn tone
void playMissTone();

//Play wah wah wah wahwahwahwahwahwah
void playLoseMusic();

//Play winning music
void playWinMusic();

//------------------------------------------------------------------------------------------------------------------

// Initialise Music player
void initMusic(uint8_t speakerPin)
{
  _speakerPin = speakerPin;
  pinMode(_speakerPin, OUTPUT);
}

//------------------------------------------------------------------------------------------------------------------

//Play a sound of a frequency in Hz for a duration in mS
void playSound(double freqHz, int durationMs)
{
  //Calculate the period in microseconds
  int periodMicro = int((1/freqHz)*1000000);
  int halfPeriod = periodMicro/2;
   
  //store start time
  long startTime = millis();
   
  //(millis() - startTime) is elapsed play time
  while((millis() - startTime) < durationMs)
  {
    digitalWrite(_speakerPin, HIGH);
    delayMicroseconds(halfPeriod);
    digitalWrite(_speakerPin, LOW);
    delayMicroseconds(halfPeriod);
  }
}

//------------------------------------------------------------------------------------------------------------------

//Play turn tone
void playHitTone()
{
  playSound(523,50); 
}

//------------------------------------------------------------------------------------------------------------------

//Play turn tone
void playMissTone()
{
  playSound(110,50); 
}

//------------------------------------------------------------------------------------------------------------------

//Play wah wah wah wahwahwahwahwahwah
void playLoseMusic()
{
  delay(400);
  //wah wah wah wahwahwahwahwahwah
  for(double wah=0; wah<4; wah+=6.541)
  {
    playSound(440+wah, 50);
  }
  playSound(466.164, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.939)
  {
    playSound(415.305+wah, 50);
  }
  playSound(440.000, 100);
  delay(80);
  for(double wah=0; wah<5; wah+=4.662)
  {
    playSound(391.995+wah, 50);
  }
  playSound(415.305, 100);
  delay(80);
  for(int j=0; j<7; j++)
  {
    playSound(391.995, 70);
    playSound(415.305, 70);
  }
  delay(400);
}

//------------------------------------------------------------------------------------------------------------------

//Play winning music
void playWinMusic()
{
  playSound(880,100); //A5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(659,100); //E5
  playSound(880,100); //A5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(988,100); //B5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(523,100); //C5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(587,100); //D5
  playSound(659,100); //E5
  playSound(659,100); //E5
  delay(250);
}

Credits

John Bradnam

John Bradnam

147 projects • 181 followers
Thanks to masonhorder.

Comments