Token Ring

Lite om projektet

Hej där! Du har kommit till grupp 9:s hemsida. Vi är fyra studenter som har utvecklat ett spel. Spelet heter Token Ring och är ett arkadspel som utmanar reaktions- och precisionsförmågan. Spelet byggdes från början där en del olika komponenter inkluderades, bland andra en ring med 16 små lampor, två knappar och en display. I ringen färdas token (markör), med knapparna stannar man token eller återställer spelet och displayn visar poängställning samt highest score. Det programmerades i språket C.


/*
                         * FourInARow.c
                         *
                         * Created: 2019-04-09 12:44:38
                         * Author : fe4242bo-s
                         */ 
                        
                        #define F_CPU 8000000UL
                        
                        #include <avr/io.h>
                        #include <util/delay.h>
                        #include <avr/interrupt.h>
                        #include "Components/Display/Display.h"
                        #include "Components/NeoPixels/NeoPixel.h"
                        #include "GameManager.h"
                        //#include "Constants/GameStrings.h"
                        
                        /*
                        NOTES TO PROGRAMMER!
                        
                         - Hardcoded literals can be found in Constants/...
                         - When passing a char array into Display with SendString(...) you can add Q{command} for special operations.
                         - Some of these special operations are: Qn (jump to new line on LCD), Qt (display countdown timer on LCD), #CONSTANT padding CONSTANT amount in text.
                         - Maximum #n command is ONCE (obviously)
                         - Maximum #t command is ONCE Do not support more than 1 timer at the moment.
                         - Game buttons connected to --> 14,15,16,17
                        */
                        
                        extern void Reset();
                        extern void Send_One();
                        extern void Send_Zero();
                        
                        
                        int pressed = 0;
                        int currentLight = 0;
                        ISR(PCINT0_vect) {
                        
                            clearPixels();
                            setPixel(currentLight,10,10,10);
                        
                        
                            if (PINA & (1 << PINA0)) {
                                 if (pressed) {
                                     pressed = 0;
                                 } 
                                 else {
                                     pressed = 1;
                                     if(currentLight == 14) {
                                        UpdatePoints();
                                     } else {
                                        EndGame();
                                     }
                                     
                                 }
                            }
                            PCIFR |= PCIF0; // Do NOT remove! Will make sure we trigger button interrupt only once!
                        }
                        
                        
                        int main(void)
                        {
                            cli();
                            LCD_init();
                            InitializeGame();
                            
                            PCICR |= 1 << PCIE0;
                            sei();
                            PCMSK0 |= 1 << PCINT0;
                            
                            // NEOPIXELS AFTER THIS
                        
                            DDRD = 1 << PORTD7;
                            PORTD &= ~(1 << PORTD7);
                                while (1) 
                                {
                                    
                                    for(int i = currentLight; i<16 ;i++){
                                        if (pressed) break;
                                        currentLight = i;
                                        setPixel(i, diffRValue(),diffGValue(),0);
                                        difficultyDelay();
                                        setPixel(i,0,0,0);
                                        if (pressed) setPixel(i,10,10,10); // Fixes the case where the pixel is set to zero if a button press is faster than the diffDelay.
                                    }
                                    if (!pressed) currentLight = 0;
                                }
                            
                        }
                        
/*
 * GameManager.h
 *
 * Created: 2019-05-08 13:27:11
 *  Author: fe4242bo-s
 */ 


#ifndef GAMEMANAGER_H_
#define GAMEMANAGER_H_

 extern uint8_t difficulty;

 void InitializeGame();
 void ResetGame();
 void UpdatePoints();
 void EndGame();
 void difficultyDelay();
 void updatePixel();
 uint8_t diffRValue();
 uint8_t diffGValue();
#endif /* GAMEMANAGER_H_ */
/*
 * GameManager.c
 *
 * Created: 2019-05-08 13:26:03
 *  Author: fe4242bo-s
 */ 

#define F_CPU 8000000UL

#include <avr/io.h>
#include <stdint-gcc.h>
#include <util/delay.h>
#include "Components/Display/Display.h"

uint32_t points = 0;
uint32_t highscore = 0;
		 difficulty = 1;

