add tetris

This commit is contained in:
nino-porcino 2021-12-14 18:40:01 +01:00
parent 28a911c706
commit 19a741cfa2
18 changed files with 2442 additions and 11 deletions

View File

@ -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
}

View File

@ -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
}

22
test.c
View File

@ -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(

30
tetris/bindump.js Normal file
View File

@ -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<code.length;i++) mem[code_address+i] = code[i];
for(let i=0;i<data.length;i++) mem[data_address+i] = data[i];
let prg = mem.slice(0x4000,0x8000);
fs.writeFileSync("tetris_apple1.bin", prg);
console.log("bin written");

112
tetris/ckboard.h Normal file
View File

@ -0,0 +1,112 @@
#ifndef CKBOARD_H
#define CKBOARD_H
// #include <string.h> // 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;r<BROWS;r++) {
ck_erase_line(r);
}
}
// draw a piece on the check board
void ck_drawpiece(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 + 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;t<BCOLS;t++) {
if(READ_BOARD(t,line)==EMPTY) return 0;
}
return 1;
}
// fills the specified line with an empty character
void ck_erase_line(byte line) {
for(byte t=0; t<BCOLS; t++) {
WRITE_BOARD(t,line,EMPTY);
}
}
// scroll down the board by 1 position from top to specified line
void ck_scroll_down(byte endline) {
for(byte line=endline;line>0;line--) {
for(byte x=0;x<BCOLS;x++) {
WRITE_BOARD(x,line,READ_BOARD(x,line-1));
}
}
// clears the top line
ck_erase_line(0);
}
#endif

768
tetris/fonts.body.h Normal file
View File

