rwth-uctetris/tetris.c
2011-07-16 02:13:39 +02:00

267 lines
5.9 KiB
C

#include <util/delay.h>
#include <stdlib.h>
#include <avr/io.h>
#include "tetris.h"
extern volatile uint8_t *volatile display_buffer; /* Buffer für Display */
volatile uint8_t *volatile brd = NULL; /* NULL if not in tetris mode */
volatile stone_t stn;
static int16_t scoring[] = {0, 40, 100, 300, 1200}; /* scoring multiplicator for 0-4 flushed lines */
static clipping_t shapes[][4] = { /* including 4 ccw rotations */
{ // SHAPE_I
{0x08, 0x08, 0x08, 0x08},
{0x00, 0x1e, 0x00, 0x00},
{0x08, 0x08, 0x08, 0x08},
{0x00, 0x3c, 0x00, 0x00}
},
{ // SHAPE_J
{0x00, 0x08, 0x08, 0x18},
{0x00, 0x00, 0x1c, 0x04},
{0x00, 0x0c, 0x08, 0x08},
{0x00, 0x10, 0x1c, 0x00}
},
{ // SHAPE_L
{0x00, 0x10, 0x10, 0x18},
{0x00, 0x08, 0x38, 0x00},
{0x00, 0x30, 0x10, 0x10},
{0x00, 0x00, 0x38, 0x20}
},
{ // SHAPE_O
{0x00, 0x18, 0x18, 0x00},
{0x00, 0x18, 0x18, 0x00},
{0x00, 0x18, 0x18, 0x00},
{0x00, 0x18, 0x18, 0x00}
},
{ // SHAPE_S
{0x00, 0x10, 0x18, 0x08},
{0x00, 0x0c, 0x18, 0x00},
{0x00, 0x08, 0x0c, 0x04},
{0x00, 0x0c, 0x18, 0x00}
},
{ // SHAPE_Z
{0x00, 0x08, 0x18, 0x10},
{0x00, 0x00, 0x18, 0x0c},
{0x00, 0x04, 0x0c, 0x08},
{0x00, 0x00, 0x18, 0x0c}
},
{ // SHAPE_T
{0x00, 0x10, 0x38, 0x00},
{0x00, 0x10, 0x30, 0x10},
{0x00, 0x38, 0x10, 0x00},
{0x00, 0x10, 0x18, 0x10}
}
};
uint8_t tetris_flush_lines() {
uint8_t lines = 0;
uint8_t i = NUM_LINES - 1;
while (i >= 0) {
if (brd[i] == 0xFF) {
lines++;
for (uint8_t j = i; j > 0; j--) { /* restock with remaining lines */
brd[j] = brd[j-1];
}
}
else if (brd[i] == 0x00) { /* empty line, no following line can be non-empty => aborting */
break;
}
else {
i--;
}
}
return lines; /* required to calculate score */
}
bool_t tetris_turn_stone() {
stn.orientation++;
stn.orientation %= 4;
uint8_t check = 0;
clipping_t tmp;
tetris_copy_clipping(shapes[stn.shape][stn.orientation], tmp);
// shift to origin
for (uint8_t i = 0; i < 4; i++) {
if (stn.pos_x > 0) {
tmp[i] >>= abs(stn.pos_x);
check = tmp[i] << abs(stn.pos_x);
}
else {
tmp[i] <<= abs(stn.pos_x);
check = tmp[i] >> abs(stn.pos_x);
}
if (check != shapes[stn.shape][stn.orientation][i] || brd[i+stn.pos_y] & tmp[i]) {
return FALSE; // detected collision
}
}
tetris_copy_clipping(tmp, stn.clipping);
return TRUE;
}
bool_t tetris_shift_stone(direction_t dir, uint8_t times) {
while (times--) {
if(tetris_detect_collision(dir)) {
return FALSE;
}
if (dir == DIR_DOWN) { // nach unten fallen lassen
stn.pos_y++;
}
else {
if (dir == DIR_LEFT) { // nach links schieben
stn.pos_x--;
for (uint8_t i = 0; i < 4; i++) {
stn.clipping[i] <<= 1;
}
}
else if (dir == DIR_RIGHT) { // nach rechts schieben
stn.pos_x++;
for (uint8_t i = 0; i < 4; i++) {
stn.clipping[i] >>= 1;
}
}
}
}
return TRUE;
}
bool_t tetris_detect_collision(direction_t dir) {
// Kollision beim Fallen?
if (dir == DIR_ALL || dir == DIR_DOWN) {
for (int8_t i = 3; i >= 0; i--) {
if (stn.clipping[i]) {
if (stn.pos_y+i+1 >= NUM_LINES) { // Kollision mit Boden
return TRUE;
}
else if (stn.clipping[i] & brd[stn.pos_y+i+1]) { // Kollision mit liegenden Steinen
return TRUE;
}
}
}
}
// Kollision beim Shiften?
if (dir == DIR_ALL || dir == DIR_LEFT || dir == DIR_RIGHT) {
for (uint8_t i = 0; i < 4; i++) {
if (stn.clipping[i] & ((dir == DIR_LEFT) ? 0x80 : 0x01)) { // Kollision mit Waenden
return TRUE;
}
// Kollision mit Steinen rechts oder links?
else if (dir == DIR_LEFT && (stn.clipping[i] << 1) & brd[stn.pos_y+i]) { // links
return TRUE;
}
else if (dir == DIR_RIGHT && (stn.clipping[i] >> 1) & brd[stn.pos_y+i]) { // rechts
return TRUE;
}
}
}
return FALSE; /* no collision */
}
void tetris_start() {
uint8_t debounce = 0;
uint8_t lines = 0;
uint8_t frames = 48;
uint8_t score = 0;
uint8_t level = 0;
brd = malloc(sizeof(board_t));
display_buffer = brd+4; /* skipping first 4 "virtual" lines */
/* starting with empty board */
memset(brd, 0, NUM_LINES);
while ( TRUE ) { /* main loop */
/* add new stone on the top of our board */
uint8_t shape = rand() % NUM_SHAPES;
uint8_t orientation = rand() % 4;
stn.shape = shape;
stn.orientation = orientation;
stn.pos_x = 0;
stn.pos_y = 0;
tetris_copy_clipping(shapes[shape][orientation], stn.clipping);
if (lines > (level + 1) * 10) {
level++; /* next level */
frames = 48 - level * (46/19.0); /* update frames per step */
}
do {
/* lets fall stone */
tetris_shift_stone(DIR_DOWN, 1);
/* poll for user interaction */
for (uint8_t i = 0; i < frames; i++) {
if (debounce) {
debounce--;
}
else {
uint8_t keys = ~PINB;
if (keys & KEY_A)
tetris_turn_stone();
if (keys & KEY_LEFT)
tetris_shift_stone(DIR_LEFT, 1);
if (keys & KEY_RIGHT)
tetris_shift_stone(DIR_RIGHT, 1);
if (keys & KEY_DOWN)
tetris_shift_stone(DIR_DOWN, 1);
if (keys & KEY_B) /* free fall */
tetris_shift_stone(DIR_DOWN, NUM_LINES);
if (keys & KEY_LEFT && keys & KEY_RIGHT) {
uint8_t *p = brd[NUM_LINES-1];
while(*p) {
*p = 0;
p--;
}
}
if (keys & KEY_Y) {
free(brd);
memset(stn.clipping, 0, 4);
return; // exit tetris
}
if (keys) {
debounce = 9;
}
}
_delay_ms(900 / FRAMES); // sleep for 1 frame (100ms approx for calculations)
}
} while (tetris_detect_collision(DIR_DOWN) == FALSE);
for (uint8_t i = 0; i < 4; i++) {
brd[stn.pos_y+i] |= stn.clipping[i];
}
/* check for completed lines and calculate score */
uint8_t flushed = tetris_flush_lines();
score += (level + 1) * scoring[flushed];
lines += flushed;
if (brd[3] > 0) {
free(brd);
memset(stn.clipping, 0, 4);
return; /* game over */
}
}
free(brd);
brd = NULL;
return;
}