/** Initializes game and expects that display is already initialized. Make sure to initialize display before game initialization. */
 void InitializeGame() {
	points = 0;
	highscore = ReadFromEEPROM(0xFF);
	updateText();
 }
 
 /** Write data to permanent memory. */
 void WriteToEEPROM(unsigned int uiAddress, unsigned char ucData) {
	 while(EECR & (1<<EEPE));
	 
	 EEAR = uiAddress;
	 EEDR = ucData;
	 
	 EECR |= (1<<EEMPE);
	 EECR |= (1<<EEPE);
	 
 }
 
  /** Read data from permanent memory. */
 void ReadFromEEPROM(unsigned int uiAddress) {
	  while(EECR & (1<<EEPE));

	   EEAR = uiAddress;
	   EECR |= (1<<EERE);
	   return EEDR;
 }
 
 /** Will reset the game back to it's initial state. */
 void ResetGame() {
	points = 0;
	difficulty = 1;
	updateText();
 }
 
 /** Updates the points on the LCD display. Will increment points. */
 void UpdatePoints() {
	 
	points = (points + 1);
	if(points>highscore){ 
		highscore = points;
		WriteToEEPROM(0xFF,highscore);	
	}
	if(points%5==0 && difficulty != 7){difficulty++;}
	updateText();
 }
 
 /** Internal function that will do the updating of the text. */
 void updateText(){
	ClearDisplay();
	
	char txt09[1+9]="";
	char txt10[2+9]="";
	
	if (points<10){
		sprintf(txt09,"Score: %dQn",points);
		SendString(txt09,1+9);	
	}
	else{
		sprintf(txt10,"Score: %dQn",points);
		SendString(txt10,2+9);
	}
	
	txt09[1+11]="";
	txt10[2+11]="";
	
	if (highscore<10){
		sprintf(txt09,"Hi-Score: %d",highscore);
		SendString(txt09,1+10);
	}
	else{
		sprintf(txt10,"Hi-Score: %d",highscore);
		SendString(txt10,2+10);
	}
	
 }
 
 /** Delay that is based on difficulty. Meaning that the use of this during a neopixel animation will make the neopixels spin faster the harder it gets! */
 void difficultyDelay(){
	 for (int i = 8;i>difficulty;i--)
	 {
		 _delay_ms(40);
	 }
 }
 
 /** R portion of the RGB value based on difficulty. */
 uint8_t diffRValue(){
	 uint8_t r = 0;
	 for (int i = 0; i < difficulty; i++)
	 {
		 if ((r == 100)) return r;
		 r += 10;
	 }
	 return r;
 }
 
 /** G portion of the RGB value based on difficulty. */
 uint8_t diffGValue(){
	 uint8_t g = 80;
	 
	 for (int i = 0; i < difficulty; i++)
	 {
			 if ((g == 0)) return g;
		 	 g -= 10;
	 }
	 return g;
 }
 
 /** Ends the game. Updates High score and resets the game. */
 void EndGame() {
	 if(points>highscore){
		  highscore = points;
		  }
	 ResetGame();
 }
/*
                         * Display.h
                         *
                         * Created: 2019-04-09 12:46:33
                         *  Author: fe4242bo-s
                         */ 
                        
                        
                        #ifndef DISPLAY_H_
                        #define DISPLAY_H_
                        
                        // Comments for the subroutines can be found in their .c file.
                        
                        extern void SendCommand(unsigned char cmd);
                        extern void SendChar(unsigned char data);
                        extern void SendString(unsigned char data[], int size);
                        extern void ClearDisplay();
                               void SkipToNextRow(int size, int atIndex);
                               void SendError();
                               void DisplayRoundTimer(char data[], int atIndex , int size); // Don't know the size of array, so we pass it around instead.
                               uint8_t ExtractEscapeCommand(char data[], int atIndex, int size);
                        extern void init_LED();
                               void EnableTimerInterrupt();
                        extern void EditLCDText(uint8_t index, char newData[], int size);
                        
                        
                        
                        #endif /* DISPLAY_H_ */
                        
/*
                         * Display.c
                         *
                         * Created: 2019-04-09 12:47:28
                         *  Author: fe4242bo-s
                         */ 
                        
                        #define F_CPU 16000000
                        
                        #define REGISTER_SELECT 7
                        #define READ_WRITE_SELECT 6
                        #define ENABLE_SELECT 1
                        
                        #define SECOND_ROW_FIRST_COLUMN_LCD_ADDRESS 40
                        #define INCREASE_ARRAY_BY_GAME_TIME_BITS 2
                        #define DDRAM_ADDRESS_COMMAND 7
                        #define LCD_SECOND_ROW_FIRST_ADDRESS 40
                        
                        // DDRD == Data port
                        // DDRC == Control port
                        #include <avr/io.h>
                        #include <util/delay.h>
                        #include <avr/interrupt.h>
                        
                        uint8_t newIndex = 0;
                        
                        int timerCountActive = 0; // If it is active, the string currently shown should be updated.
                        int interruptsEnabled = 0;
                        
                        int timerIndex = 0; // The index of where the last timer was shown
                        int timerTextSize = 0; // Size of the text that the timer is shown in
                        int timercount = 60;
                        int linebreakIndex = 0; // index where linebreak occurs
                        
                        char timerCountStr[2];
                        
                        /** Compare Match Timer interrupt used to get precise timings. This is used to send updates to the LCD at precisely at 1 update per second. */
                        ISR(TIMER1_COMPA_vect) {
                            timercount--;
                            if (timercount != 0) {
                                sprintf(timerCountStr, "%d", timercount);
                                EditLCDText(timerIndex,timerCountStr, 2);	
                            }
                            
                            if (timercount == 10) {
                                ClearDisplay();
                                SendString("Yay!",4);
                            }
                        }
                        
                        /** Send command to the display (RS = 0) */
                        void SendCommand(char cmd) {
                            PORTB = cmd;
                            PORTC &= ~(1<<REGISTER_SELECT);
                            PORTC &= ~(1<<READ_WRITE_SELECT);
                            PORTC |= (1<<ENABLE_SELECT);
                            _delay_us(1);
                            PORTC &= ~(1<<ENABLE_SELECT);
                            _delay_ms(1);
                        }
                        
                        /** Send data to the display (RS = 1) */
                        void SendChar(unsigned char data) {
                            PORTB = data;
                            PORTC |= 1<<REGISTER_SELECT;
                            PORTC &= ~(1<<READ_WRITE_SELECT);
                            
                            PORTC |= (1<<ENABLE_SELECT);
                            _delay_us(1);
                            PORTC &= ~(1<<ENABLE_SELECT);
                            _delay_us(50);
                            
                        }
                        
                        // Hops to row 2.
                        void SkipToNextRow(int size, int atIndex) {
                            SendCommand((1<<DDRAM_ADDRESS_COMMAND) + LCD_SECOND_ROW_FIRST_ADDRESS);
                        }
                        
                        /** Displays the time for a round. */
                        void DisplayRoundTimer(char data[], int atIndex , int size) {
                            
                            timerIndex = atIndex;
                            timerTextSize = size;
                            data[atIndex] = '6';
                            data[atIndex + 1] = '0';
                            
                            timerCountActive = 1;
                        }
                        
                        /** Clears all data from the LCD DDRAM. */
                        ClearDisplay() {
                            SendCommand(0b00000001); // TODO: Setup macros for binary commands.
                        }
                        
                        /** Extracts the escape command and performs actions based on what command was sent in. Returns the next index of char to read once the action is done. */
                        uint8_t ExtractEscapeCommand(char data[], int atIndex, int size) {
                            if (data[atIndex + 1] == 'n') {
                                SkipToNextRow(size,atIndex);
                                linebreakIndex = atIndex;
                                return atIndex = atIndex + 2;
                            }
                            else if (data[atIndex + 1] == 't') {
                                DisplayRoundTimer(data,atIndex,size);
                                return atIndex - 1; // The escape character has been replaced by timer, write out new chars.
                            }
                            else { // if it isn't a letter then it's likely a number indicating the padding, NOTE: Padding should be a integer value between 0-9
                                if (isdigit(data[atIndex + 1])) {
                                    // TODO: Program logic of padding.
                                } else {
                                    
                                }
                            }
                            return atIndex;
                        }
                        
                        /** Sends a string to the LCD and displays it. */
                        void SendString(unsigned char data[], int size) {
                        
                            if (timerCountActive) { 
                                 TCCR1B &= ~(1 << CS12);
                                 timerCountActive = 0;
                                 interruptsEnabled = 0; 
                                 }
                            
                            for (int i = 0; i < size;i++)
                            {
                                    if (data[i] == 'Q') {
                                        newIndex = ExtractEscapeCommand(data,i,size);
                                        i = newIndex;
                                        continue;
                                    }
                                    PORTB = data[i];
                                    PORTC |= 1<<REGISTER_SELECT;
                                    PORTC &= ~(1<<READ_WRITE_SELECT);
                                    
                                    PORTC |= (1<<ENABLE_SELECT);
                                    _delay_us(1);
                                    PORTC &= ~(1<<ENABLE_SELECT);
                                    _delay_ms(1);	
                            }
                            if (timerCountActive && !interruptsEnabled) {
                                EnableTimerInterrupt();
                            }
                        }
                        
                        /** Changes a portion of the text displayed on the LCD at the specified index. */
                        void EditLCDText(uint8_t index, char newData[], int size) { // Second row is from 40 to 67
                            
                            if (index > 16) {
                            SendCommand((1<<DDRAM_ADDRESS_COMMAND) + LCD_SECOND_ROW_FIRST_ADDRESS + (timerIndex - linebreakIndex) - 3); // -3 offset to skip escape chars.
                            } else {
                                // First row...
                            }
                            for (int i = 0; i < size; i++)
                            {
                                SendChar(newData[i]);
                            }
                        }
                        
                        /** Enables timer interrupts. */
                        EnableTimerInterrupt() {
                            sei();
                            TCCR1B |= 1 << WGM12; // CTC select
                            TCCR1B |= 1 << CS12; // prescaler 256
                            TIMSK1 |= 1 << OCIE1A; // the Timer/Counter1 Output Compare A Match interrupt is enabled
                            
                            OCR1A = (F_CPU/(1*2*256) - 1); // 0.48 is the time for calculation, calculated to achieve appx 1 interrupt per second. 16_000_000/(0.48*2*256) equal appx = 65_000/ 2^16 (16 bit timer)
                            interruptsEnabled = 1;
                        }
                        
                        /** Initialize the display */
                        void LCD_init(void) {
                            DDRC = 0b11000010;
                            DDRB = 0xFF;
                            SendCommand(0b00111110); // Function set / 8 bit
                            SendCommand(0b00001110); // Display on
                            SendCommand(0b00000010); // Entry Mode // auto increase
                            //SendCommand(0b00000010); //	return home
                            SendCommand(0b00000001); // Clear display
                        }
                        
/*
                         * NeoPixel.h
                         *
                         * Created: 2019-04-12 18:00:18
                         *  Author: fe4242bo-s
                         */ 
                        
                        
                        #ifndef NEOPIXEL_H_
                        #define NEOPIXEL_H_
                        
                        void AddPixel(int x, int r, int g, int b);
                        void clearPixels();
                        
                        
                        #endif /* NEOPIXEL_H_ */
                        
/*
                         * NeoPixel.c
                         *
                         * Created: 2019-04-12 18:00:36
                         *  Author: fe4242bo-s
                         */ 
                        
                        extern void Reset();
                        extern void Send_One();
                        extern void Send_Zero();
                        
                        #include "../../Constants/GameStrings.h"
                        
                        #include <math.h>
                        
                        /** Struct to define the RGB values for a pixel. Used to enumerate through an array of pixels. */
                        typedef struct {
                            int r;
                            int g;
                            int b;
                        } pixel_t;
                        
                        pixel_t leds[16];
                        
                        /** Resets all pixels to their default values. */
                        void clearPixels()
                        {
                            for (int i = 0; i < 20; i++) {
                                leds[i].r = 0;
                                leds[i].g = 0;
                                leds[i].b = 0;
                            }
                            
                            for (int j = 0; j < 24 * 16; j++)
                            {
                                Send_Zero();
                            }
                            Reset();
                        }
                        int bitCompare;
                        
                        /** Sets the specified pixel to the specified RGB values. */
                        void setPixel(int x, int r, int g, int b) {
                            leds[x].r = r;
                            leds[x].g = g;
                            leds[x].b = b;
                            
                            for (int i = 0; i < 16; i++) {
                                //Green
                                int n = leds[i].g;
                                int d = 0b10000000;
                                for(int j = 0;j<8;j++){
                                    bitCompare = (n&d);
                                    if(bitCompare>0){
                                        Send_One();
                                    }
                                    else{
                                        Send_Zero();
                                    }
                                            
                                    d=d>>1;
                                }
                                
                                //Red
                                n = leds[i].r;
                                d = 0b10000000;
                                        
                                for(int j = 0;j<8;j++){
                                    bitCompare = (n&d);
                                    if(bitCompare>0){
                                        Send_One();
                                    }
                                    else{
                                        Send_Zero();
                                    }
                                            
                                    d=d>>1;
                                }
                                
                                //BLUE
                                n = leds[i].b;
                                d = 0b10000000;
                                for(int j = 0;j<8;j++){
                                    bitCompare = (n&d);
                                    if(bitCompare>0){
                                        Send_One();
                                    }
                                    else{
                                        Send_Zero();
                                    }
                                    
                                    d=d>>1;
                                }
                            }
                        
                            Reset();
                        }
                        