@ -0,0 +1,768 @@
0b11111111,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b11111111,
0b11111111,
0b10000001,
0b10111101,
0b10100101,
0b10100101,
0b10111101,
0b10000001,
0b11111111,
0b11111111,
0b10000001,
0b10111101,
0b10111101,
0b10111101,
0b10111101,
0b10000001,
0b11111111,
0b11111111,
0b10000001,
0b10000001,
0b10011001,
0b10011001,
0b10000001,
0b10000001,
0b11111111,
0b11111111,
0b10000001,
0b10100101,
0b10000001,
0b10000001,
0b10100101,
0b10000001,
0b11111111,
0b11111111,
0b11000011,
0b10100101,
0b10011001,
0b10011001,
0b10100101,
0b11000011,
0b11111111,
0b11111111,
0b00000100,
0b00000100,
0b00000100,
0b11111111,
0b01000000,
0b01000000,
0b01000000,
0b00000000,
0b00000000,
0b11111111,
0b00000000,
0b11111111,
0b11111111,
0b00000000,
0b00000000,
0b00101100,
0b00101100,
0b00101100,
0b00101100,
0b00101100,
0b00101100,
0b00101100,
0b00101100,
0b00000000,
0b00000000,
0b11110000,
0b00011000,
0b11001100,
0b11101100,
0b00101100,
0b00101100,
0b00000000,
0b00000000,
0b00001111,
0b00010000,
0b00100111,
0b00101111,
0b00101100,
0b00101100,
0b00101100,
0b01101100,
0b11001100,
0b00011100,
0b11111000,
0b11110000,
0b00000000,
0b00000000,
0b00101100,
0b00101110,
0b00100111,
0b00110000,
0b00011111,
0b00001111,
0b00000000,
0b00000000,
0b11111111,
0b11111111,
0b11000011,
0b11000011,
0b11000011,
0b11000011,
0b11111111,
0b11111111,
0b11111111,
0b10011001,
0b10100101,
0b11000011,
0b11000011,
0b10100101,
0b10011001,
0b11111111,
0b11111111,
0b10100101,
0b11100111,
0b10000001,
0b10000001,
0b11100111,
0b10100101,
0b11111111,
0b10000000,
0b10000000,
0b10000000,
0b10000000,
0b10000000,
0b10000000,
0b10000000,
0b11111111,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00011100,
0b00011100,
0b00011100,
0b00011000,
0b00011000,
0b00000000,
0b00011000,
0b00000000,
0b00110110,
0b00110110,
0b00100100,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00010011,
0b00110011,
0b01111110,
0b00110110,
0b00110111,
0b01111110,
0b00100100,
0b00000000,
0b00111100,
0b01101110,
0b01101000,
0b00111110,
0b00001011,
0b01101011,
0b00111110,
0b00000000,
0b00100001,
0b01010010,
0b00100100,
0b00001000,
0b00010010,
0b00100101,
0b01000010,
0b00000000,
0b00011000,
0b00100100,
0b00110100,
0b00111000,
0b01001101,
0b01000110,
0b00111001,
0b00000000,
0b00110000,
0b00110000,
0b00010000,
0b00100000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000100,
0b00001000,
0b00011000,
0b00010000,
0b00011000,
0b00001000,
0b00000100,
0b00000000,
0b00010000,
0b00001000,
0b00001100,
0b00000100,
0b00001100,
0b00001000,
0b00010000,
0b00000000,
0b00000000,
0b00010010,
0b00001100,
0b00111111,
0b00001100,
0b00010010,
0b00000000,
0b00000000,
0b00000000,
0b00001000,
0b00001000,
0b00111110,
0b00001000,
0b00001000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00011000,
0b00011000,
0b00110000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00111110,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00110000,
0b00110000,
0b00000000,
0b00000000,
0b00000010,
0b00000100,
0b00001000,
0b00010000,
0b00100000,
0b00000000,
0b00000000,
0b00011100,
0b00100110,
0b01100011,
0b01100011,
0b01100011,
0b00110010,
0b00011100,
0b00000000,
0b00001100,
0b00011100,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00111111,
0b00000000,
0b00111110,
0b01100011,
0b00000111,
0b00011110,
0b00111100,
0b01110000,
0b01111111,
0b00000000,
0b00111111,
0b00000110,
0b00001100,
0b00011110,
0b00000011,
0b01100011,
0b00111110,
0b00000000,
0b00001110,
0b00011110,
0b00110110,
0b01100110,
0b01111111,
0b00000110,
0b00000110,
0b00000000,
0b01111110,
0b01100000,
0b01111110,
0b00000011,
0b00000011,
0b01100011,
0b00111110,
0b00000000,
0b00011110,
0b00110000,
0b01100000,
0b01111110,
0b01100011,
0b01100011,
0b00111110,
0b00000000,
0b01111111,
0b00000011,
0b00000110,
0b00001100,
0b00011000,
0b00011000,
0b00011000,
0b00000000,
0b00111100,
0b01100010,
0b01110010,
0b00111100,
0b01001111,
0b01000011,
0b00111110,
0b00000000,
0b00111110,
0b01100011,
0b01100011,
0b00111111,
0b00000011,
0b00000110,
0b00111100,
0b00000000,
0b00000000,
0b00011000,
0b00011000,
0b00000000,
0b00011000,
0b00011000,
0b00000000,
0b00000000,
0b00000000,
0b00011000,
0b00011000,
0b00000000,
0b00011000,
0b00011000,
0b00110000,
0b00000000,
0b00000110,
0b00001100,
0b00011000,
0b00110000,
0b00011000,
0b00001100,
0b00000110,
0b00000000,
0b00000000,
0b00000000,
0b00111110,
0b00000000,
0b00111110,
0b00000000,
0b00000000,
0b00000000,
0b00110000,
0b00011000,
0b00001100,
0b00000110,
0b00001100,
0b00011000,
0b00110000,
0b00000000,
0b00111110,
0b01111111,
0b01100011,
0b00000110,
0b00011100,
0b00000000,
0b00011100,
0b00000000,
0b00111100,
0b01000010,
0b10011001,
0b10100001,
0b10100001,
0b10011001,
0b01000010,
0b00111100,
0b00011100,
0b00110110,
0b01100011,
0b01100011,
0b01111111,
0b01100011,
0b01100011,
0b00000000,
0b01111110,
0b01100011,
0b01100011,
0b01111110,
0b01100011,
0b01100011,
0b01111110,
0b00000000,
0b00011110,
0b00110011,
0b01100000,
0b01100000,
0b01100000,
0b00110011,
0b00011110,
0b00000000,
0b01111100,
0b01100110,
0b01100011,
0b01100011,
0b01100011,
0b01100110,
0b01111100,
0b00000000,
0b01111111,
0b01100000,
0b01100000,
0b01111110,
0b01100000,
0b01100000,
0b01111111,
0b00000000,
0b01111111,
0b01100000,
0b01100000,
0b01111110,
0b01100000,
0b01100000,
0b01100000,
0b00000000,
0b00011111,
0b00110000,
0b01100000,
0b01100111,
0b01100011,
0b00110011,
0b00011111,
0b00000000,
0b01100011,
0b01100011,
0b01100011,
0b01111111,
0b01100011,
0b01100011,
0b01100011,
0b00000000,
0b00111111,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00111111,
0b00000000,
0b00000011,
0b00000011,
0b00000011,
0b00000011,
0b00000011,
0b01100011,
0b00111110,
0b00000000,
0b01100011,
0b01100110,
0b01101100,
0b01111000,
0b01111100,
0b01101110,
0b01100111,
0b00000000,
0b00110000,
0b00110000,
0b00110000,
0b00110000,
0b00110000,
0b00110000,
0b00111111,
0b00000000,
0b01100011,
0b01110111,
0b01111111,
0b01101011,
0b01100011,
0b01100011,
0b01100011,
0b00000000,
0b01100011,
0b01110011,
0b01111011,
0b01111111,
0b01101111,
0b01100111,
0b01100011,
0b00000000,
0b00111110,
0b01100011,
0b01100011,
0b01100011,
0b01100011,
0b01100011,
0b00111110,
0b00000000,
0b01111110,
0b01100011,
0b01100011,
0b01100011,
0b01111110,
0b01100000,
0b01100000,
0b00000000,
0b00111110,
0b01100011,
0b01100011,
0b01100011,
0b01101111,
0b01100110,
0b00111101,
0b00000000,
0b01111110,
0b01100011,
0b01100011,
0b01100111,
0b01111100,
0b01101110,
0b01100111,
0b00000000,
0b00111100,
0b01100110,
0b01100000,
0b00111110,
0b00000011,
0b01100011,
0b00111110,
0b00000000,
0b00111111,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b00000000,
0b01100011,
0b01100011,
0b01100011,
0b01100011,
0b01100011,
0b01100011,
0b00111110,
0b00000000,
0b01100011,
0b01100011,
0b01100011,
0b01110111,
0b00111110,
0b00011100,
0b00001000,
0b00000000,
0b01100011,
0b01100011,
0b01100011,
0b01101011,
0b01111111,
0b01110111,
0b01100011,
0b00000000,
0b01100011,
0b01110111,
0b00111110,
0b00011100,
0b00111110,
0b01110111,
0b01100011,
0b00000000,
0b00110011,
0b00110011,
0b00110011,
0b00011110,
0b00001100,
0b00001100,
0b00001100,
0b00000000,
0b01111111,
0b00000111,
0b00001110,
0b00011100,
0b00111000,
0b01110000,
0b01111111,
0b00000000,
0b00011110,
0b00011000,
0b00011000,
0b00011000,
0b00011000,
0b00011000,
0b00011110,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00011110,
0b00000110,
0b00000110,
0b00000110,
0b00000110,
0b00000110,
0b00011110,
0b00000000,
0b00000000,
0b00011000,
0b00111100,
0b01111110,
0b00011000,
0b00011000,
0b00011000,
0b00011000,
0b00000000,
0b00010000,
0b00110000,
0b01111111,
0b01111111,
0b00110000,
0b00010000,
0b00000000,

