📄 tetrislib.c
字号:
unsigned char i;
unsigned char moving = 0;
// First, check if bricks are currently moving in animation
for (i=0; i<active_bricks; i++) {
if (active_brick[i].x & 0xF0) {
active_brick[i].x -= 1 << 4;
moving = 1;
}
}
// Test all bricks if they are able to move one cube down
if (!moving) {
for (i=0; i<active_bricks; i++) {
if (active_brick[i].y > 0) {
// Remove brick from occupancy grid
occupancy_xor_brick(&active_brick[i]);
// Test if brick is ok moving one cube down
active_brick[i].y--;
if (test_if_brick_fits(&active_brick[i])) {
// brick did fit, keep new y coordinate
// add brick in occupancy grid at new position
occupancy_xor_brick(&active_brick[i]);
// Start animation
active_brick[i].x |= 10 << 4 ;
moving = 1;
}
else {
// didn't fit, restore old y coordinate
active_brick[i].y++;
// add brick at old position
occupancy_xor_brick(&active_brick[i]);
}
}
}
}
return moving;
}
// Zap row with coordinate y.
// Changes both active_brick array and occupancy grid
void zap_row(signed char y)
{
unsigned char i;
signed char dy;
struct Brick_Type tmp1,tmp2;
for (i=0; i<active_bricks; i++) {
dy = y - active_brick[i].y;
if ((dy >=0) && (dy < 4)) {
// this brick must be split
split_brick(&tmp1, &tmp2, &active_brick[i], dy);
if (fix_brick(&tmp1)) {
// lower brick is ok, copy this brick on top of original
copy_brick(&active_brick[i], &tmp1);
if (fix_brick(&tmp2)) {
// higher brick is also ok, add this brick to end of array
new_brick(&tmp2);
}
}
else if (fix_brick(&tmp2)) {
// only higher brick is ok, copy this brick on top of original
copy_brick(&active_brick[i], &tmp2);
}
else {
// nothing left of the brick!
// delete entry in array and re-check it
delete_brick(i);
i--;
}
}
}
// The effect on occupancy grid is to remove one row completely
occupancy[y] = OCCUPANCY_FREE;
}
// returns coordinate of row to zap, or 0xFF if no row to zap
//
unsigned char find_zap_row(void)
{
unsigned char i;
for (i=0; i<MATRIX_HEIGHT; i++) {
if (occupancy[i] == OCCUPANCY_NONE)
return i;
}
return 0xFF;
}
////////////////////////////////////////////////////////////////////////////////
// GRAPHICS RENDERING
////////////////////////////////////////////////////////////////////////////////
unsigned char two_powers[4] = {1,2,4,8};
unsigned char inline brick_cube(struct Brick_Type *brick,
signed char x, signed char y)
{
//if ((x>=0) && (x<4) && (y>=0) && (y<4)) // below is equivalent
if (((unsigned char)x<4) && ((unsigned char)y<4))
return brick->brick[y] & two_powers[x];
else
return 0;
}
// This macro convinces compiler that its ok to use unsigned 8 bit
#define UCHAR_MUL(x,y) ((unsigned char)((unsigned char)(x)*(unsigned char)(y)))
//
// Draws the brick by tracing the edge all the way around.
// The brick position is basically defined by its x and y coordinate, but it
// can be offset a number of pixels by y_offset and high nibble of x coordinate
//
// y_offset = 0xFF is a special case for drawing the pending brick left of the
// playground.
void draw_brick(struct Brick_Type *brick, unsigned char y_offset)
{
signed char dx, dy;
signed char x, y, xs;
unsigned char tmp;
// Find leftmost brick in lower row
tmp = brick->brick[0];
for (xs=0; xs<4; xs++) {
if (tmp & two_powers[xs])
break;
}
// Start pen
if (y_offset == 0xFF) {
// this special case is used for drawing the pending brick on the left
draw_set_xy(UCHAR_MUL(xs, BRICK_SIZE) + 8 + BRICK_INSET, 180);
}
else {
x = (brick->x & 0x0F) + xs;
y_offset += brick->x >> 4;
y = brick->y;
draw_set_xy(UCHAR_MUL(x, BRICK_SIZE) + X_OFFSET + BRICK_INSET,
UCHAR_MUL(y, BRICK_SIZE) + Y_OFFSET + BRICK_INSET+y_offset);
}
// Trace brick edge, start seeking right
x = xs;
y = 0;
dx = 1;
dy = 0;
do {
if (brick_cube(brick, x+dy, y-dx)) {
// turn downwards
tmp = -dx;
dx = dy;
dy = tmp;
// move inset*2
draw_pen_dxdy(dx, dy, BRICK_INSET * 2);
// one pixel down the new direction
x += dx;
y += dy;
}
else if (brick_cube(brick, x+dx, y+dy)) {
// Continue one pixel this direction
draw_pen_dxdy(dx, dy, BRICK_SIZE);
// update pixelpos
x += dx;
y += dy;
}
else {
// stay in this pixel and turn upwards
draw_pen_dxdy(dx, dy, BRICK_SIZE - BRICK_INSET * 2);
tmp = dx;
dx = -dy;
dy = tmp;
}
// check if we are back facing right on start pixel
} while (!((dx == 1) && (dy == 0) && (y == 0) && (x == xs)) );
}
// This function draws the playground square
void draw_playground(void)
{
unsigned char x1,x2,y1,y2;
x1 = X_OFFSET-1;
x2 = X_OFFSET + BRICK_SIZE*MATRIX_WIDTH;
y1 = Y_OFFSET-1;
y2 = Y_OFFSET + BRICK_SIZE*MATRIX_HEIGHT;
draw_set_xy(x1, y2);
draw_pen_dx(2, (x2-x1)>>1); // draw the playground square extra fast
draw_pen_dy(-2, (y2-y1)>>1);
draw_pen_dx(-2, (x2-x1)>>1);
draw_pen_dy(2, (y2-y1)>>1);
}
void tetris_draw_frame(void)
{
unsigned char i;
DRAW_PIXEL_SPEED = 20;
for (i=0; i<active_bricks; i++) {
draw_brick(&active_brick[i],0);
}
if (graphics_status & SHOW_FALLING_BRICK) {
draw_brick(&falling_brick, (falling_pixels & 0xFF00) >> 8);
}
if (graphics_status & SHOW_ZAPPED_ROWS) {
DRAW_PIXEL_SPEED = 50;
for (i=0; i<MATRIX_HEIGHT; i++) {
if (occupancy[i] & 0x8000) {
draw_box(X_OFFSET + 3,
Y_OFFSET + 3 + UCHAR_MUL(i, BRICK_SIZE),
X_OFFSET + MATRIX_WIDTH*BRICK_SIZE - BRICK_INSET,
Y_OFFSET + BRICK_SIZE - 3 + UCHAR_MUL(i, BRICK_SIZE));
}
}
DRAW_PIXEL_SPEED = 20;
}
draw_brick(&pending_brick, 0xFF);
if (graphics_status & SHOW_ZAP_COUNT) {
vfont_number(X_OFFSET+MATRIX_WIDTH*0.5*BRICK_SIZE - (animation_frame>>2),
Y_OFFSET+MATRIX_HEIGHT*0.75*BRICK_SIZE - (animation_frame>>1),
animation_frame>>2, lines_zapped);
if (--animation_frame == 5) {
graphics_status &= ~SHOW_ZAP_COUNT;
}
}
vfont_number(10,155,4,tetris_stats_lines);
vfont_number(10,135,4,tetris_stats_zapcount);
DRAW_PIXEL_SPEED = 10;
draw_playground();
}
////////////////////////////////////////////////////////////////////////////////
// GAME PLAY IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////////
// alter the falling brick
void tetris_modify_brick(unsigned char dx, unsigned char rot)
{
struct Brick_Type tmp;
if (dx) {
// try to move brick and see if its ok
falling_brick.x += dx;
if (!test_if_brick_fits(&falling_brick)) {
// not ok, move back
falling_brick.x -= dx;
}
}
if (rot) {
// try rotate and see if its ok
rotate_brick(&tmp, &falling_brick, 0);
if (test_if_brick_fits(&tmp)) {
// its ok!
copy_brick(&falling_brick, &tmp);
}
else {
// Not ok, this might be because brick is hitting right edge
// try moving brick left
if (tmp.x>2) {
if (tmp.x--, test_if_brick_fits(&tmp)) {
copy_brick(&falling_brick, &tmp);
}
else if (tmp.x--, test_if_brick_fits(&tmp)) {
copy_brick(&falling_brick, &tmp);
}
else if (tmp.x--, test_if_brick_fits(&tmp)) {
copy_brick(&falling_brick, &tmp);
}
}
}
}
}
void get_new_brick(void)
{
unsigned char c;
// Promote pending brick to falling brick
copy_brick(&falling_brick, &pending_brick);
do {
c = rand() & 7;
} while (c == 7);
// Make new pending brick from template
pending_brick.brick[0] = pgm_read_byte_near(&brick_template[c][0]);
pending_brick.brick[1] = pgm_read_byte_near(&brick_template[c][1]);
pending_brick.brick[2] = 0;
pending_brick.brick[3] = 0;
pending_brick.x = MATRIX_WIDTH/2-1;
pending_brick.y = MATRIX_HEIGHT-2;
}
// Game statemachine implementation
void tetris_tick(void)
{
unsigned char c;
if (tetris_state == TETRIS_NEW_BRICK) {
// add new falling brick
get_new_brick();
falling_pixels = BRICK_SIZE<<8; // reset animation
if (test_if_brick_fits(&falling_brick)) {
graphics_status |= SHOW_FALLING_BRICK;
// go to next state
tetris_state = TETRIS_BRICK_FALLING;
}
else {
// brick doesn't fit... game over
tetris_state = TETRIS_GAME_OVER;
}
}
else if (tetris_state == TETRIS_BRICK_FALLING) {
if (tetris_mode & TETRIS_MODE_SPEEDUP)
falling_pixels -= ((unsigned short)SPEEDUP_SPEED) << 2;
else
falling_pixels -= ((unsigned short)tetris_speed) << 2;
if (falling_pixels < 0) {
falling_pixels += BRICK_SIZE<<8;
// move brick down one step
falling_brick.y--;
if ((falling_brick.y>=0) && test_if_brick_fits(&falling_brick)) {
// still fits
}
else {
// it hit the ground, add brick from previous position
graphics_status &= ~SHOW_FALLING_BRICK;
falling_brick.y++;
new_brick(&falling_brick);
// update occupancy grid
occupancy_xor_brick(&falling_brick);
lines_zapped = 0;
// test zapping
tetris_state = TETRIS_TRY_ZAP;
}
}
}
else if (tetris_state == TETRIS_TRY_ZAP) {
// we need a new brick...
tetris_state = TETRIS_NEW_BRICK;
// ...unless there are lines to zap
while ((c=find_zap_row()) != 0xFF) {
zap_row(c);
occupancy[c] |= 0x8000; // use MSB in occupancy to indicate zapped row
lines_zapped++;
tetris_state = TETRIS_SHOW_ZAPPED;
animation_frame = 20;
}
if ((tetris_state == TETRIS_NEW_BRICK) && (lines_zapped > 0)) {
// We are done zapping, update score
tetris_stats_lines += lines_zapped;
tetris_stats_zapcount++;
// Brag with result if its good enough
if (lines_zapped > 3) {
graphics_status |= SHOW_ZAP_COUNT;
animation_frame = 100;
}
}
}
else if (tetris_state == TETRIS_DO_GRAVITY) {
if (tetris_mode & TETRIS_MODE_GRAVITY)
c = apply_gravity();
else
c = apply_naive_gravity();
if (c)
tetris_state = TETRIS_DO_GRAVITY; // something still moving
else
tetris_state = TETRIS_TRY_ZAP; // try zapping again
}
else if (tetris_state == TETRIS_SHOW_ZAPPED) {
animation_frame--;
if (animation_frame == 0) {
// Animation is done, remove MSB in occupancy grid
calc_occupancy_grid();
// Apply gravity
tetris_state = TETRIS_DO_GRAVITY;
graphics_status &= ~SHOW_ZAPPED_ROWS;
}
else {
// Show zapped rows
graphics_status |= SHOW_ZAPPED_ROWS;
}
}
}
//
// Must be called with 100 Hz
//
void tetris_handler(void)
{
// draw frame
tetris_draw_frame();
// update game
tetris_tick();
}
// Must be called at startup
void tetris_init(void)
{
active_bricks = 0;
graphics_status = 0;
tetris_stats_lines = 0;
tetris_stats_zapcount = 0;
tetris_state = TETRIS_NEW_BRICK;
tetris_mode = 0;
tetris_speed = 16;
calc_occupancy_grid();
get_new_brick();
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -