// Game program (written for 8Mhz Mega163) #include "lcd.c" // LCD drivers, includes most definitions #define Down 0 #define Left 1 #define Right 2 #define Clockwise 3 #define NotClockwise 4 unsigned char players; // Players flag unsigned char buttons; // Gamepad state unsigned char game; // Game state flag unsigned char i,j; // Temp counters unsigned char current_x; // Current block status unsigned char current_y; unsigned char current_block; unsigned char current_rotation; unsigned char last_press; unsigned char win; // Game state variables unsigned char lose; unsigned char level; unsigned int score; unsigned char cleared; unsigned int lines_cleared; unsigned char sound; // Command to sound board (port D) unsigned char pause; unsigned char penalty; float compare; // Ratio compare unsigned int blocks_dropped; unsigned char r_char; // UART character unsigned char next_block; // Next block unsigned int board[18]; // Game board state unsigned int stats[4]; unsigned int blocks[7][4]={{0x2222,0x0f00,0x2222,0x0f00},{0x0464,0x0270,0x0262,0x0720}, {0x0446,0x0170,0x0622,0x0740},{0x0226,0x0710,0x0644,0x0470}, {0x0660,0x0660,0x0660,0x0660},{0x0462,0x0360,0x0462,0x0360}, {0x0264,0x0630,0x0264,0x0630}}; /* Game pieces - blocks[type][rotation], rotation 0 is the initial rotation on creation Type Piece ---- ---- 0 Long Block 1 3 and 1 block 2 Inverted L block 3 L block 4 Square block 5 Left to right diag block 6 Right to left diag block */ // Timer 1 match times (for block drops), level 1 = 2 seconds/drop, level 10 = 4 drops/second unsigned int TimerMatch[10]={11719,9603,7870,6449,5285,4331,3549,2908,2383,1953}; // Block graphics unsigned char LeftBlock[12]={0xff,0xff,0xe0,0xe0,0xe7,0xe7,0xe7,0xe7,0xe0,0xe0,0xff,0xff}; unsigned char RightBlock[12]={0xff,0xff,0x07,0x07,0xe7,0xe7,0xe7,0xe7,0x07,0x07,0xff,0xff}; // Level scores unsigned int levelscores[10]={20,40,60,90,120,150,200,300,500,65535}; void Initialize(void); // Initialize the program void DrawBlock(unsigned char Action); // Draw a block void DrawNextBlock(unsigned char Action); // Draw next block on left side void UpdateGameScreen(unsigned char Stop); // Update the game screen unsigned char CheckOverlap(unsigned char Move); // Check overlap function void LineClearing(void); // Block landing routine void PrintEnd(unsigned char state); // Print ending void PrintStats(void); // Print endgame stats // Timer 0 overflow interrupt (gamepad polling) interrupt [TIM0_OVF] void timer0_overflow(void) begin /* Genesis gamepad pins 5 4 3 2 1 9 8 7 6 Select low / Select High Connection on Port C 1 - Up / Up 2 - Down / Down PC0 3 - Ground / Left PC4 4 - Ground / Right PC3 5 - +5V Vcc 6 - Button A / Button B PC2 7 - Mux select line PC7 8 - Ground Gnd 9 - Start / Button C PC1 Buttons variable bits - A/Start/Down/Left/Right/B/C/Down, low on press */ buttons = PINC << 5; // Grab bottom 3 bits PORTC.7 = 1; // Set select to high #asm nop nop nop #endasm buttons = buttons | (PINC & 0x1f); // Grab the other 5 bits PORTC.7 = 0; // Set select to low if(buttons == 0xff) last_press = 0xff; else begin if(buttons != last_press) begin last_press = buttons; if(game == 0) // If game is off begin if((win == 0) && (lose == 0)) begin switch(buttons) // Switch on button state begin case 0x7f: // Button A if(players == 0) begin Highlight(0x1CF7,0x00,8); // Unhighlight "1 Player" Highlight(0x1F77,0xFF,8); // Highlight "2 Player" players++; // Set to 2 player end else begin Highlight(0x1F77,0x00,8); // Unhighlight "2 Player" Highlight(0x1CF7,0xFF,8); // Highlight "1 Player" players = 0; // Set to 1 player end break; case 0xbf: // Start if(players != 0) // If 2 player mode begin TIMSK = 0; WriteCommand(CursorRight); // Print "Waiting..." MoveCursor(0x01E0); WriteCommand(MemoryWrite); Clear15(); for(i=0;i<10;i++) begin WriteData(wait[i]); end Clear15(); UCSRB = 0x18; UBRR = 51; // Turn on UART UDR = 'w'; // Send a 'w' #asm("sei") while(game == 0) {} // Wait for sync signal #asm("cli") end else game++; // Start the game current_block = TCNT2; ClearLCD(); // Clear the LCD GameSetup(); // Set up the game screen current_y = 17; // Initial block parameters current_x = 7; current_rotation = 0; level = 0; score = 0; OCR1A = TimerMatch[0]; // Get the timer 1 match DrawBlock(Draw); // Draw a new block next_block = TCNT2; // Get next block DrawNextBlock(Draw); // Draw the next block TCNT0 = 0; TCNT1 = 0; TIMSK = 0x11; // Enable Timer 1 match TCCR1B = 0x0d; // Start timer 1 break; end end else // If game is already over begin if(buttons == 0xbf) // Start resets the game begin players = 0; win = 0; lose = 0; penalty = 0; level = 0; score = 0; pause = 0; lines_cleared = 0; sound = Off; PORTD = 0x07 | Off; for(i=2;i<18;i++) begin board[i] = 0xe007; end PORTD.2 = 0; board[0] = 0xffff; board[1] = 0xffff; for(i=0;i<4;i++) begin stats[i] = 0; end blocks_dropped = 0; ClearLCD(); DrawIntro(); end end end else // If game is running begin if(buttons == 0x7f) // Button A begin // Change music if(sound == Off) sound = 0; else sound += 0x10; // Cycle to next song switch(sound) begin case Song0: // Send command to sound PORTD.4 = 0; // board PORTD.5 = 0; PORTD.6 = 0; PORTD.7 = 0; break; case Song1: PORTD.4 = 1; PORTD.5 = 0; PORTD.6 = 0; PORTD.7 = 0; break; case Song2: PORTD.4 = 0; PORTD.5 = 1; PORTD.6 = 0; PORTD.7 = 0; break; case Song3: PORTD.4 = 1; PORTD.5 = 1; PORTD.6 = 0; PORTD.7 = 0; break; case Song4: PORTD.4 = 0; PORTD.5 = 0; PORTD.6 = 1; PORTD.7 = 0; break; case Song5: PORTD.4 = 1; PORTD.5 = 0; PORTD.6 = 1; PORTD.7 = 0; break; case Song6: PORTD.4 = 0; PORTD.5 = 1; PORTD.6 = 1; PORTD.7 = 0; break; case Song7: PORTD.4 = 1; PORTD.5 = 1; PORTD.6 = 1; PORTD.7 = 0; break; case Off: PORTD.4 = 0; PORTD.5 = 0; PORTD.6 = 0; PORTD.7 = 1; break; end PORTD.2 = 1; // Trigger external ISR PrintSound(sound); // Print song name PORTD.2 = 0; end else begin if(pause == 0) // If not paused begin switch(buttons) // Switch begin case 0xde: // Down (quick drop) TCCR1B = 0; TCNT1 = 0; DrawBlock(Undraw); while(CheckOverlap(Down) == 0) begin current_y--; end DrawBlock(Draw); LineClearing(); TCNT0 = 0; TIFR = 0; if(win == 0 && lose == 0) TCCR1B = 0x0d; break; case 0xef: // Left if(CheckOverlap(Left) == 0) begin DrawBlock(Undraw); current_x--; DrawBlock(Draw); end break; case 0xf7: // Right if(CheckOverlap(Right) == 0) begin DrawBlock(Undraw); current_x++; DrawBlock(Draw); end break; case 0xfb: // Counterclockwise if(CheckOverlap(NotClockwise) == 0) begin DrawBlock(Undraw); if(current_rotation == 0) current_rotation = 3; else current_rotation--; DrawBlock(Draw); end break; case 0xfd: // Clockwise rotate if(CheckOverlap(Clockwise) == 0) begin DrawBlock(Undraw); if(current_rotation == 3) current_rotation = 0; else current_rotation++; DrawBlock(Draw); end break; case 0xbf: // Enter pause if(players == 0) begin TCCR1B = 0; pause++; // Set pause flag PrintPause(Draw); end break; end end else // Game is paused begin if(buttons == 0xbf) // Start the game begin pause = 0; PrintPause(Undraw); TCCR1B = 0x0d; end end end end end end TCNT0 = 0; end // Timer 1 compare match (used for block dropping) interrupt [TIM1_COMPA] void cmpA_overflow(void) begin if(CheckOverlap(Down) == 0) // Check if block is overlapping begin DrawBlock(Undraw); // If not, draw a new one current_y--; DrawBlock(Draw); end else begin // If so, game is over TCCR1B = 0; TCNT1 = 0; LineClearing(); if(win == 0 && lose == 0) TCCR1B = 0x0d; end end // UART receive complete ISR (for multiplayer, synchronization) interrupt [UART_RXC] void UART_Rec(void) begin r_char = UDR; // Store the received character if(game == 0) // If game hasn't started begin if(win == 0 && lose == 0) // If the game hasn't started begin game++; // Start the game if(r_char == 'w') UDR = 'r'; // Send a ready signal end end else begin if(r_char == 'l') begin TCCR1B = 0; TIFR = TIFR & 0x01; TIMSK = 0x01; win++; game = 0; end else begin if((r_char >= 1) && (r_char <= 3)) // Penalty signal begin penalty = r_char; // Set flag PrintPenalty(penalty); // Print message end end end end void main(void) begin Initialize(); // Initialize the system while(1) begin while((win == 0) && (lose == 0)) {} // Loop until game is over TCCR0 = 0; // Pause gamepad polling UCSRB = 0; UBRR = 0; // Turn off the UART PORTD = Off | 0x07; // Turn off the music if(win != 0) PrintEnd(Win); // Print ending message else PrintEnd(Lose); PORTD.2 = 0; PrintStats(); // Print the game stats TCCR0 = 5; // Start gamepad polling while((win != 0) || (lose != 0)) {} // Loop until game is reset end end void Initialize(void) begin DDRC = 0x80; // Port C MSB is the gamepad select line PORTC = 0x7f; // Set select to low, pullups enabled DDRD = 0xfc; // Bits 2-7 are outputs to the sound board players = 0; // Default is single player game = 0; // Game is initially off win = 0; lose = 0; penalty = 0; level = 0; lines_cleared = 0; score = 0; pause = 0; // Reset variables last_press = 0xff; sound = Off; // Sound is off PORTD = 0x07 | Off; // Send command to sound board for(i=2;i<18;i++) begin board[i] = 0xe007; // Set initial game board state end PORTD.2 = 0; board[0] = 0xffff; // Bottom row - 2 is board[0] board[1] = 0xffff; // Top row is board[18] for(i=0;i<4;i++) begin stats[i] = 0; end blocks_dropped = 0; TCCR0 = 0; TCCR2 = 0; TCNT0 = 0; TCNT2 = 0; // Reset timers InitLCD(); // Initialize the LCD DrawIntro(); // Draw the introduction screen TIMSK = 0x01; // Turn on timer 0 overflow OCR2 = 6; // Set timer 2 match to 6 TCCR2 = 9; // Prescalar Timer 2 to CLK, clear on match TCCR0 = 5; // Timer 0 clocked at CLK/1024 #asm("sei") // Turn on interrupts end void DrawBlock(unsigned char Action) // Draw or erase a block begin unsigned char k,check; unsigned int address; address = 9174 + (current_x << 1) - (480 * (unsigned int)current_y); WriteCommand(CursorRight); MoveCursor(address); for(i=0;i<4;i++) begin check = (blocks[current_block][current_rotation]>>(i<<2))&0xf; for(j=0;j<12;j++) begin for(k=0;k<4;k++) begin address += 2; if(((check >> (3-k)) & 0x01) != 0) begin WriteCommand(MemoryWrite); if(Action == Draw) begin WriteData(LeftBlock[j]); WriteData(RightBlock[j]); end else begin WriteData(0x00); WriteData(0x00); end end else MoveCursor(address); end address += 32; MoveCursor(address); end end end void DrawNextBlock(unsigned char Action) // Draw or erase the next block indicator begin unsigned char k,check; unsigned int address; address = 0x12EA; WriteCommand(CursorRight); MoveCursor(address); for(i=0;i<4;i++) begin check = (blocks[next_block][0]>>(i<<2))&0xf; for(j=0;j<12;j++) begin for(k=0;k<4;k++) begin address += 2; if(((check >> (3-k)) & 0x01) != 0) begin WriteCommand(MemoryWrite); if(Action == Draw) begin WriteData(LeftBlock[j]); WriteData(RightBlock[j]); end else begin WriteData(0x00); WriteData(0x00); end end else MoveCursor(address); end address += 32; MoveCursor(address); end end end void UpdateGameScreen(unsigned char Stop) // Update game board begin unsigned char k; unsigned int address; address = 0x03FC; MoveCursor(address); for(i=17;i>=Stop;i--) begin for(j=0;j<12;j++) begin WriteCommand(MemoryWrite); for(k=3;k<13;k++) begin if(((board[i]<>(i<<2))&(board[check_y-i]>>(12-check_x)))&0xf) != 0) break; end if(i == 4) return 0; else return 1; end void LineClearing(void) // Block landing routine begin for(i=0;i<4;i++) // Integrate block into board begin board[current_y-i]=board[current_y-i]|(((blocks[current_block][current_rotation]>>(i<<2))&0xf)<< (12-current_x)); end cleared = 0; for(i=2;i<18;i++) // Check for complete lines begin if(board[i] == 0xffff) begin cleared++; for(j=i;j<17;j++) begin board[j] = board[j+1]; end board[17] = 0xe007; UpdateGameScreen(i--); end end if(cleared > 0) // Increment scores begin switch(cleared) begin case 1: score++; break; case 2: score += 3; break; case 3: score += 6; break; case 4: score += 10; break; end stats[cleared-1]++; if((cleared > 1) && (players != 0)) UDR = cleared - 1; // Send penalty itoa(score,lcd_buffer); MoveCursor(0x0051); PrintBuffer(); lines_cleared += cleared; itoa(lines_cleared,lcd_buffer); MoveCursor(0x0141); PrintBuffer(); PrintCleared(cleared); if(score >= levelscores[level]) // Increment level begin OCR1A = TimerMatch[++level]; if(score >= 50000) begin win++; TIMSK = 0x01; game = 0; end else begin itoa((unsigned int)level+1,lcd_buffer); // Print level MoveCursor(0x00C9); PrintBuffer(); end end end else PrintCleared(0); if(win == 0) begin if((players != 0) && (penalty > 0)) // Inflict penalty begin for(i=17;i>17-penalty;i--) begin if(board[i] != 0xe007) begin lose++; TIMSK = 0x01; game = 0; if(players != 0) UDR = 'l'; break; end end for(i=17-penalty;i>=2;i--) begin board[i+penalty] = board[i]; end for(i=2;i= 2) WriteData(but[i]); else WriteData(and[i]); end for(i=0;i<16;i++) begin WriteData(sucks[i]); end end else begin if(score >= 250) begin MoveCursor(0x03D8); WriteCommand(MemoryWrite); for(i=0;i<12;i++) begin WriteData(nice[i]); end end end if(score > high_score) // If user beat high score, update EEPROM value begin MoveCursor(0x0016); WriteCommand(MemoryWrite); for(i=0;i<16;i++) begin WriteData(new_score[i]); end high_score = score; end end