14
tetris/fonts.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef FONTS_H
#define FONTS_H
#pragma data_seg(Code)
unsigned char FONTS[127*8] = {
#include "fonts.body.h"
};
#pragma data_seg(Data)
#endif

257
tetris/grboard.h Normal file
View File

@ -0,0 +1,257 @@
#include <stdlib.h>
#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 <stdio.h> // for sprintf
// #include <sound.h>
#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;i<offset;i++) tmp[i] = 32; // fill with spaces
}
// update score table
char tmp[32];
void updateScore() {
byte color = FG_BG(COLOR_WHITE,COLOR_BLACK);
right_pad_number(score);
gr4_prints(POS_SCORE_X+1,POS_SCORE_Y+2,tmp,color);
right_pad_number((unsigned long)total_lines);
gr4_prints(POS_LINES_X+1,POS_LINES_Y+2,tmp,color);
right_pad_number((unsigned long)level);
gr4_prints(POS_LEVEL_X+1,POS_LEVEL_Y+2,tmp,color);
}
#define FRAME_VERT 7
#define FRAME_HORIZ 8
#define FRAME_NW_CORNER 9
#define FRAME_NE_CORNER 10
#define FRAME_SW_CORNER 11
#define FRAME_SE_CORNER 12
void drawFrame(byte x, byte y, byte w, byte h, byte color) {
byte i;
for (i=1; i<w-1; i++) {
gr4_tile(x+i, y , FRAME_VERT, color);
gr4_tile(x+i, y+h-1, FRAME_VERT, color);
}
for (i=1; i<h-1; i++) {
gr4_tile(x , y+i, FRAME_HORIZ, color);
gr4_tile(x+w-1, y+i, FRAME_HORIZ, color);
}
gr4_tile(x ,y , FRAME_NE_CORNER, color);
gr4_tile(x+w-1 ,y , FRAME_NW_CORNER, color);
gr4_tile(x ,y+h-1, FRAME_SE_CORNER, color);
gr4_tile(x+w-1 ,y+h-1, FRAME_SW_CORNER, color);
}
void fillFrame(byte x, byte y, byte w, byte h, byte ch, byte color) {
byte i,j;
for (i=0; i<w; i++) {
for (j=0; j<h; j++) {
gr4_tile(x+i, y+j, ch, color);
}
}
}
// draws the board
void drawPlayground() {
set_color(COLOR_DARK_BLUE);
byte frame_color = FG_BG(COLOR_GRAY,COLOR_BLACK);
byte text_color = FG_BG(COLOR_LIGHT_YELLOW,COLOR_BLACK);
// draw tetris board
byte bg = FG_BG(COLOR_BLACK, COLOR_DARK_BLUE);
// tetris frame
fillFrame(0,0, 10,24, 16, bg);
fillFrame(STARTBOARD_X,STARTBOARD_Y,BCOLS,BROWS,32,FG_BG(COLOR_BLACK, COLOR_BLACK));
drawFrame(STARTBOARD_X-1,STARTBOARD_Y-1,BCOLS+2,BROWS+2, frame_color);
fillFrame(22,0,10,24, 16, bg);
fillFrame(0, 0,32, 1, 16, bg);
fillFrame(0,23,32, 1, 16, bg);
drawFrame(POS_SCORE_X, POS_SCORE_Y, 8, 4, frame_color);
drawFrame(POS_LEVEL_X, POS_LEVEL_Y, 8, 4, frame_color);
drawFrame(POS_LINES_X, POS_LINES_Y, 8, 4, frame_color);
drawFrame(POS_NEXT_X , POS_NEXT_Y , 8, 8, frame_color);
fillFrame(POS_NEXT_X+1 , POS_NEXT_Y+1 , 6, 6, 32, FG_BG(COLOR_BLACK, COLOR_BLACK));
gr4_prints(POS_SCORE_X+1, POS_SCORE_Y+1, "SCORE ", text_color);
gr4_prints(POS_LEVEL_X+1, POS_LEVEL_Y+1, "LEVEL ", text_color);
gr4_prints(POS_LINES_X+1, POS_LINES_Y+1,"LINES ", text_color);
gr4_prints(POS_NEXT_X +1, POS_NEXT_Y +1,"NEXT" , text_color);
}
// displays "game over" and waits for return key
void gameOver() {
byte color = FG_BG(COLOR_LIGHT_YELLOW, COLOR_BLACK);
byte frame_color = FG_BG(COLOR_GRAY,COLOR_BLACK);
byte y = (STARTBOARD_Y+BROWS)/2;
drawFrame(STARTBOARD_X-2, y-1,14,3, frame_color);
gr4_prints(STARTBOARD_X-1,y-0," GAME OVER ", FG_BG(COLOR_LIGHT_YELLOW,COLOR_BLACK));
// sound effect
bit_fx2(7);
// since it's game over, there's no next piece
gr_erasepiece(&piece_preview);
byte flip = 0;
while(1) {
flip++;
if(test_key(KEY_RETURN)) break;
if(flip < 60 ) color = FG_BG(COLOR_LIGHT_YELLOW ,COLOR_BLACK);
else if(flip < 120 ) color = FG_BG(COLOR_DARK_BLUE ,COLOR_BLACK);
else flip = 0;
gr4_prints(STARTBOARD_X,y-0,"GAME OVER", color);
}
while(test_key(KEY_RETURN));
}
// erase piece from the screen
void gr_erasepiece(sprite *p) {
tile_offset *data = get_piece_offsets(p->piece, 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<BCOLS; i++) {
gr4_tile(STARTBOARD_X+i, STARTBOARD_Y+line, crunch_char, crunch_color);
}
}
void gr_update_board() {
byte tile,ch,col;
for(byte line=0;line<BROWS;line++) {
for(byte column=0;column<BCOLS;column++) {
tile = READ_BOARD(column,line);
ch = piece_chars[tile];
col = piece_colors[tile];
gr4_tile(STARTBOARD_X+column, STARTBOARD_Y+line, ch, col);
}
}
}

