Last active
January 4, 2016 12:09
-
-
Save dwhacks/8619740 to your computer and use it in GitHub Desktop.
Progress on gamby blocdrop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
BLOC DROP: A strangely familiar falling block game | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
by David Randall Stokes (gamby@logicalzero.com) 9/05/2012 | |
THIS IS STILL A WORK-IN-PROGRESS. The game is playable, but | |
the scoring doesn't work correctly. | |
*/ | |
#include <Gamby.h> | |
#include <avr/pgmspace.h> | |
////////////////////////////////////////////////////////////////////////////// | |
// The falling block assemblies. There are 7 different shapes. Each | |
// orientation of a piece (0, 90, 180 and 270 degrees) is actually another | |
// shape, which is why they are stored in a 2D array (pieces[shape][rotation]). | |
// Each piece (and each orientation of each piece) is stored as a 4x4 grid, | |
// even though most are smaller. Since these are a 4x4 grid, they are stored as | |
// 16b unsigned integers, just like blocks and GambyGraphicsMode fill patterns. | |
unsigned int pieces[7][4] = { | |
{ 0x0720, 0x2620, 0x2700, 0x4640 }, // "T" now its good DW **DRU**0 | |
{ 0x0f00, 0x2222, 0x0f00, 0x2222 }, // Bar | |
{ 0x2310, 0x3600, 0x2310, 0x3600 }, // 'S' zig-zag | |
{ 0x1320, 0x6300, 0x1320, 0x6300 }, // 'Z' zig-zag | |
{ 0x4700, 0x3220, 0x7100, 0x2260 }, // 'J' | |
{ 0x7400, 0x6220, 0x1700, 0x2230 }, // 'L' | |
{ 0x3300, 0x3300, 0x3300, 0x3300 } // 2x2 square | |
//{ 0x8f80, 0xcc44, 0x1710, 0x44cc } // SURPRISE **DRU**10 | |
}; | |
////////////////////////////////////////////////////////////////////////////// | |
// The GambyBlockMode block palette. | |
// Each piece has its own block type: falling piece `n` uses block `n+1`. | |
// There is a duplicate of each block used for 'dropped' pieces (`n+8`); this | |
// makes it easy to ignore the currently falling piece's blocks when testing | |
// for collisions. | |
PROGMEM prog_uint16_t palette[] = { | |
0x0000, // 0: Empty | |
0xf99f, // 1: Block 0 ("T") | |
0xadaf, // 2: Block 1 ("-") | |
0xfdbf, // 3: Block 2 ('S') | |
0xfbdf, // 4: Block 3 ('Z') | |
0xfafd, // 5: Block 4 ('J') | |
0xdadf, // 6: Block 5 ('L') | |
0xf9df, // 7: Block 6 (2x2) | |
0xf99f, // 8: Block 0 dropped | |
0xadaf, // 9: Block 1 dropped | |
0xfdbf, // 10: Block 2 dropped | |
0xfbdf, // 11: Block 3 dropped | |
0xfafd, // 12: Block 4 dropped | |
0xdadf, // 13: Block 5 dropped | |
0xf9df, // 14: Block 6 dropped | |
0xffff, // 15: "Well" wall | |
}; | |
////////////////////////////////////////////////////////////////////////////// | |
// Constants to be used when rotating a piece. Since bytes are only positive | |
// and there are only 4 orientations, counter-clockwise is 3; the number | |
// will 'wrap around' to get to the previous number. | |
const byte ROTATE_CW = 1; | |
const byte ROTATE_CCW = 3; | |
// Some game-related constants | |
const byte WELL_WIDTH = 12; | |
const byte WELL_BOTTOM = 15; | |
const int DEFAULT_SPEED = 800; | |
const byte INPUT_DELAY = 50; | |
const byte START_X = 4, START_Y = 0; | |
const byte NEXT_X = NUM_BLOCK_COLUMNS - 4, NEXT_Y = 0; | |
boolean playing; // 'true' when the game is playing, 'false' after game over | |
byte currentPiece; // Index of the falling piece in the pieces 'outer' array | |
byte dir; // orientation of the falling piece ('inner' piece array) | |
byte x, y; // The upper left corner of the piece's 4x4 grid | |
unsigned long timeToDrop; // Time remaining (ms) before the piece drops a level | |
unsigned long dropSpeed; // The speed of the drop, changes with skill level | |
int score; // The player's score | |
int highScore; // Self-explanatory | |
int level; | |
byte nextPiece; // Index of the next piece to fall (see currentPiece) | |
int pieceCount; // The number of pieces that have fallen | |
unsigned long lastInputTime; // The time at which gamby.readInputs() was last called | |
byte lastInputs; // The state of the inputs the last time they were checked. | |
// Bring in the font from the other tab (font.ino) | |
extern prog_int32_t font[]; | |
GambyBlockMode gamby; | |
////////////////////////////////////////////////////////////////////////////// | |
void setup() { | |
gamby.palette = palette; | |
gamby.font = font; | |
showSplashScreen(); | |
// Reset the random number generator. With the splash screen showing | |
// first, differences in starting time should make this pretty well | |
// randomized. | |
randomSeed(millis()); | |
startGame(); | |
} | |
void loop() { | |
if (playing) { | |
// Read the inputs if enough time has passed | |
if (millis() > lastInputTime) { | |
gamby.readInputs(); | |
// Moving left/right and rotating don't auto-repeat, but dropping does | |
if (gamby.inputs & DPAD_DOWN || gamby.inputs != lastInputs) { | |
lastInputs = gamby.inputs; | |
if (gamby.inputs & DPAD_LEFT) | |
movePiece(x-1, y, dir); | |
else if (gamby.inputs & DPAD_RIGHT) | |
movePiece(x+1, y, dir); | |
else if (gamby.inputs & DPAD_DOWN) | |
timeToDrop = 0; | |
else if (gamby.inputs & DPAD_UP) | |
timeToDrop = millis(); // HARD DROP. (0 might not be right tho, not sure if coordinate will just go at bottom or stop when reaching ) **DRU**1 | |
else if (gamby.inputs & BUTTON_2) | |
rotate(ROTATE_CW); //(btn Right) TURN CLOCKWIZE **DRU** | |
else if (gamby.inputs & BUTTON_3) | |
rotate(ROTATE_CCW); // (btn left) TURN COUNTER-CLOCKWIZE | |
lastInputTime = millis() + INPUT_DELAY; | |
} | |
} | |
// If it's time, drop the piece down a row. | |
if (millis() > timeToDrop) | |
dropPiece(); | |
} | |
else { | |
// After 'game over'; wait for a button press to restart. | |
delay(100); | |
gamby.readInputs(); | |
if (gamby.inputs) | |
startGame(); | |
} | |
} | |
////////////////////////////////////////////////////////////////////////////// | |
// Start a new game. | |
// | |
// | |
void startGame() { | |
byte i; | |
// 1. Draw the background | |
gamby.clearScreen(); | |
// The vertical sides of the 'well' | |
for (i=0; i<WELL_BOTTOM; i++) { | |
gamby.setBlock(0,i,15); | |
gamby.setBlock(WELL_WIDTH-1, i, 15); | |
} | |
// The well's bottom | |
for (i=0; i<WELL_WIDTH; i++) { | |
gamby.setBlock(i, WELL_BOTTOM, 15); | |
} | |
// setBlock() doesn't immediately draw; update() shows all the | |
// changes at the same time. | |
gamby.update(0,0,WELL_WIDTH-1,WELL_BOTTOM); | |
// Draw the rest of the screen (the 'Next:' text, etc.) | |
gamby.setPos(52,6); | |
gamby.print("Score:"); | |
// Draw Level on the screen **DRU**6 DW moved to the middle | |
gamby.setPos(52,4); | |
gamby.print("Level:"); | |
// 2. Reset some of the player variables | |
score = 0; | |
level = 0; | |
x = START_X; | |
y = START_Y; | |
dropSpeed = DEFAULT_SPEED; // todo: make this change by 'level'. | |
playing = true; | |
// 3. Add the first piece. | |
nextPiece = random(7); | |
addNewPiece(); | |
} | |
// End the game. Called when a new block doesn't have room | |
// to be added. | |
// | |
void gameOver() { | |
playing = false; | |
showGameOver(); | |
} | |
// Start a new piece at the top of the 'well,' using the shape in the | |
// 'next piece' preview. A new 'next piece' is picked and drawn. The | |
// 'drop clock' is also reset. | |
void addNewPiece() { | |
// Erase 'next piece' preview | |
drawPiece(nextPiece, 0, NEXT_X, NEXT_Y, 0); | |
currentPiece = nextPiece; | |
// Pick a new 'next piece' that's not the same as the current one. | |
// In theory, this loop could run forever, but that's really unlikely. | |
//while (nextPiece == currentPiece) We do want the same as previous if random... | |
nextPiece = random(7); //7 not 6... 6 excludes de 4x4 block... | |
// Draw the new preview | |
gamby.setPos(52,0); | |
gamby.print("Next:"); | |
drawPiece(nextPiece, 0, NEXT_X, NEXT_Y, nextPiece+1); | |
// Reset the rest of the piece-related variables | |
x = START_X; | |
y = START_Y; | |
dir = 0; | |
timeToDrop = millis() + dropSpeed; | |
// If adding a piece fails (clips something), then the 'well' is | |
// full -- the game is over. | |
if (!movePiece(x, y, dir)) | |
gameOver(); | |
// Everything's good, so increment the total piece count. | |
pieceCount++; | |
} | |
// Move the piece to a new position (or rotation), if that new | |
// position/direction doesn't hit anything. If it does clip something | |
// (dropped blocks or the sides of the well), the piece isn't moved. | |
// This will usually be called with only one of the 3 arguments different | |
// from the current position/direction. | |
boolean movePiece(byte newX, byte newY, byte newDir) { | |
// See if the piece in the new position/rotation hits something. | |
// If it does, return false. | |
if (checkHits(newX, newY, newDir)) | |
return false; | |
// The piece can be moved safely. Erase the piece in its old position and | |
// redraw it in its new position. | |
drawPiece(currentPiece, dir, x, y, 0); | |
dir = newDir; | |
x = newX; | |
y = newY; | |
drawPiece(currentPiece, dir, x, y, currentPiece+1); | |
// | |
return true; | |
} | |
// Draw a piece. The position, direction, shape and block type are all | |
// parameters; this is used to erase pieces as well as to show the | |
// 'next' piece, not just the currently falling one. | |
void drawPiece(byte piece, byte d, byte px, byte py, byte block) { | |
byte i,j; | |
for (i=0; i<4; i++) { | |
for (j=0; j<4; j++) { | |
if (getPieceBlock(piece, d, j, i)) { | |
gamby.setBlock(px+j, py+i, block); | |
} | |
} | |
} | |
gamby.update(px, py, px+3, py+3); | |
} | |
// Given a piece at a certain orientation -- which are all 4x4 grids -- | |
// determine if one of the squares in that grid is filled. | |
// | |
boolean getPieceBlock(byte piece, byte d, byte px, byte py) { | |
// This is overly complex due to the fact that the pieces are stored like | |
// GAMBY blocks/patterns: bottom to top, left to right. | |
return (pieces[piece][d] & (1 << (15 - ((3-py) + (px << 2))))) != 0; | |
} | |
// Attempt to rotate the currently falling piece. | |
// | |
// | |
void rotate(byte d) { | |
// This just wraps movePiece(), hiding the weirdness in the way | |
// the rotation is done. | |
movePiece(x, y, (dir + d) & B00000011); | |
} | |
// Check if the peice will hit something if moved or rotated. | |
// | |
// | |
boolean checkHits(byte newX, byte newY, byte newDir) { | |
byte i,j; | |
for (i=0; i<4; i++) | |
// if ((newY + i) < 15) | |
for (j=0; j<4; j++) | |
if (getPieceBlock(currentPiece, newDir, j, i) && | |
gamby.getBlock(newX+j,newY+i) > 7) | |
return true; | |
return false; | |
} | |
// | |
// | |
// | |
void dropPiece() { | |
if (!movePiece(x, y+1, dir)) { | |
// Draw in the falling piece's blocks using the | |
// identical-looking 'static' version. | |
drawPiece(currentPiece, dir, x, y, currentPiece+8); | |
int c = checkCompleteRows(); | |
if (c == 1)//(c > 0) | |
score += 40 * (level+1); //score += 10 ^ c;(brain fart much?? **DRU**4 | |
else if (c == 2) | |
score += 100 * (level+1); | |
else if (c == 3) | |
score += 300 * (level+1); | |
else if (c == 4) //TETRIS MUTHA FUCKA! **DRU**5 | |
score += 1200 * (level+1); | |
if (score > 999) | |
level = score / 1000; | |
gamby.setPos(52,7); | |
gamby.print(score, DEC); | |
gamby.setPos(52,5); //LEVELS !!! dw **DRU**7 | |
gamby.print(level, DEC); | |
gamby.clearLine(); | |
addNewPiece(); | |
} | |
timeToDrop = millis() + dropSpeed; | |
} | |
// Clear filled rows, shifting down everything above each cleared row. | |
// The number of rows cleared is returned. | |
// | |
byte checkCompleteRows() { | |
int cleared; // The number of cleared rows | |
byte c; // The number of blocks per row | |
byte i, j; | |
for (i=0; i<WELL_BOTTOM; i++) { | |
byte c = 0; | |
for (j=1; j<WELL_WIDTH-1; j++) { | |
if (gamby.getBlock(j,i) > 7) { | |
c++; | |
} | |
} | |
// If the row is full, slide everything down. | |
if (c == WELL_WIDTH-2) { | |
byte m, n; | |
for (m=1; m<WELL_WIDTH-1; m++) { | |
for (n=i; n>0; n--) { | |
gamby.setBlock(m, n, gamby.getBlock(m,n-1)); | |
} | |
gamby.setBlock(m, 0, 0); | |
} | |
cleared++; | |
} | |
} | |
// Draw all the changes made with setBlock() | |
gamby.update(1,0,WELL_WIDTH-1,WELL_BOTTOM-1); | |
return cleared; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment