From 19a741cfa2daed6c985a13d3ab1f9077f3a2dac6 Mon Sep 17 00:00:00 2001 From: nino-porcino Date: Tue, 14 Dec 2021 18:40:01 +0100 Subject: [PATCH] add tetris --- apple1.h | 21 ++ interrupt.h | 10 + test.c | 22 +- tetris/bindump.js | 30 ++ tetris/ckboard.h | 112 ++++++ tetris/fonts.body.h | 768 ++++++++++++++++++++++++++++++++++++++++ tetris/fonts.h | 14 + tetris/grboard.h | 257 ++++++++++++++ tetris/intro.h | 65 ++++ tetris/keyboard_input.h | 30 ++ tetris/lib500_mock.h | 15 + tetris/mk.bat | 11 + tetris/mkfont.js | 588 ++++++++++++++++++++++++++++++ tetris/pieces.h | 58 +++ tetris/sprite.h | 16 + tetris/t.bat | 23 ++ tetris/tetris.c | 404 +++++++++++++++++++++ tetris/types.h | 9 + 18 files changed, 2442 insertions(+), 11 deletions(-) create mode 100644 tetris/bindump.js create mode 100644 tetris/ckboard.h create mode 100644 tetris/fonts.body.h create mode 100644 tetris/fonts.h create mode 100644 tetris/grboard.h create mode 100644 tetris/intro.h create mode 100644 tetris/keyboard_input.h create mode 100644 tetris/lib500_mock.h create mode 100644 tetris/mk.bat create mode 100644 tetris/mkfont.js create mode 100644 tetris/pieces.h create mode 100644 tetris/sprite.h create mode 100644 tetris/t.bat create mode 100644 tetris/tetris.c create mode 100644 tetris/types.h diff --git a/apple1.h b/apple1.h index 7bbf866..9f5ea7a 100644 --- a/apple1.h +++ b/apple1.h @@ -83,3 +83,24 @@ byte woz_getkey() { return key; #endif } + +// non blocking keyboard read +// reads a key and return 0 if no key is pressed +byte woz_readkey() { + #ifdef APPLE1 + if((PEEK(KEY_CTRL) & 0x80)==0) return 0; + else return PEEK(KEY_DATA) & 0x7f; + #else + byte key; + byte const *keyptr = &key; + kickasm(uses keyptr, uses GETIN) {{ + jsr GETIN + cmp #0 + bne __keypress + lda #0 + __keypress: + sta keyptr + }} + return key; + #endif +} diff --git a/interrupt.h b/interrupt.h index 2b5750e..f6e9fed 100644 --- a/interrupt.h +++ b/interrupt.h @@ -16,6 +16,7 @@ export volatile byte _ticks; export volatile byte _seconds; export volatile byte _minutes; export volatile byte _hours; +export volatile byte _irq_trigger; // interrupt routine called every 1/60th by the CPU after TMS9918 sets the /INT pin export __interrupt(hardware_all) void interrupt_handler() { @@ -30,6 +31,8 @@ export __interrupt(hardware_all) void interrupt_handler() { } } } + _irq_trigger = 1; // signals that an interrupt has been triggered + acknowledge_interrupt(); // acknowledge interrupt by reading the status register } @@ -40,3 +43,10 @@ void install_interrupt() { IRQ_JUMP_ADDRESS = (word) &interrupt_handler; // JUMP interrupt_handler asm { cli }; // re-enable 6502 interrupts } + +// waits until IRQ is triggered +void wait_interrupt() { + // _irq_trigger = 0; + // while(_irq_trigger == 0); // waits until it's set to 1 from the interrupt handler +} + diff --git a/test.c b/test.c index 85a2619..5fec598 100644 --- a/test.c +++ b/test.c @@ -2,18 +2,18 @@ #pragma encoding(ascii) // encode strings in plain ascii -#include "utils.h" -#include "apple1.h" -#include "tms9918.h" -#include "font8x8.h" -#include "tms_screen1.h" -#include "tms_screen2.h" -#include "interrupt.h" +// #include "utils.h" +// #include "apple1.h" +// #include "tms9918.h" +// #include "font8x8.h" +// #include "tms_screen1.h" +// #include "tms_screen2.h" +// #include "interrupt.h" -#include "demo_amiga_hand.h" -#include "demo_interrupt.h" -#include "demo_extvid.h" -#include "demo_blank.h" +// #include "demo_amiga_hand.h" +// #include "demo_interrupt.h" +// #include "demo_extvid.h" +// #include "demo_blank.h" void help() { woz_puts( diff --git a/tetris/bindump.js b/tetris/bindump.js new file mode 100644 index 0000000..7fc7f95 --- /dev/null +++ b/tetris/bindump.js @@ -0,0 +1,30 @@ +/* +const fs = require('fs'); + +let prg = fs.readFileSync("tetris_apple1.prg"); + +prg = prg.slice(2); + +fs.writeFileSync("tetris_apple1.bin",prg); + +console.log("bin written"); +*/ + +const fs = require('fs'); + +let code = fs.readFileSync("apple1_codeseg.bin"); +let data = fs.readFileSync("apple1_dataseg.bin"); + +let mem = new Uint8Array(65536).fill(0xAA); + +let code_address = 0x4000; +let data_address = 0x8000-0x7ff-0x280+1; + +for(let i=0;i // for memcpy() + +// #include "sprite.h" +// #include "pieces.h" + +#define BCOLS 10 // number of board columns +#define BROWS 20 // number of board rows +#define EMPTY NUMPIECES // the empty character is the character after all tetrominoes + +// TODO KickC doesn't allow double arrays[][] + +//byte board[32 /*BROWS*/ ][16 /*BCOLS*/]; // the board +byte board[16*BROWS]; + +#define BOARD_INDEX(x,y) (((int)(y))*16+((int)(x))) +#define WRITE_BOARD(x,y,c) board[BOARD_INDEX(x,y)]=(c) +#define READ_BOARD(x,y) board[BOARD_INDEX(x,y)] + +// prototypes +void ck_init(); +void ck_drawpiece(sprite *pl); +void ck_erasepiece(sprite *pl); +int collides(sprite *pl); +byte is_line_filled(byte line); +void ck_erase_line(byte line); +void ck_scroll_down(byte endline); + +// fills the check board with EMPTY +void ck_init() { + for(byte r=0;rpiece, pl->angle); + for(byte t=0; t<4; t++) { + int x = pl->x; byte x1 = data->offset_x; x+= (int) x1; + int y = pl->y; byte y1 = data->offset_y; y+= (int) y1; + /* + int x = pl->x + d->offset_x; + int y = pl->y + d->offset_y; + */ + WRITE_BOARD(x,y,pl->piece); + data++; + } +} + +// erase a piece from the check board +void ck_erasepiece(sprite *pl) { + tile_offset *data = get_piece_offsets(pl->piece, pl->angle); + for(byte t=0; t<4; t++) { + int x = pl->x; byte x1 = data->offset_x; x+= (int) x1; + int y = pl->y; byte y1 = data->offset_y; y+= (int) y1; + /* + int x = pl->x + (int) data->offset_x; + int y = pl->y + (int) data->offset_y; + */ + WRITE_BOARD(x,y,EMPTY); + data++; + } +} + +// returns 1 if the piece collides with something +int collides(sprite *pl) { + tile_offset *data = get_piece_offsets(pl->piece, pl->angle); + for(byte t=0; t<4; t++) { + int x = pl->x; byte x1 = data->offset_x; x+= (int) x1; + int y = pl->y; byte y1 = data->offset_y; y+= (int) y1; + + //int x = pl->x + (int) data->offset_x; + //int y = pl->y + (int) data->offset_y; + if(x<0) return 1; // does it collide with left border? + if(x>=BCOLS) return 1; // does it collide with right border? + if(y>=BROWS) return 1; // does it collide with bottom? + if(READ_BOARD(x,y) != EMPTY) return 1; // does it collide with something? + data++; + } + return 0; +} + +// returns 1 if the line is all filled +byte is_line_filled(byte line) { + for(byte t=0;t0;line--) { + for(byte x=0;x + +#define STARTBOARD_X 11 /* X start position of the board on the screen */ +#define STARTBOARD_Y 2 /* Y start position of the board on the screen */ + +#define BOARD_CHAR_LEFT 6 +#define BOARD_CHAR_RIGHT 6 + +//#define NCOLS 32 /* number of screen columns */ +//#define NROWS 24 /* number of screen rows, also board height */ + +#define CRUNCH_CHAR_1 13 +#define CRUNCH_COLOR_1 FG_BG(COLOR_BLACK, COLOR_GRAY) + +#define CRUNCH_CHAR_2 32 +#define CRUNCH_COLOR_2 FG_BG(COLOR_BLACK, COLOR_BLACK) + +#define POS_SCORE_X 23 +#define POS_SCORE_Y 1 + +#define POS_LEVEL_X 23 +#define POS_LEVEL_Y 7 + +#define POS_LINES_X 23 +#define POS_LINES_Y 13 + +#define POS_NEXT_X 1 +#define POS_NEXT_Y 1 + +#define NEXT_X (POS_NEXT_X+2) +#define NEXT_Y (POS_NEXT_Y+3) + +void bit_fx2(int sound) { + // throw not implemented +} + +void bit_fx3(int sound) { + // throw not implemented +} + +void updateScore(); +void drawPlayground(); +void gameOver(); + +void gr_drawpiece(sprite *p); +void gr_erasepiece(sprite *p); + +void gr_update_board(); +void gr_crunch_line(byte line, byte crunch_char, byte crunch_color); + +// grahpic board +// #include // for sprintf +// #include + +#include "keyboard_input.h" +#include "fonts.h" +#include "pieces.h" +#include "ckboard.h" + +extern unsigned long score; + +void right_pad_number(unsigned long number) { + ultoa(number, tmp, DECIMAL); + + int l = (int) strlen(tmp); + int offset = 6-l; + + for(int i=l;i>=0;i--) tmp[i+offset] = tmp[i]; // move to the right + for(int i=0;ipiece, p->angle); + int px = p->x; + int py = p->y; + + // are we erasing the "next" piece ? + if(py==PIECE_IS_NEXT) { + px = NEXT_X; + py = NEXT_Y; + } + else { + px += STARTBOARD_X; + py += STARTBOARD_Y; + } + + for(byte t=0; t<4; t++) { + int x = px + data->offset_x; + int y = py + data->offset_y; + data++; + gr4_tile((byte)x,(byte)y,EMPTY_GR_CHAR,EMPTY_GR_COLOR); + } +} + +// draw a piece on the screen +void gr_drawpiece(sprite *p) { + tile_offset *data = get_piece_offsets(p->piece, p->angle); + int px = p->x; + int py = p->y; + + // are we drawing the "next" piece ? + if(py==PIECE_IS_NEXT) { + px = NEXT_X; + py = NEXT_Y; + } + else { + px += STARTBOARD_X; + py += STARTBOARD_Y; + } + + byte piece = p->piece; + for(byte t=0; t<4; t++) { + int x = px; byte x1 = data->offset_x; x+= (int) x1; + int y = py; byte y1 = data->offset_y; y+= (int) y1; + data++; + /* + int x = px + data[t].offset_x; + int y = py + data[t].offset_y; + */ + byte ch = piece_chars[piece]; //piece_chars[p->piece]; + byte col = piece_colors[piece]; //piece_colors[p->piece]; + gr4_tile((byte)x,(byte)y,ch,col); + } +} + +// fills the specified line with an empty character +void gr_crunch_line(byte line, byte crunch_char, byte crunch_color) { + for(byte i=0; i // for rand() + +#include "keyboard_input.h" + +#pragma data_seg(Code) + +const byte *logo = + // 12345678901234567890123456789012 + "TTTTT EEEE XXXXX RRRR I SSS " + " T E X R R I S S" + " T E X R R I S " + " T EEE X RRRR I SSS " + " T E X R R I S" + " T E X R R I S S" + " T EEEE X R R I SSS "; + +#pragma data_seg(Data) + +void drawLogo() { + byte *s = logo; + for(byte r=0;r<7;r++) { + for(byte c=0;c<32;c++) { + byte tile = 0; + switch(*s++) { + case 'T': tile = 1; break; + case 'E': tile = 2; break; + case 'X': tile = 3; break; + case 'R': tile = 4; break; + case 'I': tile = 5; break; + case 'S': tile = 6; break; + } + if(tile) { + byte ch = piece_chars[tile]; + byte col = piece_colors[tile]; + gr4_tile(c,r+3,ch,col); + } + } + } +} + +// introduction screen +void introScreen() { + TMS_INIT(SCREEN2_TABLE); + SCREEN2_FILL(); + screen2_square_sprites(); + set_color(COLOR_BLACK); + + // simulate cls (TODO improve speed) + fillFrame(0, 0, 32, 24, 32, FG_BG(COLOR_BLACK, COLOR_BLACK)); + + drawLogo(); + + gr4_prints(3,13,"(C) 2021 ANTONINO PORCINO" , FG_BG(COLOR_LIGHT_YELLOW,COLOR_BLACK)); + gr4_prints(2,18,"USE ARROWS+SPACE OR JOYSTICK", FG_BG(COLOR_WHITE ,COLOR_BLACK)); + gr4_prints(5,20,"PRESS RETURN TO START" , FG_BG(COLOR_WHITE ,COLOR_BLACK)); + + // wait for key released + while(test_key(KEY_RETURN)); + + // wait for key press and do the coloured animation + while(!test_key(KEY_RETURN)) { + // TODO music + rand(); // extract random numbers, making rand() more "random" + } +} diff --git a/tetris/keyboard_input.h b/tetris/keyboard_input.h new file mode 100644 index 0000000..0ee6e71 --- /dev/null +++ b/tetris/keyboard_input.h @@ -0,0 +1,30 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +/* keyboard defititions */ +#define KEY_LEFT 'J' +#define KEY_RIGHT 'L' +#define KEY_DOWN 'K' +#define KEY_DROP ' ' +#define KEY_ROTATE 'I' +#define KEY_RETURN 0x0d + +byte test_key(byte key); +byte read_keyboard(); +byte player_input(); + +// test a specific key +byte test_key(byte key) { + return read_keyboard() == key ? 1 : 0; +} + +// reads the keyboard and return the key pressed +byte read_keyboard() { + return woz_readkey(); +} + +byte player_input() { + return read_keyboard(); +} + +#endif diff --git a/tetris/lib500_mock.h b/tetris/lib500_mock.h new file mode 100644 index 0000000..a15528e --- /dev/null +++ b/tetris/lib500_mock.h @@ -0,0 +1,15 @@ + + +void gr4_tile(byte x, byte y, byte ch, byte col) { + byte *source = &FONTS[(word)ch*8]; + word addr = x*8 + y*256; + set_vram_write_addr(SCREEN2_PATTERN_TABLE + addr); for(byte i=0;i<8;i++) { TMS_WRITE_DATA_PORT(source[i]); NOP; /*NOP;*/ /*NOP; NOP;*/ /*NOP; NOP; NOP; NOP;*/ } + set_vram_write_addr(SCREEN2_COLOR_TABLE + addr); for(byte i=0;i<8;i++) { TMS_WRITE_DATA_PORT(col); NOP; /*NOP;*/ /*NOP; NOP;*/ /*NOP; NOP; NOP; NOP;*/ } +} + +void gr4_prints(byte x, byte y, byte *s, byte color) { + byte c; + while(c=*s++) { + gr4_tile(x++, y, c, color); + } +} diff --git a/tetris/mk.bat b/tetris/mk.bat new file mode 100644 index 0000000..129e5a2 --- /dev/null +++ b/tetris/mk.bat @@ -0,0 +1,11 @@ +SET COMPILER=SDCC + +@IF %COMPILER%==ZCC zcc +laser500 tetris500.c -o tetris500.bin -I..\lib500 -create-app -Cz--audio --list -m -s -DZCC +@IF %COMPILER%==SDCC zcc +laser500 tetris500.c -o tetris500.bin -I..\lib500 -create-app -Cz--audio --list -m -s -DSDCC -compiler sdcc -SO3 + +@copy tetris500.bin ..\..\autoload.bin +@cd .. +@cd .. +@call node makeautoload +@cd software\tetris + diff --git a/tetris/mkfont.js b/tetris/mkfont.js new file mode 100644 index 0000000..ec39f5b --- /dev/null +++ b/tetris/mkfont.js @@ -0,0 +1,588 @@ +function encode(s) { + let b = 0; + for(let t=0;t test_apple1.woz +call node bindump + +@rem @del *.klog + +@del *.vs +@del *.dbg +@del tetris.prg +@del tetris_apple1.prg diff --git a/tetris/tetris.c b/tetris/tetris.c new file mode 100644 index 0000000..b9800c9 --- /dev/null +++ b/tetris/tetris.c @@ -0,0 +1,404 @@ +// TODO-KICKC: NPE when -t VIC20_8K is missing +// TODO-KICKC: NPE when array initializer contains unesisting macro +// TODO-KICKC: #include "keyboard.h" switches to c64 ? +// TODO-KICKC: conflict parameter if prototype has different parameter names +// TODO-KICKC: missing fragment: int x = pl->x + data[t].offset_x; oppure int x = pl->x + d->offset_x; + +// TODO-ALIGN: read board[] (avoid [], use pointer) +// TODO-ALIGN: ck_scroll_down() do not use memcpy, remove +// TODO-ALIGN: gr_drawpiece: byte piece = p->piece; +// TODO-ALIGN: gr_updateboard sfora se 255 +// TODO-ALIGN: grboard: remove CRUNCH_CHAR1 e 2, BACKGROUND +// TODO-ALIGN: score unsigned ? +// TODO-ALIGN: int generate_new_piece() +// TODO-ALIGN: unsigned int/byte all +// TODO-ALIGN: PIECE_IS_NEXT + +// TODO: solve division by counter_factor +// TODO: interrupt and wait_interrupt() +// TODO: better choice of colors for tiles +// TODO: better initial fill/cls + +// +// TETRIS for Apple1 + TMS9918 video card by P-LAB +// Written by Antonino Porcino, Dec 2021 +// nino.porcino@gmail.com +// +// This tetris version has been derived from my +// previous implementations for the Laser 310 and Laser 500 +// + +// standard libraries +#include // memset, memcopy (memcopy no longer necessary) +#include // for sprintf, rand + +#define COUNTER_MAX 3000 // the speed counter at level 0 +#define COUNTER_FACTOR 8 // speed decrease factor: speed -= speed / factor + +// TODO solve division by counter_factor + +// ERASED: #include +#define INLINE inline +#define FG_BG(f,b) (((f)<<4)|(b)) +#include "../utils.h" +#include "../apple1.h" +#include "../tms9918.h" +#include "../font8x8.h" +#include "../tms_screen1.h" +#include "../tms_screen2.h" +#include "../interrupt.h" + +// simulate the calls made in lib500 +#include "lib500_mock.h" + +#include "pieces.h" + +#define STYLE 3 + +#if STYLE == 1 + byte piece_chars[NUMPIECES] = { + 16, // L (orange in the original tetris) + 16, // J + 16, // T + 16, // I + 16, // O + 16, // S + 16, // Z + }; + byte piece_colors[NUMPIECES] = { + FG_BG( 0 , WHITE ), // L (orange in the original tetris) + FG_BG( 0 , VIOLET ), // J + FG_BG( 0 , LIGHT_MAGENTA ), // T + FG_BG( 0 , LIGHT_CYAN ), // I + FG_BG( 0 , YELLOW ), // O + FG_BG( 0 , LIGHT_GREEN ), // S + FG_BG( 0 , LIGHT_RED ) // Z + }; +#endif + +#if STYLE == 2 +byte piece_chars[NUMPIECES] = { + 0, // L (orange in the original tetris) + 1, // J + 2, // T + 3, // I + 4, // O + 15, // S + 14, // Z +}; +byte piece_colors[NUMPIECES] = { + FG_BG( WHITE , DARK_GREY ), // L (orange in the original tetris) + FG_BG( VIOLET , BLUE ), // J + FG_BG( LIGHT_MAGENTA , MAGENTA ), // T + FG_BG( LIGHT_CYAN , CYAN ), // I + FG_BG( YELLOW , BROWN ), // O + FG_BG( LIGHT_GREEN , GREEN ), // S + FG_BG( LIGHT_RED , RED ) // Z +}; +#endif + +#define EMPTY_GR_CHAR 32 +#define EMPTY_GR_COLOR FG_BG(COLOR_BLACK, COLOR_BLACK) + +#if STYLE == 3 +byte piece_chars[NUMPIECES+1] = { + 0, // L (orange in the original tetris) + 1, // J + 2, // T + 3, // I + 2, // O + 1, // S + 0, // Z + EMPTY_GR_CHAR // space +}; +byte piece_colors[NUMPIECES+1] = { + FG_BG( COLOR_GRAY , COLOR_WHITE ), // L (orange in the original tetris) + FG_BG( COLOR_LIGHT_BLUE , COLOR_DARK_BLUE ), // J + FG_BG( COLOR_MAGENTA , COLOR_WHITE ), // T + FG_BG( COLOR_CYAN , COLOR_LIGHT_BLUE ), // I + FG_BG( COLOR_LIGHT_YELLOW , COLOR_DARK_YELLOW ), // O + FG_BG( COLOR_LIGHT_GREEN , COLOR_DARK_GREEN ), // S + FG_BG( COLOR_LIGHT_RED , COLOR_DARK_RED ), // Z + EMPTY_GR_COLOR // empty character +}; +#endif + +void check_crunched_lines(); + +#define COLLIDES 1 +#define NOT_COLLIDES 0 + +#define PIECE_IS_NEXT 255 /* tells a piece is not on the board but on the "next" display */ + +#include "sprite.h" + +sprite piece_preview; // the "next" piece +sprite player; // the piece moved by the player +sprite new_pos; // new player position when making a 1-step move + +word drop_counter; // counter used to set the pace +word drop_counter_max; // maximum value of the counter + +unsigned long score; // player's score +unsigned int level; // level +unsigned int lines_remaining; // lines to complete the level +unsigned int total_lines; // total number of lines + +// game files +#include "pieces.h" +#include "ckboard.h" +#include "fonts.h" +#include "keyboard_input.h" +#include "grboard.h" +#include "intro.h" + +// simulates rand() % 7, since KickC does not support % 7 +// extracts a random number % 8 and repeats if it's not below < 7 +byte rand_modulo_7() { + for(;;) { + byte p = (byte) rand() % 8; + if(p!=7) { + return p; + } + } +} + +// generate a new piece after the last one can no longer move +// piece is taken from the "next" which in turn is generated randomly +// returns "COLLIDES" if a new piece can't be generated (the board is full) + +byte generate_new_piece() { + // move "next" piece onto the board + player.piece = piece_preview.piece; + player.angle = piece_preview.angle; + player.x = 4; + player.y = 0; + + // get a new "next" piece + gr_erasepiece(&piece_preview); + piece_preview.piece = (byte) rand_modulo_7(); // rand() % NUMPIECES; + piece_preview.angle = (byte) rand() % NUMROT; + gr_drawpiece(&piece_preview); + + if(collides(&player)) { + // new piece can't be drawn => game over + return COLLIDES; + } else { + // does not collide, draw it on the board + ck_drawpiece(&player); + gr_drawpiece(&player); + return NOT_COLLIDES; + } +} + +void handle_player_input() { + byte key = player_input(); + byte allowed = 0; + + if(key == 0) return; + + ck_erasepiece(&player); + + // calculate the new position + sprite_copy(&new_pos, &player); + + if(key == KEY_LEFT) { + new_pos.x--; + if(!collides(&new_pos)) allowed = 1; + } + else if(key == KEY_RIGHT) { + new_pos.x++; + if(!collides(&new_pos)) allowed = 1; + } + else if(key == KEY_ROTATE) { + new_pos.angle = (new_pos.angle + 1) & 3; + if(!collides(&new_pos)) allowed = 1; + } + else if(key == KEY_DOWN) { + drop_counter = drop_counter_max; + return; + } + else if(key == KEY_DROP) { + // animate the falling piece + while(1) { + ck_erasepiece(&player); + sprite_copy(&new_pos, &player); + new_pos.y++; + if(collides(&new_pos)) { + break; + } + gr_erasepiece(&player); + gr_drawpiece(&new_pos); + ck_drawpiece(&new_pos); + sprite_copy(&player,&new_pos); + } + drop_counter=drop_counter_max; // force an automatic drop + return; + } + + if(allowed == 1) { + gr_erasepiece(&player); + gr_drawpiece(&new_pos); + sprite_copy(&player, &new_pos); + } + ck_drawpiece(&player); +} + +// the main game loop, exits when GAME OVER +// if the speed counter reaches its max then the piece is automatically pushed down 1 position +// else lets the player move the piece with keyboard/joystick commands +void gameLoop() { + while(1) { + if(drop_counter++==drop_counter_max) { + // automatic drop down + drop_counter = 0; + + // erase from the check board in order to make the move + ck_erasepiece(&player); + + // calculate the new position (1 square down) + sprite_copy(&new_pos, &player); + new_pos.y++; + + if(collides(&new_pos)) { + // collides, redraw it again on the check board + ck_drawpiece(&player); + // check if lines to be crunched + check_crunched_lines(); + // generate a new piece if possible, otherwise exit to game over + if(generate_new_piece()==COLLIDES) return; + } + else { + // automatic drop does not collide, simply draw it + gr_erasepiece(&player); // erase and draw are as close as possible + gr_drawpiece(&new_pos); + ck_drawpiece(&new_pos); + sprite_copy(&player, &new_pos); // make player new pos + } + } + else { + handle_player_input(); + } + } +} + +unsigned int scores[5] = {0, 40, 100, 300, 1200}; // variable score on number of lines crunched + +byte lines_cruched[BROWS]; // stores which lines have been crunched + +// checks if player has made complete lines and "crunches" them +void check_crunched_lines() { + byte num_lines_crunched = 0; + + // mark completed lines + for(byte line=(BROWS-1);line>0;line--) { + byte filled = is_line_filled(line); + lines_cruched[line] = filled; + if(filled) { + ck_erase_line(line); + gr_crunch_line(line, CRUNCH_CHAR_1, CRUNCH_COLOR_1); + num_lines_crunched++; + } + } + + if(num_lines_crunched == 0) return; + + // wait 5 frames so the effect is visible + for(byte t=1; t<5; t++) { + wait_interrupt(); + } + + // assign score + + // does multiplication by repeat sums, as KickC does not support multiplication + unsigned int s=0; + for(byte t=0;t<(level+1);t++) s += scores[num_lines_crunched]; + score += (unsigned long) s; + + // score += scores[num_lines_crunched] * (level+1); + lines_remaining -= num_lines_crunched; + total_lines += num_lines_crunched; + + // advance level + if(lines_remaining <= 0) { + level = level + 1; + lines_remaining += 10; + drop_counter_max -= drop_counter_max/COUNTER_FACTOR; + // TODO effect when advancing level? + } + + // update score + updateScore(); + + // // marks the lines crunched with another character + for(byte line=0; line