65
tetris/intro.h Normal file
View File

@ -0,0 +1,65 @@
#include <stdlib.h> // 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"
}
}

30
tetris/keyboard_input.h Normal file
View File

@ -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

15
tetris/lib500_mock.h Normal file
View File

@ -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);
}
}

11
tetris/mk.bat Normal file
View File

@ -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

588
tetris/mkfont.js Normal file
View File

@ -0,0 +1,588 @@
function encode(s) {
let b = 0;
for(let t=0;t<s.length;t++) {
if(s.charAt(t) != ".") {
b |= 1<<t;
}
}
let msg = `00000000`+b.toString(2);
console.log(` 0b${msg.substr(msg.length-8)},`);
}
// char $0a.");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
// char $0aX");
encode("...XXX..");
encode("...XXX..");
encode("...XXX..");
encode("...XX...");
encode("...XX...");
encode("........");
encode("...XX...");
encode("........");
// char $0a2");
encode("..XX.XX.");
encode("..XX.XX.");
encode("..X..X..");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
// char $0a3");
encode("...X..XX");
encode("..XX..XX");
encode(".XXXXXX.");
encode("..XX.XX.");
encode("..XX.XXX");
encode(".XXXXXX.");
encode("..X..X..");
encode("........");
// char $0a4");
encode("..XXXX..");
encode(".XX.XXX.");
encode(".XX.X...");
encode("..XXXXX.");
encode("....X.XX");
encode(".XX.X.XX");
encode("..XXXXX.");
encode("........");
// char $0a5");
encode("..X....X");
encode(".X.X..X.");
encode("..X..X..");
encode("....X...");
encode("...X..X.");
encode("..X..X.X");
encode(".X....X.");
encode("........");
// char $0a6");
encode("...XX...");
encode("..X..X..");
encode("..XX.X..");
encode("..XXX...");
encode(".X..XX.X");
encode(".X...XX.");
encode("..XXX..X");
encode("........");
// char $0a7");
encode("..XX....");
encode("..XX....");
encode("...X....");
encode("..X.....");
encode("........");
encode("........");
encode("........");
encode("........");
// char $0a8");
encode(".....X..");
encode("....X...");
encode("...XX...");
encode("...X....");
encode("...XX...");
encode("....X...");
encode(".....X..");
encode("........");
// char $0a9");
encode("...X....");
encode("....X...");
encode("....XX..");
encode(".....X..");
encode("....XX..");
encode("....X...");
encode("...X....");
encode("........");
// char $0aa");
encode("........");
encode("...X..X.");
encode("....XX..");
encode("..XXXXXX");
encode("....XX..");
encode("...X..X.");
encode("........");
encode("........");
// char $0ab");
encode("........");
encode("....X...");
encode("....X...");
encode("..XXXXX.");
encode("....X...");
encode("....X...");
encode("........");
encode("........");
// char $0ac");
encode("........");
encode("........");
encode("........");
encode("........");
encode("...XX...");
encode("...XX...");
encode("..XX....");
encode("........");
// char $0ad");
encode("........");
encode("........");
encode("........");
encode("..XXXXX.");
encode("........");
encode("........");
encode("........");
encode("........");
// char $0ae");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("..XX....");
encode("..XX....");
encode("........");
// char $0af");
encode("........");
encode("......X.");
encode(".....X..");
encode("....X...");
encode("...X....");
encode("..X.....");
encode("........");
encode("........");
// char $0b.");
encode("...XXX..");
encode("..X..XX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode("..XX..X.");
encode("...XXX..");
encode("........");
// char $0bX");
encode("....XX..");
encode("...XXX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("..XXXXXX");
encode("........");
// char $0b2");
encode("..XXXXX.");
encode(".XX...XX");
encode(".....XXX");
encode("...XXXX.");
encode("..XXXX..");
encode(".XXX....");
encode(".XXXXXXX");
encode("........");
// char $0b3");
encode("..XXXXXX");
encode(".....XX.");
encode("....XX..");
encode("...XXXX.");
encode("......XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $0b4");
encode("....XXX.");
encode("...XXXX.");
encode("..XX.XX.");
encode(".XX..XX.");
encode(".XXXXXXX");
encode(".....XX.");
encode(".....XX.");
encode("........");
// char $0b5");
encode(".XXXXXX.");
encode(".XX.....");
encode(".XXXXXX.");
encode("......XX");
encode("......XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $0b6");
encode("...XXXX.");
encode("..XX....");
encode(".XX.....");
encode(".XXXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $0b7");
encode(".XXXXXXX");
encode("......XX");
encode(".....XX.");
encode("....XX..");
encode("...XX...");
encode("...XX...");
encode("...XX...");
encode("........");
// char $0b8");
encode("..XXXX..");
encode(".XX...X.");
encode(".XXX..X.");
encode("..XXXX..");
encode(".X..XXXX");
encode(".X....XX");
encode("..XXXXX.");
encode("........");
// char $0b9");
encode("..XXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode("..XXXXXX");
encode("......XX");
encode(".....XX.");
encode("..XXXX..");
encode("........");
// char $0ba");
encode("........");
encode("...XX...");
encode("...XX...");
encode("........");
encode("...XX...");
encode("...XX...");
encode("........");
encode("........");
// char $0bb");
encode("........");
encode("...XX...");
encode("...XX...");
encode("........");
encode("...XX...");
encode("...XX...");
encode("..XX....");
encode("........");
// char $0bc");
encode(".....XX.");
encode("....XX..");
encode("...XX...");
encode("..XX....");
encode("...XX...");
encode("....XX..");
encode(".....XX.");
encode("........");
// char $0bd");
encode("........");
encode("........");
encode("..XXXXX.");
encode("........");
encode("..XXXXX.");
encode("........");
encode("........");
encode("........");
// char $0be");
encode("..XX....");
encode("...XX...");
encode("....XX..");
encode(".....XX.");
encode("....XX..");
encode("...XX...");
encode("..XX....");
encode("........");
// char $0bf");
encode("..XXXXX.");
encode(".XXXXXXX");
encode(".XX...XX");
encode(".....XX.");
encode("...XXX..");
encode("........");
encode("...XXX..");
encode("........");
// char $08.");
encode("..XXXX..");
encode(".X....X.");
encode("X..XX..X");
encode("X.X....X");
encode("X.X....X");
encode("X..XX..X");
encode(".X....X.");
encode("..XXXX..");
// char $08X");
encode("...XXX..");
encode("..XX.XX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXXXXXX");
encode(".XX...XX");
encode(".XX...XX");
encode("........");
// char $082");
encode(".XXXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXXXXX.");
encode("........");
// char $083");
encode("...XXXX.");
encode("..XX..XX");
encode(".XX.....");
encode(".XX.....");
encode(".XX.....");
encode("..XX..XX");
encode("...XXXX.");
encode("........");
// char $084");
encode(".XXXXX..");
encode(".XX..XX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX..XX.");
encode(".XXXXX..");
encode("........");
// char $085");
encode(".XXXXXXX");
encode(".XX.....");
encode(".XX.....");
encode(".XXXXXX.");
encode(".XX.....");
encode(".XX.....");
encode(".XXXXXXX");
encode("........");
// char $086");
encode(".XXXXXXX");
encode(".XX.....");
encode(".XX.....");
encode(".XXXXXX.");
encode(".XX.....");
encode(".XX.....");
encode(".XX.....");
encode("........");
// char $087");
encode("...XXXXX");
encode("..XX....");
encode(".XX.....");
encode(".XX..XXX");
encode(".XX...XX");
encode("..XX..XX");
encode("...XXXXX");
encode("........");
// char $088");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXXXXXX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode("........");
// char $089");
encode("..XXXXXX");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("..XXXXXX");
encode("........");
// char $08a");
encode("......XX");
encode("......XX");
encode("......XX");
encode("......XX");
encode("......XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $08b");
encode(".XX...XX");
encode(".XX..XX.");
encode(".XX.XX..");
encode(".XXXX...");
encode(".XXXXX..");
encode(".XX.XXX.");
encode(".XX..XXX");
encode("........");
// char $08c");
encode("..XX....");
encode("..XX....");
encode("..XX....");
encode("..XX....");
encode("..XX....");
encode("..XX....");
encode("..XXXXXX");
encode("........");
// char $08d");
encode(".XX...XX");
encode(".XXX.XXX");
encode(".XXXXXXX");
encode(".XX.X.XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode("........");
// char $08e");
encode(".XX...XX");
encode(".XXX..XX");
encode(".XXXX.XX");
encode(".XXXXXXX");
encode(".XX.XXXX");
encode(".XX..XXX");
encode(".XX...XX");
encode("........");
// char $08f");
encode("..XXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $09.");
encode(".XXXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXXXXX.");
encode(".XX.....");
encode(".XX.....");
encode("........");
// char $09X");
encode("..XXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX.XXXX");
encode(".XX..XX.");
encode("..XXXX.X");
encode("........");
// char $092");
encode(".XXXXXX.");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX..XXX");
encode(".XXXXX..");
encode(".XX.XXX.");
encode(".XX..XXX");
encode("........");
// char $093");
encode("..XXXX..");
encode(".XX..XX.");
encode(".XX.....");
encode("..XXXXX.");
encode("......XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $094");
encode("..XXXXXX");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("........");
// char $095");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode("..XXXXX.");
encode("........");
// char $096");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XXX.XXX");
encode("..XXXXX.");
encode("...XXX..");
encode("....X...");
encode("........");
// char $097");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX...XX");
encode(".XX.X.XX");
encode(".XXXXXXX");
encode(".XXX.XXX");
encode(".XX...XX");
encode("........");
// char $098");
encode(".XX...XX");
encode(".XXX.XXX");
encode("..XXXXX.");
encode("...XXX..");
encode("..XXXXX.");
encode(".XXX.XXX");
encode(".XX...XX");
encode("........");
// char $099");
encode("..XX..XX");
encode("..XX..XX");
encode("..XX..XX");
encode("...XXXX.");
encode("....XX..");
encode("....XX..");
encode("....XX..");
encode("........");
// char $09a");
encode(".XXXXXXX");
encode(".....XXX");
encode("....XXX.");
encode("...XXX..");
encode("..XXX...");
encode(".XXX....");
encode(".XXXXXXX");
encode("........");
// char $09b");
encode("...XXXX.");
encode("...XX...");
encode("...XX...");
encode("...XX...");
encode("...XX...");
encode("...XX...");
encode("...XXXX.");
encode("........");
// char $09c");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
encode("........");
// char $09d");
encode("...XXXX.");
encode(".....XX.");
encode(".....XX.");
encode(".....XX.");
encode(".....XX.");
encode(".....XX.");
encode("...XXXX.");
encode("........");
// char $09e");
encode("........");
encode("...XX...");
encode("..XXXX..");
encode(".XXXXXX.");
encode("...XX...");
encode("...XX...");
encode("...XX...");
encode("...XX...");
// char $09f");
encode("........");
encode("...X....");
encode("..XX....");
encode(".XXXXXXX");
encode(".XXXXXXX");
encode("..XX....");
encode("...X....");
encode("........");

58
tetris/pieces.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef PIECES_H
#define PIECES_H
#include "types.h"
#define NUMPIECES 7 // number of tetrominos in tetris
#define NUMTILES 4 // number of tiles in a piece
#define NUMROT 4 // number of rotations of a piece
// offset of a single tile composing a piece
typedef struct {
byte offset_x;
byte offset_y;
} tile_offset;
tile_offset *get_piece_offsets(byte piece, byte angle);
#pragma data_seg(Code)
tile_offset pieces_XY[NUMPIECES*NUMTILES*NUMROT] = {
{+1,+1} , {+2,+1} , {+3,+1} , {+1,+2} , // L
{+2,+0} , {+2,+1} , {+2,+2} , {+3,+2} ,
{+3,+0} , {+1,+1} , {+2,+1} , {+3,+1} ,
{+1,+0} , {+2,+0} , {+2,+1} , {+2,+2} ,
{+1,+1} , {+2,+1} , {+3,+1} , {+3,+2} , // J
{+2,+0} , {+3,+0} , {+2,+1} , {+2,+2} ,
{+1,+0} , {+1,+1} , {+2,+1} , {+3,+1} ,
{+2,+0} , {+2,+1} , {+1,+2} , {+2,+2} ,
{+1,+0} , {+0,+1} , {+1,+1} , {+2,+1} , // T
{+1,+0} , {+0,+1} , {+1,+1} , {+1,+2} ,
{+0,+1} , {+1,+1} , {+2,+1} , {+1,+2} ,
{+1,+0} , {+1,+1} , {+2,+1} , {+1,+2} ,
{+0,+1} , {+1,+1} , {+2,+1} , {+3,+1} , // I
{+1,+0} , {+1,+1} , {+1,+2} , {+1,+3} ,
{+0,+1} , {+1,+1} , {+2,+1} , {+3,+1} ,
{+1,+0} , {+1,+1} , {+1,+2} , {+1,+3} ,
{+1,+0} , {+2,+0} , {+1,+1} , {+2,+1} , // O
{+1,+0} , {+2,+0} , {+1,+1} , {+2,+1} ,
{+1,+0} , {+2,+0} , {+1,+1} , {+2,+1} ,
{+1,+0} , {+2,+0} , {+1,+1} , {+2,+1} ,
{+2,+0} , {+3,+0} , {+1,+1} , {+2,+1} , // S
{+1,+0} , {+1,+1} , {+2,+1} , {+2,+2} ,
{+2,+0} , {+3,+0} , {+1,+1} , {+2,+1} ,
{+1,+0} , {+1,+1} , {+2,+1} , {+2,+2} ,
{+1,+0} , {+2,+0} , {+2,+1} , {+3,+1} , // Z
{+2,+0} , {+1,+1} , {+2,+1} , {+1,+2} ,
{+1,+0} , {+2,+0} , {+2,+1} , {+3,+1} ,
{+2,+0} , {+1,+1} , {+2,+1} , {+1,+2}
};
#pragma data_seg(Data)
// given a piece number and an angle returns the 4 byte "offsets" of the piece
tile_offset *get_piece_offsets(byte piece, byte angle) {
return &pieces_XY[(piece*sizeof(tile_offset)*4*2)+angle*4];
}
#endif

16
tetris/sprite.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef SPRITE_H
#define SPRITE_H
// tetromino sprite
typedef struct {
int x; // signed integers to allow negative board positions
int y; // -1 means piece is preview ("next")
byte piece;
byte angle;
} sprite;
INLINE void sprite_copy(sprite *to, sprite *from) {
memcpy(to, from, sizeof(sprite));
}
#endif

23
tetris/t.bat Normal file
View File

@ -0,0 +1,23 @@
@rem === BUILD TETRIS ===
@SET TARGET=tetris
@rem @echo ======================== VIC20 ===================================================
@rem call kickc -t VIC20_8K -D=VIC20 %TARGET%.c -o %TARGET%_vic20.prg -e
@rem copy %TARGET%.prg %TARGET%_vic20.prg
@echo ======================== APPLE 1 =================================================
call kickc -t apple1 -D=APPLE1 %TARGET%.c -o %TARGET%_apple1.prg -e -Xassembler="-symbolfile"
copy %TARGET%.prg %TARGET%_apple1.prg
@rem call node hexdump > test_apple1.woz
call node bindump
@rem @del *.klog
@del *.vs
@del *.dbg
@del tetris.prg
@del tetris_apple1.prg

404
tetris/tetris.c Normal file
View File

@ -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 <string.h>
// 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 <string.h> // memset, memcopy (memcopy no longer necessary)
#include <stdlib.h> // 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 <lib500.h>
#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<BROWS; line++) {
if(lines_cruched[line]) {
gr_crunch_line(line, CRUNCH_CHAR_2, CRUNCH_COLOR_2);
}
}
// wait 5 frames so the effect is visible
for(byte t=1; t<5; t++) {
wait_interrupt();
}
// compact the heap of tetrominos, removing empty lines
for(byte line=0; line<BROWS; line++) {
if(lines_cruched[line]) {
ck_scroll_down(line);
}
}
// update the screen
gr_update_board();
// sound effect
// ERASED: bit_fx3(0);
}
// initializes a new game
void initGame() {
level = 0;
score = 0;
total_lines = 0;
lines_remaining = 10;
drop_counter_max = COUNTER_MAX;
ck_init();
drawPlayground();
updateScore();
piece_preview.x = PIECE_IS_NEXT;
piece_preview.y = PIECE_IS_NEXT; // piece is on "next" display
// generate pieces twice: one for "next" and one for player
generate_new_piece();
ck_erasepiece(&player);
gr_erasepiece(&player);
generate_new_piece();
}
#ifdef APPLE1
#define LOWRAM_START 0x280
#define LOWRAM_END 0x7FF
#define LOWRAM_SIZE (LOWRAM_END - LOWRAM_START + 1)
#define DATAINCODE (0x8000 - LOWRAM_SIZE)
#endif
void main() {
// install the start-of-frame interrupt detection
//install_interrupt();
#ifdef APPLE1
// copy the initializaton data from ROM to lowram where "Data" segment is allocated
memcpy(LOWRAM_START, DATAINCODE, LOWRAM_SIZE);
#endif
while(1) {
introScreen();
initGame();
gameLoop();
gameOver();
}
}

9
tetris/types.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef TYPES_H
#define TYPES_H
// ERASED: typedef unsigned char byte;
// ERASED: typedef unsigned int word;
#endif