/*
 * GameStrings.h
 *
 * Created: 2019-04-10 15:07:37
 *  Author: fe4242bo-s
 */ 

/** This file will only consist of hardcoded strings.  */

#ifndef GAMESTRINGS_H_
#define GAMESTRINGS_H_
// if \n calculate for new line, if \CONSTANT add padding. {xx} is the time.
extern const char START_TEXT[29] = "4 in a row\n Press 1 to start";
extern const char PLAYER_ONE_SETUP[23] = "Player 1\n Choose color";
extern const char PLAYER_TWO_SETUP[23] = "Player 2\n Choose color";
extern const char P1_CHOICE[] = "P1 Choose column \n {xx}";
extern const char P2_CHOICE[] = "P2 Choose column \n {xx}";
	
extern const char P1_WON[] = "Player 1 WON! \n {xx}";
extern const char P2_WON[] = "Player 2 WON! \n {xx}";
extern const char GAME_TIE[] = "TIE! \n {xx}";

extern const char GAME_PAUSE[] = "Game Paused \n Press 1 to start";
extern const int nPixels = 16;

#endif /* GAMESTRINGS_H_ */
/*
 * NeoPixelTiming.s
 *
 * Created: 2019-04-17 10:30:49
 *  Author: fe4242bo-s
 */ 

 

 .global Reset
 .global Send_One
 .global Send_Zero

#define DATA_PORT 0x0B
#define DATA_DDR 0x0A


 Reset: 
	
	nop x 396
	ret


Send_One: 
	ldi r22, 0b10000000
	out DATA_PORT, r22
	nop
	nop
	nop
	nop
	ldi r22, 0b00000000 // 700/125 = 5.6 clock cycles
	out DATA_PORT, r22
	nop // 600/125 = 4.8 clock cycles
 ret


Send_Zero:
	
	ldi r18, 0b10000000
	out 0X0B, r18
	nop

	ldi r18, 0
	out 0X0B, r18

	nop
	nop

	ret


	

Detaljer

Spelet går ut på att få token att stanna på ett visst ställe för varje varv den färdas. Ju fler gånger desto fler poäng. Svårighetsgraden ökar vart femte lyckade drag. Vid miss nollställs poängställningen.


Tack!

Om du är intresserad av hur spelet byggdes eller om du vill vidareutveckla det så ta gärna en titt på rapporten för fler detaljer. I rapporten står allt från idéförfarandet till slutprodukt.


ATMEGA 1284

NeoPixels

LCD Display

EEPROM

Timers

Interrupts

Buttons

Galleri

Token Ring, projektarbete. Grupp 9 IDA1 LTH HBG 2019.

Sound by Bensound.com