Merge pull request #1 from nippur72/main

Update
This commit is contained in:
Francesco Sblendorio 2021-12-16 14:45:06 +01:00 committed by GitHub
commit b8df1232f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 31923 additions and 670 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@
*.vs
*.dbg
*.asm
*.woz
*.bin
bug/*
cc65/*

View File

@ -2,13 +2,23 @@
Library and demos for the Apple-1 TMS9918 video card by P-LAB.
# USAGE (Windows)
- `env_kick.bat` sets KickC compiler path variable (run it once)
- `c.bat` compiles `test.c` for both VIC-20 and Apple-1
- `test_vic20.prg` runs on the "hybrid" VIC-20 emulator
- `node hexdump` puts `test_apple1.prg` into WOZMON format for the Apple-1
# Building for the "Juke Box" EPROM expansion
The demo contained in the repo are meant to be launched from the Apple-1 "Juke Box"
EPROM expansion at address $4000. The executables are built according to the
following memory map:
$0000-$00FF zero page: holds most of C program variables
$0280-$0FFF low RAM space: C program "Data" segment
$4000-$7581 EPROM (Juke Box): C program "Code" segment
$7582-$7FFF EPROM (Juke Box): C program "Data" segment (startup values)
The build script "mkeprom.js" creates a 16K binary file to be placed on the
EPROM where the "Data" segment startup values are put at the end of the file itself.
The user program need to manually copy such data from EPROM to low RAM once after
the `main()` is launched. That can be easily done by simply calling the library
function `apple1_eprom_init()`.
Note: Apple-1 start address is $4000, with TMS address range $C000-$C001

28
bug/bug1.c Normal file
View File

@ -0,0 +1,28 @@
#pragma zp_reserve(0,1,2)
volatile word tick_counts;
__interrupt(hardware_all) void interrupt_handler() {
tick_counts++;
}
void install_interrupt() {
*((word *)0x0001) = (word) &interrupt_handler;
}
void main() {
// make some use of "tick_counts"
*((word *)0x0001) = tick_counts;
install_interrupt();
}
/*
const byte *VDP_DATA = 0xA000; // TMS9918 data port (VRAM)
#define TMS_READ_DATA (*VDP_DATA);
void main() {
byte x = TMS_READ_DATA();
*((byte *)0xFFFF) = x;
}
*/

13
c.bat
View File

@ -1,13 +0,0 @@
rem call kickc -t asm6502 -D=APPLE1 bug1.c -o bug1.prg -e
rem goto uscita
@echo ======================== VIC20 ===================================================
call kickc -t VIC20 -D=VIC20 test.c -o test_vic20.prg -e
rem call kickc -vasmoptimize -vasmout -vcreate -vfragment -vliverange -vloop -vnonoptimize -voptimize -vparse -vsequence -vsizeinfo -vunroll -vuplift -t VIC20 -D=VIC20 test.c -o test_vic20.prg -e
copy test.prg test_vic20.prg
@echo ======================== APPLE 1 =================================================
call kickc -t asm6502 -D=APPLE1 test.c -o test_apple1.prg -e
copy test.prg test_apple1.prg
:uscita

55
demo/demo.c Normal file
View File

@ -0,0 +1,55 @@
// TODO make screen tables parametric (fixed values calculated by macros)
#include "../lib/utils.h"
#include "../lib/apple1.h"
#include "../lib/tms9918.h"
#include "../lib/font8x8.h"
#include "../lib/tms_screen1.h"
#include "../lib/tms_screen2.h"
#include "../lib/interrupt.h"
#include "demo_screen1.h"
#include "demo_screen2.h"
#include "demo_amiga_hand.h"
#include "demo_interrupt.h"
#include "demo_extvid.h"
#include "demo_blank.h"
void help() {
woz_puts(
"\rTMS9918 DEMOS\r"
"=============\r"
"1 SCREEN1\r"
"2 SCREEN2\r"
"A AMIGA HAND\r"
"I INTERRUPT\r"
"E FLIP EXT VIDEO\r"
"B BLANK ON/OFF\r"
"H HELP\r"
"0 EXITS\r\r"
);
}
void main() {
#ifdef APPLE1
apple1_eprom_init();
#endif
byte key = 'H';
for(;;) {
if(key == '1') prova_screen1();
else if(key == '2') prova_screen2();
else if(key == 'A') demo_amiga_hand();
else if(key == 'I') demo_interrupt();
else if(key == 'E') flip_external_input();
else if(key == 'B') flip_blank();
else if(key == 'H') help();
else if(key == '0') break;
else woz_putc(key);
key = apple1_getkey();
}
woz_puts("BYE\r");
woz_mon();
}

169
demo/demo_amiga_hand.h Normal file
View File

@ -0,0 +1,169 @@
byte amiga_data[612] = {
107,35,130,35,
130,35,130,57,
130,57,185,57,
185,57,185,35,
185,35,197,35,
197,35,208,46,
208,46,208,118,
208,118,199,118,
199,118,199,80,
199,80,136,80,
136,80,134,78,
134,78,125,78,
125,78,124,80,
124,80,117,80,
117,80,117,89,
117,89,107,96,
107,96,107,35,
105,96,105,34,
105,34,198,34,
198,34,210,46,
210,46,210,119,
210,119,158,119,
158,119,158,124,
158,124,154,132,
154,132,150,137,
150,137,146,140,
146,140,140,149,
140,149,132,153,
132,153,132,164,
132,164,82,164,
82,164,82,133,
82,133,81,133,
81,133,81,105,
81,105,85,99,
85,99,92,93,
92,93,93,89,
93,89,97,82,
97,82,102,79,
102,79,105,78,
105,78,105,78,
105,78,102,80,
102,80,98,82,
98,82,94,89,
94,89,93,93,
93,93,86,99,
86,99,82,105,
82,105,82,132,
82,132,83,133,
83,133,83,163,
83,163,112,163,
112,163,112,142,
112,142,120,142,
120,142,124,138,
124,138,124,119,
124,119,122,116,
122,116,122,106,
122,106,137,93,
137,93,137,81,
137,81,134,79,
134,79,131,79,
131,79,134,82,
134,82,134,89,
134,89,133,90,
133,90,126,90,
126,90,123,87,
123,87,114,94,
114,94,102,100,
102,100,90,109,
90,109,90,108,
90,108,102,99,
102,99,98,98,
98,98,95,95,
95,95,98,97,
98,97,102,98,
102,98,105,96,
122,85,124,78,
124,78,132,79,
132,79,133,82,
133,82,133,89,
133,89,126,89,
126,89,122,85,
123,116,123,106,
123,106,138,93,
138,93,138,81,
138,81,197,81,
197,81,197,118,
197,118,124,118,
124,118,123,114,
132,35,183,35,
183,35,183,56,
183,56,132,56,
132,56,132,35,
168,38,179,38,
179,38,179,52,
179,52,168,52,
168,52,168,38,
170,39,177,39,
177,39,177,51,
177,51,170,51,
170,51,170,39,
119,81,122,81,
122,81,122,83,
122,83,119,87,
119,87,119,81,
113,163,113,143,
113,143,120,143,
120,143,125,138,
125,138,125,129,
125,129,129,131,
129,131,137,131,
137,131,137,133,
137,133,141,138,
141,138,145,138,
145,138,145,140,
145,140,139,149,
139,149,131,152,
131,152,131,163,
131,163,113,163,
125,119,125,124,
125,124,130,121,
130,121,125,119,
129,122,125,124,
125,124,125,119,
125,119,146,119,
146,119,136,129,
136,129,135,129,
135,129,137,127,
137,127,132,122,
132,122,129,122,
150,119,157,119,
157,119,157,124,
157,124,153,132,
153,132,150,136,
150,136,145,137,
145,137,142,137,
142,137,139,133,
139,133,139,130,
139,130,150,119,
140,130,140,133,
140,133,142,136,
142,136,144,136,
144,136,148,135,
148,135,149,132,
149,132,145,129,
145,129,140,130,
126,128,134,130,
134,130,136,127,
136,127,132,123,
132,123,129,123,
129,123,126,125,
126,125,126,128,
198,35,209,46,
209,46,209,118
};
void demo_amiga_hand() {
tms_init_regs(SCREEN2_TABLE);
screen2_init_bitmap(FG_BG(COLOR_BLACK,COLOR_WHITE));
screen2_puts(0, 0, FG_BG(COLOR_BLACK,COLOR_WHITE), "*** P-LAB VIDEO CARD SYSTEM ***");
screen2_puts(0, 2, FG_BG(COLOR_BLACK,COLOR_WHITE), "16K VRAM BYTES FREE");
screen2_puts(0, 4, FG_BG(COLOR_BLACK,COLOR_WHITE), "READY.");
for(word p=0;p<612;p+=4) {
vti_line(amiga_data[p],amiga_data[p+1],amiga_data[p+2],amiga_data[p+3]);
}
screen2_puts(18, 12, FG_BG(COLOR_DARK_BLUE, COLOR_WHITE), "APPLE1");
}

12
demo/demo_blank.h Normal file
View File

@ -0,0 +1,12 @@
// TODO make it static once KickC bug is fixed
byte blank = BLANK_OFF;
void flip_blank() {
blank ^= 1;
if(blank == BLANK_OFF) woz_puts("NORMAL\r");
else woz_puts("BLANK\r");
// write "blank" bit
tms_set_blank(blank);
}

19
demo/demo_extvid.h Normal file
View File

@ -0,0 +1,19 @@
// TODO make it static once KickC bug is fixed
byte external_input = 0;
void flip_external_input() {
external_input ^= 1;
if(external_input) woz_puts("EXT INPUT ON\r");
else woz_puts("EXT INPUT OFF\r");
// fill color table with transparent color so that external input can be seen
tms_set_vram_write_addr(SCREEN1_COLOR_TABLE);
for(byte i=32;i!=0;i--) {
TMS_WRITE_DATA_PORT(FG_BG(COLOR_DARK_YELLOW, COLOR_TRANSPARENT));
}
tms_set_external_video(external_input); // write "external video input" bit
tms_set_color(0); // transparent color
}

44
demo/demo_interrupt.h Normal file
View File

@ -0,0 +1,44 @@
// TODO make it static once KickC bug is fixed
byte last_seconds = 0;
void demo_interrupt() {
// resets the watch to 00:00:00.0
_ticks = 0;
_seconds = 0;
_minutes = 0;
_hours = 0;
// installs the interrupt handler routine
install_interrupt();
// enables interrupts on the TMS9918
tms_set_interrupt_bit(INTERRUPT_ENABLED);
woz_puts("INTERRUPT INSTALLED\r");
woz_puts("0 TURNS OFF\r");
woz_puts("1 TURNS ON\r");
woz_puts("E EXIT TO MAIN MENU\r");
for(;;) {
if(apple1_iskeypressed()) {
byte k = apple1_getkey();
if(k=='1') { tms_set_interrupt_bit(INTERRUPT_ENABLED); woz_puts("INT ENABLED\r"); }
else if(k=='0') { tms_set_interrupt_bit(INTERRUPT_DISABLED); woz_puts("INT DISABLED\r"); }
else if(k=='E') break;
}
if(last_seconds != _seconds) {
woz_print_hex(_hours); woz_putc(':');
woz_print_hex(_minutes); woz_putc(':');
woz_print_hex(_seconds); woz_putc('.');
woz_print_hex(_ticks); woz_putc('\r');
last_seconds = _seconds;
}
}
// disables interrupts on the TMS9918
tms_set_interrupt_bit(INTERRUPT_DISABLED);
woz_puts("INTERRUPT STOPPED\r");
}

32
demo/demo_screen1.h Normal file
View File

@ -0,0 +1,32 @@
void screen1_square_sprites() {
// fills first sprite pattern with 255
tms_set_vram_write_addr(SCREEN1_SPRITE_PATTERNS); // start writing in the sprite patterns
for(byte i=0;i<8;i++) {
TMS_WRITE_DATA_PORT(255);
}
// set sprite coordinates
tms_set_vram_write_addr(SCREEN1_SPRITE_ATTRS); // start writing in the sprite attribute
for(byte i=0;i<32;i++) {
TMS_WRITE_DATA_PORT((6+i)*8); NOP; NOP; NOP; NOP; // y coordinate
TMS_WRITE_DATA_PORT((6+i)*8); NOP; NOP; NOP; NOP; // x coordinate
TMS_WRITE_DATA_PORT(0); NOP; NOP; NOP; NOP; // name
TMS_WRITE_DATA_PORT(i); NOP; NOP; NOP; NOP; // color
}
}
void prova_screen1() {
tms_init_regs(SCREEN1_TABLE);
screen1_prepare();
screen1_load_font();
screen1_home(); screen1_puts("*** P-LAB VIDEO CARD SYSTEM ***");
screen1_locate(0, 2); screen1_puts("16K VRAM BYTES FREE");
screen1_locate(0, 4); screen1_puts("READY.");
screen1_locate(0, 10);
for(word i=0;i<256;i++) screen1_putc((byte)i);
screen1_square_sprites();
}

32
demo/demo_screen2.h Normal file
View File

@ -0,0 +1,32 @@
void prova_screen2() {
tms_init_regs(SCREEN2_TABLE);
byte text_color = FG_BG(COLOR_BLACK,COLOR_WHITE);
screen2_init_bitmap(text_color);
screen2_puts(0, 0, text_color, "*** P-LAB VIDEO CARD SYSTEM ***");
screen2_puts(0, 2, text_color, "16K VRAM BYTES FREE");
screen2_puts(0, 4, text_color, "READY.");
// display all colors in the palette
for(byte i=0;i<16;i++) {
screen2_puts(5,(byte)(6+i),(byte)(((15-i)<<4)+i)," SCREEN 2 ");
}
vti_line(18, 45,232,187);
vti_line(18,187,232, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_RESET;
vti_line(18+5, 45,232+5,187);
vti_line(18+5,187,232+5, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_INVERT;
vti_line(18+5+5, 45,232+5+5,187);
vti_line(18+5+5,187,232+5+5, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_SET;
//vti_ellipse_rect(7,9,202,167);
}

2
demo/m.bat Normal file
View File

@ -0,0 +1,2 @@
@call ..\tools\build demo

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
path %path%;"c:\Program Files\Java\jdk1.8.0_161\bin"
path %path%;C:\Users\Nino1\Desktop\kickc\bin
path %path%;C:\Users\Nino1\Desktop\USB\kickc\bin

6
kickc/apple1.ld Normal file
View File

@ -0,0 +1,6 @@
// Apple 1
.file [name="%O", type="prg", segments="Program"]
.segmentdef Program [segments="Code, Data"]
.segmentdef Code [start=%P,outBin="apple1_codeseg.bin"]
.segmentdef Data [start=$0280,min=$0280,max=$0fff,outBin="apple1_dataseg.bin"]

11
kickc/apple1.tgt Normal file
View File

@ -0,0 +1,11 @@
{
"extension": "prg",
"link": "apple1.ld",
"start_address": "0x2000",
"cpu": "MOS6502X",
"interrupt": "hardware_all",
"emulator": "x64sc"
}

8
kickc/vic20_8k.ld Normal file
View File

@ -0,0 +1,8 @@
// Commodore VIC 20 8k executable PRG file
.file [name="%O", type="prg", segments="Program"]
.segmentdef Program [segments="Basic, Code, Data"]
.segmentdef Basic [start=$1201]
.segmentdef Code [start=%P]
.segmentdef Data [startAfter="Code"]
.segment Basic
:BasicUpstart(%E)

12
kickc/vic20_8k.tgt Normal file
View File

@ -0,0 +1,12 @@
{
"description": "Commodore VIC 20 8K executable PRG file",
"extension": "prg",
"link": "vic20_8k.ld",
"start_address": "0x120d",
"cpu": "MOS6502X",
"interrupt": "rom_min_vic20",
"emulator": "xvic",
"defines": {
"__VIC20__": 1
}
}

122
lib/apple1.h Normal file
View File

@ -0,0 +1,122 @@
#ifdef APPLE1
// APPLE1
#pragma start_address(0x4000)
const word WOZMON = 0xFF1F; // enters monitor
const word ECHO = 0xFFEF; // output ascii character in A (A not destroyed)
const word PRBYTE = 0xFFDC; // print hex byte in A (A destroyed)
const word KEY_DATA = 0xd010; // read key
const word KEY_CTRL = 0xd011; // control port
const word TERM_DATA = 0xd012; // write ascii
const word TERM_CTRL = 0xd013; // control port
#else
// VIC20
const word ECHO = 0xFFD2; // chrout routine in kernal rom
const word GETIN = 0xFFE4; // GETIN keyboard read routine
#endif
// prints a hex byte using the WOZMON routine
void woz_print_hex(byte c) {
#ifdef APPLE1
asm {
lda c
jsr PRBYTE
};
#else
asm {
lda c
jsr ECHO
};
#endif
}
// puts a character on the apple1 screen using the WOZMON routine
void woz_putc(byte c) {
asm {
lda c
jsr ECHO
}
}
// puts a 0-terminated string on the apple1 screen
void woz_puts(byte *s) {
byte c;
while(c=*s++) woz_putc(c);
}
// returns to WOZMON prompt
void woz_mon() {
#ifdef APPLE1
asm {
jmp WOZMON
}
#endif
}
// returns nonzero if a key has been pressed
inline byte apple1_iskeypressed() {
#ifdef APPLE1
return PEEK(KEY_CTRL) & 0x80;
#else
return 0;
#endif
}
// blocking keyboard read
// reads a key from the apple-1 keyboard
byte apple1_getkey() {
#ifdef APPLE1
asm {
__wait:
lda KEY_CTRL
bpl __wait
}
return PEEK(KEY_DATA) & 0x7f;
#else
byte key;
byte const *keyptr = &key;
kickasm(uses keyptr, uses GETIN) {{
__wait:
jsr GETIN
cmp #0
beq __wait
sta keyptr
}}
return key;
#endif
}
// non blocking keyboard read
// reads a key and return 0 if no key is pressed
byte apple1_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
}
#ifdef APPLE1
#include <stdlib.h> // for memcpy
#define LOWRAM_START 0x280
#define LOWRAM_END 0x7FF
#define LOWRAM_SIZE (LOWRAM_END - LOWRAM_START + 1)
#define DATAINCODE (0x8000 - LOWRAM_SIZE)
inline void apple1_eprom_init() {
// copy the initializaton data from ROM to lowram where "Data" segment is allocated
memcpy(LOWRAM_START, DATAINCODE, LOWRAM_SIZE);
}
#endif

View File

@ -1,3 +1,7 @@
// font from LASER-500 (ASCII characters only)
#pragma data_seg(Code)
byte FONT[768] = {
0
, 0
@ -854,3 +858,5 @@ byte FONT[768] = {
, 0
, 0
};
#pragma data_seg(Data)

52
lib/interrupt.h Normal file
View File

@ -0,0 +1,52 @@
// reserve locations 0,1,2 used by the APPLE1 IRQ jump vector
// these will contain a three byte instruction "JUMP <interrupt_handler>"
// in order to make the interrupt routine not reside in zero page
#pragma zp_reserve(0,1,2)
// acknowledge the interrupt by reading the status register on the TMS9918 (see manual section 2.3.1)
inline void acknowledge_interrupt() {
asm { lda VDP_REG };
}
export __address(0) byte IRQ_JUMP_OPCODE; // location 0 contains the opcode for "JMP"
export __address(1) word IRQ_JUMP_ADDRESS; // location 1 and 2 contain the jump address
// storage for a simple watch timer
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() {
// update the watch
if(++_ticks == 60) {
_ticks = 0;
if(++_seconds == 60) {
_seconds = 0;
if(++_minutes == 60) {
_minutes = 0;
_hours++;
}
}
}
_irq_trigger = 1; // signals that an interrupt has been triggered
acknowledge_interrupt(); // acknowledge interrupt by reading the status register
}
// safely installs the interrupt routine
void install_interrupt() {
asm { sei }; // disable 6502 interrupts
IRQ_JUMP_OPCODE = 0x4C; // $4C = JUMP opcode
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
}

124
lib/tms9918.h Normal file
View File

@ -0,0 +1,124 @@
#pragma encoding(ascii) // encode strings in plain ascii
#ifdef APPLE1
const byte *VDP_DATA = 0xCC00; // TMS9918 data port (VRAM)
const byte *VDP_REG = 0xCC01; // TMS9918 register port (write) or status (read)
#else
const byte *VDP_DATA = 0xA000; // TMS9918 data port (VRAM)
const byte *VDP_REG = 0xA001; // TMS9918 register port (write) or status (read)
#endif
// control port bits
const byte WRITE_TO_VRAM = 0b01000000; // write to VRAM command
const byte READ_FROM_VRAM = 0b00000000; // read from VRAM command
const byte HIADDRESS_MASK = 0b00111111; // bit mask for the high byte of the address
const byte WRITE_TO_REG = 0b10000000; // write to register command
const byte REGNUM_MASK = 0b00000111; // bit mask for register number (0-7)
// register 0 masks
const byte REG0_M3_MASK = 0b00000010;
const byte REG0_EXTVID_MASK = 0b00000001;
// register 1 masks
const byte REG1_16K_MASK = 0b10000000;
const byte REG1_BLANK_MASK = 0b01000000;
const byte REG1_IE_MASK = 0b00100000;
const byte REG1_M1M2_MASK = 0b00011000;
const byte REG1_UNUSED_MASK = 0b00000100;
const byte REG1_SIZE_MASK = 0b00000010;
const byte REG1_MAG_MASK = 0b00000001;
// TMS9918 color palette
const byte COLOR_TRANSPARENT = 0x0;
const byte COLOR_BLACK = 0x1;
const byte COLOR_MEDIUM_GREEN = 0x2;
const byte COLOR_LIGHT_GREEN = 0x3;
const byte COLOR_DARK_BLUE = 0x4;
const byte COLOR_LIGHT_BLUE = 0x5;
const byte COLOR_DARK_RED = 0x6;
const byte COLOR_CYAN = 0x7;
const byte COLOR_MEDIUM_RED = 0x8;
const byte COLOR_LIGHT_RED = 0x9;
const byte COLOR_DARK_YELLOW = 0xA;
const byte COLOR_LIGHT_YELLOW = 0xB;
const byte COLOR_DARK_GREEN = 0xC;
const byte COLOR_MAGENTA = 0xD;
const byte COLOR_GREY = 0xE;
const byte COLOR_WHITE = 0xF;
// macro for combining foreground and background into a single byte value
#define FG_BG(f,b) (((f)<<4)|(b))
// status register bits (read only)
#define FRAME_BIT(a) ((a) & 0b10000000)
#define FIVESPR_BIT(a) ((a) & 0b01000000)
#define COLLISION_BIT(a) ((a) & 0b00100000)
#define SPRITE_NUM(a) ((a) & 0b00011111)
// read/write to TMS9918 macros
#define TMS_WRITE_CTRL_PORT(a) (*VDP_REG=(byte)(a))
#define TMS_WRITE_DATA_PORT(a) (*VDP_DATA=(byte)(a))
#define TMS_READ_CTRL_PORT (*VDP_REG);
#define TMS_READ_DATA_PORT (*VDP_DATA);
// sets the VRAM address on the TMS9918 for a write operation
void tms_set_vram_write_addr(word addr) {
TMS_WRITE_CTRL_PORT(LOBYTE(addr));
TMS_WRITE_CTRL_PORT((HIBYTE(addr) & HIADDRESS_MASK)|WRITE_TO_VRAM);
}
// sets the VRAM address on the TMS9918 for a read operation
void tms_set_vram_read_addr(word addr) {
TMS_WRITE_CTRL_PORT(LOBYTE(addr));
TMS_WRITE_CTRL_PORT((HIBYTE(addr) & HIADDRESS_MASK)|READ_FROM_VRAM);
}
// buffer containing the last register values, because TMS registers are write only
byte tms_regs_latch[8];
// writes a value to a TMS9918 register (0-7)
void tms_write_reg(byte regnum, byte val) {
TMS_WRITE_CTRL_PORT(val);
TMS_WRITE_CTRL_PORT((regnum & REGNUM_MASK)|WRITE_TO_REG);
tms_regs_latch[regnum] = val; // save value to buffer
}
// sets border color and background for mode 0
inline void tms_set_color(byte col) {
tms_write_reg(7, col);
}
// initialize all registers from a table
void tms_init_regs(byte *table) {
for(byte i=0;i<8;i++) {
tms_write_reg(i, table[i]);
}
}
const byte INTERRUPT_ENABLED = 1;
const byte INTERRUPT_DISABLED = 0;
// sets the interrupt enable bit on register 1
void tms_set_interrupt_bit(byte val) {
byte regvalue = tms_regs_latch[1] & (~REG1_IE_MASK);
if(val) regvalue |= REG1_IE_MASK;
tms_write_reg(1, regvalue);
}
const byte BLANK_ON = 0;
const byte BLANK_OFF = 1;
// sets the blank bit on register 1
void tms_set_blank(byte val) {
byte regvalue = tms_regs_latch[1] & (~REG1_BLANK_MASK);
if(val) regvalue |= REG1_BLANK_MASK;
tms_write_reg(1, regvalue);
}
// sets the external video input bit on register 0
void tms_set_external_video(byte val) {
byte regvalue = tms_regs_latch[0] & (~REG0_EXTVID_MASK);
if(val) regvalue |= REG0_EXTVID_MASK;
tms_write_reg(0, regvalue);
}

86
lib/tms_screen1.h Normal file
View File

@ -0,0 +1,86 @@
byte SCREEN1_TABLE[8] = {
0x00, 0xc0, 0x05, 0x80, 0x01, 0x20, 0x00, 0x25
};
// SCREEN 1 VALUES
// sprite patterns: $0000
// pattern table: $0800 (256*8)
// sprite attributes: $1000
// unused: $1080
// name table: $1400 (32*24)
// unused: $1800
// color table: $2000 (32)
// unused $2020-$3FFF
const word SCREEN1_PATTERN_TABLE = 0x0800;
const word SCREEN1_NAME_TABLE = 0x1400;
const word SCREEN1_COLOR_TABLE = 0x2000;
const word SCREEN1_SPRITE_PATTERNS = 0x0000;
const word SCREEN1_SPRITE_ATTRS = 0x1000;
const word SCREEN1_SIZE = (32*24);
// loads the font on the pattern table
void screen1_load_font() {
byte *source = FONT;
// start writing into VRAM from space character (32..127)
tms_set_vram_write_addr(SCREEN1_PATTERN_TABLE+(32*8));
for(word i=768;i!=0;i--) {
TMS_WRITE_DATA_PORT(*source++);
}
// reverse font (32..127)
source = FONT;
tms_set_vram_write_addr(SCREEN1_PATTERN_TABLE+((128+32)*8));
for(word i=768;i!=0;i--) {
TMS_WRITE_DATA_PORT(~(*source++));
}
}
// holds the cursor location for the putchar routine
word screen1_cursor;
// prints character to TMS (SCREEN 1 MODE)
void screen1_putc(byte c) {
tms_set_vram_write_addr(screen1_cursor++);
TMS_WRITE_DATA_PORT(c);
}
// prints a 0 terminated string to TMS (SCREEN 1 MODE)
void screen1_puts(byte *s) {
byte c;
while(c=*s++) {
screen1_putc(c);
}
}
void screen1_home() {
screen1_cursor = SCREEN1_NAME_TABLE;
}
void screen1_locate(byte x, byte y) {
screen1_cursor = SCREEN1_NAME_TABLE + ((word)y)*32 + x;
}
void screen1_prepare() {
// fills name table with spaces (32)
tms_set_vram_write_addr(SCREEN1_NAME_TABLE);
for(word i=SCREEN1_SIZE;i!=0;i--) {
TMS_WRITE_DATA_PORT(32);
}
// fill pattern table with 0
tms_set_vram_write_addr(SCREEN1_PATTERN_TABLE);
for(word i=256*8;i!=0;i--) {
TMS_WRITE_DATA_PORT(0);
}
// fill color table with black on white
tms_set_vram_write_addr(SCREEN1_COLOR_TABLE);
for(byte i=32;i!=0;i--) {
TMS_WRITE_DATA_PORT(FG_BG(COLOR_BLACK,COLOR_WHITE));
}
}

170
lib/tms_screen2.h Normal file
View File

@ -0,0 +1,170 @@
byte SCREEN2_TABLE[8] = {
0x02, 0xc0, 0x0e, 0xff, 0x03, 0x76, 0x03, 0x25
};
// SCREEN 2 VALUES
// pattern table: $0000-$17FF (256*8*3)
// sprite patterns: $1800-$19FF
// color table: $2000-$27FF (256*8*3)
// name table: $3800-$3AFF (32*24 = 256*3 = 768)
// sprite attributes: $3B00-$3BFF
// unused $3C00-$3FFF
//
const word SCREEN2_PATTERN_TABLE = 0x0000;
const word SCREEN2_NAME_TABLE = 0x3800;
const word SCREEN2_COLOR_TABLE = 0x2000;
const word SCREEN2_SPRITE_PATTERNS = 0x1800;
const word SCREEN2_SPRITE_ATTRS = 0x3b00;
const word SCREEN2_SIZE = (32*24);
// prepare the screen 2 to be used as a bitmap
void screen2_init_bitmap(byte color) {
// erase the first sprite pattern
tms_set_vram_write_addr(SCREEN2_SPRITE_PATTERNS); // start writing in the sprite patterns
for(byte i=0;i<8;i++) {
TMS_WRITE_DATA_PORT(0); NOP;
}
// set all sprite coordinates to 0
tms_set_vram_write_addr(SCREEN2_SPRITE_ATTRS); // start writing in the sprite attribute
for(byte i=0;i<32;i++) {
TMS_WRITE_DATA_PORT(0); NOP; // y coordinate
TMS_WRITE_DATA_PORT(0); NOP; // x coordinate
TMS_WRITE_DATA_PORT(0); NOP; // name
TMS_WRITE_DATA_PORT(i); NOP; // color
}
// fill pattern table with 0 (clear screen)
tms_set_vram_write_addr(SCREEN2_PATTERN_TABLE);
for(word i=768*8;i!=0;i--) {
TMS_WRITE_DATA_PORT(0);
NOP;
}
// fill color table with black on white
tms_set_vram_write_addr(SCREEN2_COLOR_TABLE);
for(word i=768*8;i!=0;i--) {
TMS_WRITE_DATA_PORT(color);
NOP;
}
// fills name table x3 with increasing numbers
tms_set_vram_write_addr(SCREEN2_NAME_TABLE);
for(word i=0;i<SCREEN2_SIZE;i++) {
TMS_WRITE_DATA_PORT(i & 0xFF);
NOP;
}
}
void screen2_putc(byte ch, byte x, byte y, byte col) {
byte *source = &FONT[(word)(ch-32)*8];
word addr = x*8 + y*256;
tms_set_vram_write_addr(SCREEN2_PATTERN_TABLE + addr); for(byte i=0;i<8;i++) TMS_WRITE_DATA_PORT(source[i]);
tms_set_vram_write_addr(SCREEN2_COLOR_TABLE + addr); for(byte i=0;i<8;i++) TMS_WRITE_DATA_PORT(col);
}
void screen2_puts(byte x, byte y, byte col, char *s) {
byte c;
while(c=*s++) {
screen2_putc(c, x++, y, col);
}
}
#define PLOT_MODE_RESET 0
#define PLOT_MODE_SET 1
#define PLOT_MODE_INVERT 2
byte SCREEN2_PLOT_MODE = PLOT_MODE_SET;
void screen2_plot(byte x, byte y) {
byte pow2_table_reversed[8] = { 128,64,32,16,8,4,2,1 };
word paddr = SCREEN2_PATTERN_TABLE + (word)(x & 0b11111000) + (word)(y & 0b11111000)*32 + y%8;
tms_set_vram_read_addr(paddr);
byte data = TMS_READ_DATA_PORT;
byte mask = pow2_table_reversed[x%8];
tms_set_vram_write_addr(paddr);
switch(SCREEN2_PLOT_MODE) {
case PLOT_MODE_RESET:
data &= ~mask;
break;
case PLOT_MODE_SET:
data |= mask;
break;
case PLOT_MODE_INVERT:
data ^= mask;
break;
}
TMS_WRITE_DATA_PORT(data);
}
signed int vti_abs(signed int x) {
return x < 0 ? -x : x;
}
// http://members.chello.at/~easyfilter/bresenham.html
void vti_line(byte _x0, byte _y0, byte _x1, byte _y1) {
signed int x0 = (signed int) _x0;
signed int x1 = (signed int) _x1;
signed int y0 = (signed int) _y0;
signed int y1 = (signed int) _y1;
signed int dx = vti_abs(x1-x0);
signed int dy = -vti_abs(y1-y0);
signed int err = dx+dy; /* error value e_xy */
bool ix = x0<x1;
bool iy = y0<y1;
signed int e2;
for(;;){ /* loop */
screen2_plot((byte)x0, (byte)y0);
if (x0==x1 && y0==y1) break;
e2 = err<<1;//2*err;
if (e2 >= dy) { err += dy; if(ix) ++x0; else --x0; } /* e_xy+e_x > 0 */
if (e2 <= dx) { err += dx; if(iy) ++y0; else --y0; } /* e_xy+e_y < 0 */
}
}
/*
// http://members.chello.at/~easyfilter/bresenham.html
void vti_ellipse_rect(byte _x0, byte _y0, byte _x1, byte _y1)
{
//unsigned int x0,y0,x1,y1;
signed int x0 = (signed int) _x0;
signed int y0 = (signed int) _y0;
signed int x1 = (signed int) _x1;
signed int y1 = (signed int) _y1;
signed int a = vti_abs(x1-x0), b = vti_abs(y1-y0);
signed int b1 = b&1; // values of diameter
signed int dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
signed int err = dx+dy+b1*a*a, e2; // error of 1.step
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a *= 8*a; b1 = 8*b*b;
do {
screen2_plot((byte) x1, (byte) y0); // I. Quadrant
screen2_plot((byte) x0, (byte) y0); // II. Quadrant
screen2_plot((byte) x0, (byte) y1); // III. Quadrant
screen2_plot((byte) x1, (byte) y1); // IV. Quadrant
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 < b) { // too early stop of flat ellipses a=1
screen2_plot((byte) x0-1, (byte) (y0)); // -> finish tip of ellipse
screen2_plot((byte) x1+1, (byte) (y0++));
screen2_plot((byte) x0-1, (byte) (y1));
screen2_plot((byte) x1+1, (byte) (y1--));
}
}
*/

7
lib/utils.h Normal file
View File

@ -0,0 +1,7 @@
#define POKE(a,b) (*((byte *)(a))=(byte)(b))
#define PEEK(a) (*((byte *)(a)))
#define HIBYTE(c) (>(c))
#define LOBYTE(c) (<(c))
#define NOP asm { nop }

650
test.c
View File

@ -1,650 +0,0 @@
// TODO verificare NOPs
#ifdef APPLE1
// APPLE1
#pragma start_address(0x4000)
const word WOZMON = 0xFF1F; // enters monitor
const word ECHO = 0xFFEF; // output ascii character in A (A not destroyed)
const word PRBYTE = 0xFFDC; // print hex byte in A (A destroyed)
const word KEY_DATA = 0xd010; // read key
const word KEY_CTRL = 0xd011; // control port
const word TERM_DATA = 0xd012; // write ascii
const word TERM_CTRL = 0xd013; // control port
const byte *VDP_DATA = 0xC000; // TMS9918 data port (VRAM)
const byte *VDP_REG = 0xC001; // TMS9918 register port (write) or status (read)
#else
// VIC20
const word ECHO = 0xFFD2; // chrout routine in kernal rom
const word GETIN = 0xFFE4; // GETIN keyboard read routine
const byte *VDP_DATA = 0xA000; // TMS9918 data port (VRAM)
const byte *VDP_REG = 0xA001; // TMS9918 register port (write) or status (read)
#endif
// typedef unsigned char byte;
// typedef unsigned int word;
// TMS9918 interface flags
const byte WRITE_TO_REG = 0b10000000;
const byte WRITE_TO_VRAM = 0b01000000;
const byte READ_FROM_VRAM = 0b00000000;
#define POKE(a,b) (*((byte *)(a))=(byte)(b))
#define PEEK(a) (*((byte *)(a)))
#define NOP asm { nop }
#define TMS_WRITE_REG(a) (*VDP_REG=(byte)(a))
#define TMS_WRITE_DATA(a) (*VDP_DATA=(byte)(a))
#define TMS_READ_REG (*VDP_REG);
#define TMS_READ_DATA (*VDP_DATA);
// status register
#define FRAME_BIT(a) ((a) & 0b10000000)
#define FIVESPR_BIT(a) ((a) & 0b01000000)
#define COLLISION_BIT(a) ((a) & 0b00100000)
#define SPRITE_NUM(a) ((a) & 0b00011111)
// sets the VRAM write address on the TMS9918
void set_vram_write_addr(word addr) {
TMS_WRITE_REG(<addr);
TMS_WRITE_REG((>addr & 0b00111111)|WRITE_TO_VRAM);
}
// sets the VRAM read address on the TMS9918
void set_vram_read_addr(word addr) {
TMS_WRITE_REG(<addr);
TMS_WRITE_REG((>addr & 0b00111111)|READ_FROM_VRAM);
}
// writes a value to a TMS9918 register (0-7)
void write_reg(byte regnum, byte val) {
TMS_WRITE_REG(val);
TMS_WRITE_REG((regnum & 0b00001111)|WRITE_TO_REG);
}
inline void set_color(byte col) {
write_reg(7, col);
}
byte SCREEN1_TABLE[8] = {
0x00, 0xc0, 0x05, 0x80, 0x01, 0x20, 0x00, 0x25
};
byte SCREEN2_TABLE[8] = {
0x02, 0xc0, 0x0e, 0xff, 0x03, 0x76, 0x03, 0x25
};
/*
IRQ on the apple1 goes to 0
- #pragma zp_reserve(0,1,2)
- POKE 0,$4C (JUMP)
- DOKE 1,address routine
*/
/*
word tick_counts;
__interrupt(hardware_all) void interrupt_handler() {
tick_counts++;
}
// reserve locations for the IRQ vector
#pragma zp_reserve(0,1,2)
//__address(0x0001) volatile word KERNEL_IRQ;
void install_interrupt() {
asm { sei }
POKE(0,0x4C);
//KERNEL_IRQ = (word) &interrupt_handler;
// *((word *)0x0001) = (word) &interrupt_handler;
asm { cli }
}
*/
word screen1_cursor;
#include "laser500_font.ascii.c"
// SCREEN 1 VALUES
// sprite patterns: $0000
// pattern table: $0800 (256*8)
// sprite attributes: $1000
// unused: $1080
// name table: $1400 (32*24)
// unused: $1800
// color table: $2000 (32)
// unused $2020-$3FFF
const word SCREEN1_PATTERN_TABLE = 0x0800;
const word SCREEN1_NAME_TABLE = 0x1400;
const word SCREEN1_COLOR_TABLE = 0x2000;
const word SCREEN1_SPRITE_PATTERNS = 0x0000;
const word SCREEN1_SPRITE_ATTRS = 0x1000;
const word SCREEN1_SIZE = (32*24);
// loads the Laser 500 font on the pattern table
void SCREEN1_LOAD_FONT() {
static byte *source = FONT;
static word i;
// start writing into VRAM from space character (32..127)
set_vram_write_addr(SCREEN1_PATTERN_TABLE+(32*8));
for(i=768;i!=0;i--) {
TMS_WRITE_DATA(*source++);
}
// reverse font (32..127)
source = FONT;
set_vram_write_addr(SCREEN1_PATTERN_TABLE+((128+32)*8));
for(i=768;i!=0;i--) {
TMS_WRITE_DATA(~(*source++));
}
}
// prints character to TMS (SCREEN 1 MODE)
void SCREEN1_PUTCHAR(byte c) {
set_vram_write_addr(screen1_cursor++);
TMS_WRITE_DATA(c);
}
// prints 0 terminated string pointed by YA
void SCREEN1_PUTS(byte *s) {
byte c;
while(c=*s++) {
SCREEN1_PUTCHAR(c);
}
}
void SCREEN1_HOME() {
screen1_cursor = SCREEN1_NAME_TABLE;
}
void SCREEN1_LOCATEXY(byte x, byte y) {
screen1_cursor = SCREEN1_NAME_TABLE + ((word)y)*32 + x;
}
void SCREEN1_FILL() {
// fills name table with spaces (32)
set_vram_write_addr(SCREEN1_NAME_TABLE);
for(word i=SCREEN1_SIZE;i!=0;i--) {
TMS_WRITE_DATA(32);
}
// fill pattern table with 0
set_vram_write_addr(SCREEN1_PATTERN_TABLE);
for(word i=256*8;i!=0;i--) {
TMS_WRITE_DATA(0);
}
// fill color table with $1F
set_vram_write_addr(SCREEN1_COLOR_TABLE);
for(byte i=32;i!=0;i--) {
TMS_WRITE_DATA(0x1f);
}
}
// SCREEN 2 VALUES
// pattern table: $0000-$17FF (256*8*3)
// sprite patterns: $1800-$19FF
// color table: $2000-$27FF (256*8*3)
// name table: $3800-$3AFF (32*24 = 256*3 = 768)
// sprite attributes: $3B00-$3BFF
// unused $3C00-$3FFF
//
const word SCREEN2_PATTERN_TABLE = 0x0000;
const word SCREEN2_NAME_TABLE = 0x3800;
const word SCREEN2_COLOR_TABLE = 0x2000;
const word SCREEN2_SPRITE_PATTERNS = 0x1800;
const word SCREEN2_SPRITE_ATTRS = 0x3b00;
const word SCREEN2_SIZE = (32*24);
void SCREEN_INIT(byte *table) {
for(byte i=0;i<8;i++) {
write_reg(i, table[i]);
}
}
void SCREEN2_FILL() {
// fills name table x3 with increasing numbers
set_vram_write_addr(SCREEN2_NAME_TABLE);
for(word i=0;i<SCREEN2_SIZE;i++) {
TMS_WRITE_DATA(i & 0xFF);
}
// fill pattern table with 0 (clear screen)
set_vram_write_addr(SCREEN2_PATTERN_TABLE);
for(word i=768*8;i!=0;i--) {
TMS_WRITE_DATA(0);
}
// fill color table with $1F
set_vram_write_addr(SCREEN2_COLOR_TABLE);
for(word i=768*8;i!=0;i--) {
TMS_WRITE_DATA(0x1f);
}
}
void SCREEN2_PUTC(byte ch, byte x, byte y, byte col) {
byte *source = &FONT[(word)(ch-32)*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(source[i]);
set_vram_write_addr(SCREEN2_COLOR_TABLE + addr); for(byte i=0;i<8;i++) TMS_WRITE_DATA(col);
}
void SCREEN2_PUTS(byte x, byte y, byte col, char *s) {
byte c;
while(c=*s++) {
SCREEN2_PUTC(c, x++, y, col);
}
}
#define PLOT_MODE_RESET 0
#define PLOT_MODE_SET 1
#define PLOT_MODE_INVERT 2
byte SCREEN2_PLOT_MODE = PLOT_MODE_SET;
void SCREEN2_PLOT(byte x, byte y) {
byte pow2_table_reversed[8] = { 128,64,32,16,8,4,2,1 };
word paddr = SCREEN2_PATTERN_TABLE + (word)(x & 0b11111000) + (word)(y & 0b11111000)*32 + y%8;
set_vram_read_addr(paddr);
byte data = TMS_READ_DATA;
byte mask = pow2_table_reversed[x%8];
set_vram_write_addr(paddr);
switch(SCREEN2_PLOT_MODE) {
case PLOT_MODE_RESET:
data &= ~mask;
break;
case PLOT_MODE_SET:
data |= mask;
break;
case PLOT_MODE_INVERT:
data ^= mask;
break;
}
TMS_WRITE_DATA(data);
}
void screen1_square_sprites() {
// fills first sprite pattern with 255
set_vram_write_addr(SCREEN1_SPRITE_PATTERNS); // start writing in the sprite patterns
for(byte i=0;i<8;i++) {
TMS_WRITE_DATA(255);
}
// set sprite coordinates
set_vram_write_addr(SCREEN1_SPRITE_ATTRS); // start writing in the sprite attribute
for(byte i=0;i<32;i++) {
TMS_WRITE_DATA((6+i)*8); NOP; NOP; NOP; NOP; // y coordinate
TMS_WRITE_DATA((6+i)*8); NOP; NOP; NOP; NOP; // x coordinate
TMS_WRITE_DATA(0); NOP; NOP; NOP; NOP; // name
TMS_WRITE_DATA(i); NOP; NOP; NOP; NOP; // color
}
}
void screen2_square_sprites() {
// fills first sprite pattern with 255
set_vram_write_addr(SCREEN2_SPRITE_PATTERNS); // start writing in the sprite patterns
for(byte i=0;i<8;i++) {
TMS_WRITE_DATA(0);
}
// set sprite coordinates
set_vram_write_addr(SCREEN2_SPRITE_ATTRS); // start writing in the sprite attribute
for(byte i=0;i<32;i++) {
TMS_WRITE_DATA(0); NOP; NOP; NOP; NOP; // y coordinate
TMS_WRITE_DATA(0); NOP; NOP; NOP; NOP; // x coordinate
TMS_WRITE_DATA(0); NOP; NOP; NOP; NOP; // name
TMS_WRITE_DATA(i); NOP; NOP; NOP; NOP; // color
}
}
void prova_screen1() {
SCREEN_INIT(SCREEN1_TABLE);
SCREEN1_FILL();
SCREEN1_LOAD_FONT();
SCREEN1_HOME(); SCREEN1_PUTS("*** P-LAB VIDEO CARD SYSTEM ***");
SCREEN1_LOCATEXY(0, 2); SCREEN1_PUTS("16K VRAM BYTES FREE");
SCREEN1_LOCATEXY(0, 4); SCREEN1_PUTS("READY.");
SCREEN1_LOCATEXY(0, 10);
for(word i=0;i<256;i++) SCREEN1_PUTCHAR((byte)i);
screen1_square_sprites();
}
byte amiga_data[612] = {
107,35,130,35,
130,35,130,57,
130,57,185,57,
185,57,185,35,
185,35,197,35,
197,35,208,46,
208,46,208,118,
208,118,199,118,
199,118,199,80,
199,80,136,80,
136,80,134,78,
134,78,125,78,
125,78,124,80,
124,80,117,80,
117,80,117,89,
117,89,107,96,
107,96,107,35,
105,96,105,34,
105,34,198,34,
198,34,210,46,
210,46,210,119,
210,119,158,119,
158,119,158,124,
158,124,154,132,
154,132,150,137,
150,137,146,140,
146,140,140,149,
140,149,132,153,
132,153,132,164,
132,164,82,164,
82,164,82,133,
82,133,81,133,
81,133,81,105,
81,105,85,99,
85,99,92,93,
92,93,93,89,
93,89,97,82,
97,82,102,79,
102,79,105,78,
105,78,105,78,
105,78,102,80,
102,80,98,82,
98,82,94,89,
94,89,93,93,
93,93,86,99,
86,99,82,105,
82,105,82,132,
82,132,83,133,
83,133,83,163,
83,163,112,163,
112,163,112,142,
112,142,120,142,
120,142,124,138,
124,138,124,119,
124,119,122,116,
122,116,122,106,
122,106,137,93,
137,93,137,81,
137,81,134,79,
134,79,131,79,
131,79,134,82,
134,82,134,89,
134,89,133,90,
133,90,126,90,
126,90,123,87,
123,87,114,94,
114,94,102,100,
102,100,90,109,
90,109,90,108,
90,108,102,99,
102,99,98,98,
98,98,95,95,
95,95,98,97,
98,97,102,98,
102,98,105,96,
122,85,124,78,
124,78,132,79,
132,79,133,82,
133,82,133,89,
133,89,126,89,
126,89,122,85,
123,116,123,106,
123,106,138,93,
138,93,138,81,
138,81,197,81,
197,81,197,118,
197,118,124,118,
124,118,123,114,
132,35,183,35,
183,35,183,56,
183,56,132,56,
132,56,132,35,
168,38,179,38,
179,38,179,52,
179,52,168,52,
168,52,168,38,
170,39,177,39,
177,39,177,51,
177,51,170,51,
170,51,170,39,
119,81,122,81,
122,81,122,83,
122,83,119,87,
119,87,119,81,
113,163,113,143,
113,143,120,143,
120,143,125,138,
125,138,125,129,
125,129,129,131,
129,131,137,131,
137,131,137,133,
137,133,141,138,
141,138,145,138,
145,138,145,140,
145,140,139,149,
139,149,131,152,
131,152,131,163,
131,163,113,163,
125,119,125,124,
125,124,130,121,
130,121,125,119,
129,122,125,124,
125,124,125,119,
125,119,146,119,
146,119,136,129,
136,129,135,129,
135,129,137,127,
137,127,132,122,
132,122,129,122,
150,119,157,119,
157,119,157,124,
157,124,153,132,
153,132,150,136,
150,136,145,137,
145,137,142,137,
142,137,139,133,
139,133,139,130,
139,130,150,119,
140,130,140,133,
140,133,142,136,
142,136,144,136,
144,136,148,135,
148,135,149,132,
149,132,145,129,
145,129,140,130,
126,128,134,130,
134,130,136,127,
136,127,132,123,
132,123,129,123,
129,123,126,125,
126,125,126,128,
198,35,209,46,
209,46,209,118
};
void prova_screen2() {
SCREEN_INIT(SCREEN2_TABLE);
SCREEN2_FILL();
screen2_square_sprites();
SCREEN2_PUTS(0,0,0x1F,"*** P-LAB VIDEO CARD SYSTEM ***");
SCREEN2_PUTS(0,2,0x1F,"16K VRAM BYTES FREE");
SCREEN2_PUTS(0,4,0x1F,"READY.");
for(byte i=0;i<16;i++) {
SCREEN2_PUTS(5,(byte)(6+i),(byte)(((15-i)<<4)+i)," SCREEN 2 ");
}
vti_line(18, 45,232,187);
vti_line(18,187,232, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_RESET;
vti_line(18+5, 45,232+5,187);
vti_line(18+5,187,232+5, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_INVERT;
vti_line(18+5+5, 45,232+5+5,187);
vti_line(18+5+5,187,232+5+5, 45);
SCREEN2_PLOT_MODE = PLOT_MODE_SET;
//vti_ellipse_rect(7,9,202,167);
}
void prova_screen3() {
SCREEN_INIT(SCREEN2_TABLE);
SCREEN2_FILL();
screen2_square_sprites();
SCREEN2_PUTS(0,0,0x1F,"*** P-LAB VIDEO CARD SYSTEM ***");
SCREEN2_PUTS(0,2,0x1F,"16K VRAM BYTES FREE");
SCREEN2_PUTS(0,4,0x1F,"READY.");
for(word p=0;p<612;p+=4) {
vti_line(amiga_data[p],amiga_data[p+1],amiga_data[p+2],amiga_data[p+3]);
}
SCREEN2_PUTS(18,12,0x4F,"APPLE1");
}
// puts a character on the apple1 screen using the WOZMON routine
void woz_putc(byte c) {
asm {
lda c
jsr ECHO
}
}
// returns to WOZMON prompt
void woz_mon() {
#ifdef APPLE1
asm {
jmp WOZMON
}
#endif
}
// reads a key from the apple-1 keyboard
byte woz_getkey() {
#ifdef APPLE1
asm {
__wait:
lda KEY_CTRL
bpl __wait
}
return PEEK(KEY_DATA) & 0x7f;
#else
byte key;
byte const *keyptr = &key;
kickasm(uses keyptr, uses GETIN) {{
__wait:
jsr GETIN
cmp #0
beq __wait
sta keyptr
}}
return key;
#endif
}
signed int vti_abs(signed int x) {
return x < 0 ? -x : x;
}
// http://members.chello.at/~easyfilter/bresenham.html
void vti_line(byte _x0, byte _y0, byte _x1, byte _y1) {
signed int x0 = (signed int) _x0;
signed int x1 = (signed int) _x1;
signed int y0 = (signed int) _y0;
signed int y1 = (signed int) _y1;
signed int dx = vti_abs(x1-x0);
signed int dy = -vti_abs(y1-y0);
signed int err = dx+dy; /* error value e_xy */
bool ix = x0<x1;
bool iy = y0<y1;
signed int e2;
for(;;){ /* loop */
SCREEN2_PLOT((byte)x0, (byte)y0);
if (x0==x1 && y0==y1) break;
e2 = err<<1;//2*err;
if (e2 >= dy) { err += dy; if(ix) ++x0; else --x0; } /* e_xy+e_x > 0 */
if (e2 <= dx) { err += dx; if(iy) ++y0; else --y0; } /* e_xy+e_y < 0 */
}
}
/*
// http://members.chello.at/~easyfilter/bresenham.html
void vti_ellipse_rect(byte _x0, byte _y0, byte _x1, byte _y1)
{
//unsigned int x0,y0,x1,y1;
signed int x0 = (signed int) _x0;
signed int y0 = (signed int) _y0;
signed int x1 = (signed int) _x1;
signed int y1 = (signed int) _y1;
signed int a = vti_abs(x1-x0), b = vti_abs(y1-y0);
signed int b1 = b&1; // values of diameter
signed int dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
signed int err = dx+dy+b1*a*a, e2; // error of 1.step
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a *= 8*a; b1 = 8*b*b;
do {
SCREEN2_PLOT((byte) x1, (byte) y0); // I. Quadrant
SCREEN2_PLOT((byte) x0, (byte) y0); // II. Quadrant
SCREEN2_PLOT((byte) x0, (byte) y1); // III. Quadrant
SCREEN2_PLOT((byte) x1, (byte) y1); // IV. Quadrant
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 < b) { // too early stop of flat ellipses a=1
SCREEN2_PLOT((byte) x0-1, (byte) (y0)); // -> finish tip of ellipse
SCREEN2_PLOT((byte) x1+1, (byte) (y0++));
SCREEN2_PLOT((byte) x0-1, (byte) (y1));
SCREEN2_PLOT((byte) x1+1, (byte) (y1--));
}
}
*/
void main() {
//install_interrupt();
byte key = '1';
for(;;) {
if(key == '1') prova_screen1();
else if(key == '2') prova_screen2();
else if(key == '3') prova_screen3();
else if(key == '0') break;
else woz_putc(key);
key = woz_getkey();
}
woz_putc(42);
woz_mon();
}

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

87
tetris/draw.h Normal file
View File

@ -0,0 +1,87 @@
// color escape codes that can be used with "print_string"
#define BG_TRANSPARENT "\x1c\x00"
#define BG_BLACK "\x1c\x01"
#define BG_MEDIUM_GREEN "\x1c\x02"
#define BG_LIGHT_GREEN "\x1c\x03"
#define BG_DARK_BLUE "\x1c\x04"
#define BG_LIGHT_BLUE "\x1c\x05"
#define BG_DARK_RED "\x1c\x06"
#define BG_CYAN "\x1c\x07"
#define BG_MEDIUM_RED "\x1c\x08"
#define BG_LIGHT_RED "\x1c\x09"
#define BG_DARK_YELLOW "\x1c\x0A"
#define BG_LIGHT_YELLOW "\x1c\x0B"
#define BG_DARK_GREEN "\x1c\x0C"
#define BG_MAGENTA "\x1c\x0D"
#define BG_GREY "\x1c\x0E"
#define BG_WHITE "\x1c\x0F"
#define FG_TRANSPARENT "\x1b\x00"
#define FG_BLACK "\x1b\x10"
#define FG_MEDIUM_GREEN "\x1b\x20"
#define FG_LIGHT_GREEN "\x1b\x30"
#define FG_DARK_BLUE "\x1b\x40"
#define FG_LIGHT_BLUE "\x1b\x50"
#define FG_DARK_RED "\x1b\x60"
#define FG_CYAN "\x1b\x70"
#define FG_MEDIUM_RED "\x1b\x80"
#define FG_LIGHT_RED "\x1b\x90"
#define FG_DARK_YELLOW "\x1b\xA0"
#define FG_LIGHT_YELLOW "\x1b\xB0"
#define FG_DARK_GREEN "\x1b\xC0"
#define FG_MAGENTA "\x1b\xD0"
#define FG_GREY "\x1b\xE0"
#define FG_WHITE "\x1b\xF0"
void draw_tile(byte x, byte y, byte ch, byte col) {
byte *source = &FONTS[(word)ch*8];
word addr = x*8 + y*256;
tms_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;*/ }
tms_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 draw_string(byte x, byte y, byte *s, byte color) {
byte c;
while(c=*s++) {
draw_tile(x++, y, c, color);
}
}
// print string with ESC + color support
void print_string(byte x, byte y, byte *s, byte color) {
byte c;
while(c=*s++) {
if(c==0x1b) color = *s++ | (color & 0x0F);
else if(c==0x1c) color = *s++ | (color & 0xF0);
else {
draw_tile(x++, y, c, color);
}
}
}
/*
byte screen2_cursor_pos_x;
byte screen2_cursor_pos_y;
byte screen2_cursor_color;
void color(byte color) {
screen2_cursor_color = color;
}
void locate(byte x, byte y) {
screen2_cursor_pos_x = x;
screen2_cursor_pos_y = y;
}
void print_string(byte *s) {
byte c;
while(c=*s++) {
draw_tile(screen2_cursor_pos_x++, screen2_cursor_pos_y, c, screen2_cursor_color);
if(screen2_cursor_pos_x>=32) {
screen2_cursor_pos_x = 0;
screen2_cursor_pos_y++;
}
}
}
*/

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

246
tetris/grboard.h Normal file
View File

@ -0,0 +1,246 @@
#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 CRUNCH_CHAR_1 13
#define CRUNCH_COLOR_1 FG_BG(COLOR_BLACK, COLOR_GREY)
#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 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);
draw_string(POS_SCORE_X+1,POS_SCORE_Y+2,tmp,color);
right_pad_number((unsigned long)total_lines);
draw_string(POS_LINES_X+1,POS_LINES_Y+2,tmp,color);
right_pad_number((unsigned long)level);
draw_string(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++) {
draw_tile(x+i, y , FRAME_VERT, color);
draw_tile(x+i, y+h-1, FRAME_VERT, color);
}
for (i=1; i<h-1; i++) {
draw_tile(x , y+i, FRAME_HORIZ, color);
draw_tile(x+w-1, y+i, FRAME_HORIZ, color);
}
draw_tile(x ,y , FRAME_NE_CORNER, color);
draw_tile(x+w-1 ,y , FRAME_NW_CORNER, color);
draw_tile(x ,y+h-1, FRAME_SE_CORNER, color);
draw_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++) {
draw_tile(x+i, y+j, ch, color);
}
}
}
// draws the board
void drawPlayground() {
tms_set_color(COLOR_DARK_BLUE);
byte frame_color = FG_BG(COLOR_GREY,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));
draw_string(POS_SCORE_X+1, POS_SCORE_Y+1, "SCORE ", text_color);
draw_string(POS_LEVEL_X+1, POS_LEVEL_Y+1, "LEVEL ", text_color);
draw_string(POS_LINES_X+1, POS_LINES_Y+1,"LINES ", text_color);
draw_string(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_GREY,COLOR_BLACK);
byte y = (STARTBOARD_Y+BROWS)/2;
drawFrame(STARTBOARD_X-2, y-1,14,3, frame_color);
draw_string(STARTBOARD_X-1,y-0," GAME OVER ", FG_BG(COLOR_LIGHT_YELLOW,COLOR_BLACK));
// sound effect
// ERASED: 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;
draw_string(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++;
draw_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];
draw_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++) {
draw_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];
draw_tile(STARTBOARD_X+column, STARTBOARD_Y+line, ch, col);
}
}
}

66
tetris/intro.h Normal file
View File

@ -0,0 +1,66 @@
#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];
draw_tile(c,r+1,ch,col);
}
}
}
}
// introduction screen
void introScreen() {
tms_set_color(COLOR_BLACK);
// simulate cls (TODO improve speed)
fillFrame(0, 0, 32, 24, 32, FG_BG(COLOR_BLACK, COLOR_BLACK));
drawLogo();
print_string(8, 10, " FOR THE APPLE-1", FG_BG(COLOR_WHITE, COLOR_BLACK));
print_string(8, 14, " WRITTEN BY ", FG_BG(COLOR_WHITE, COLOR_BLACK));
print_string(8, 16, "ANTONINO PORCINO", FG_BG(COLOR_WHITE, COLOR_BLACK));
print_string(7,21,"USE " BG_DARK_BLUE"I"BG_BLACK" "BG_DARK_BLUE"J"BG_BLACK" "BG_DARK_BLUE"K"BG_BLACK" "BG_DARK_BLUE"L"BG_BLACK" "BG_DARK_BLUE"SPACE"BG_BLACK , FG_BG(COLOR_DARK_YELLOW ,COLOR_BLACK));
print_string(5,23,"PRESS " BG_DARK_BLUE "RETURN" BG_BLACK " TO START" , FG_BG(COLOR_DARK_YELLOW ,COLOR_BLACK));
// wait for key released
while(test_key(KEY_RETURN));
// wait for key press
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 apple1_readkey();
}
byte player_input() {
return read_keyboard();
}
#endif

2
tetris/m.bat Normal file
View File

@ -0,0 +1,2 @@
@rem === BUILD TETRIS ===
call ..\tools\build tetris

590
tetris/mkfont.js Normal file
View File

@ -0,0 +1,590 @@
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)},`);
}
console.log("// file generated automatically by mkfont.js -- do not edit");
// 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("........");

56
tetris/pieces.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef PIECES_H
#define PIECES_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

338
tetris/tetris.c Normal file
View File

@ -0,0 +1,338 @@
// 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: solve division by counter_factor
// TODO: interrupt and wait_interrupt()
//
// 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
#include "../lib/utils.h"
#include "../lib/apple1.h"
#include "../lib/tms9918.h"
#include "../lib/font8x8.h"
#include "../lib/tms_screen1.h"
#include "../lib/tms_screen2.h"
#include "../lib/interrupt.h"
#include "draw.h"
#include "pieces.h"
#define EMPTY_GR_CHAR 32
#define EMPTY_GR_COLOR FG_BG(COLOR_BLACK, COLOR_BLACK)
byte piece_chars[NUMPIECES+1] = {
0, // L (orange in the original tetris)
3, // J
2, // T
1, // I
2, // O
1, // S
0, // Z
EMPTY_GR_CHAR // space
};
byte piece_colors[NUMPIECES+1] = {
FG_BG( COLOR_GREY , COLOR_WHITE ), // L ORANGE
FG_BG( COLOR_LIGHT_BLUE , COLOR_DARK_BLUE ), // J VIOLET
FG_BG( COLOR_MAGENTA , COLOR_WHITE ), // T LIGHT_MAGENTA
FG_BG( COLOR_CYAN , COLOR_LIGHT_BLUE ), // I LIGHT_CYAN
FG_BG( COLOR_LIGHT_YELLOW , COLOR_DARK_YELLOW ), // O YELLOW
FG_BG( COLOR_LIGHT_GREEN , COLOR_DARK_GREEN ), // S LIGHT_GREEN
FG_BG( COLOR_LIGHT_RED , COLOR_DARK_RED ), // Z LIGHT_RED
EMPTY_GR_COLOR // empty character
};
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();
}
void main() {
// install the start-of-frame interrupt detection
//install_interrupt();
#ifdef APPLE1
apple1_eprom_init();
#endif
// initialize the screen
tms_init_regs(SCREEN2_TABLE);
screen2_init_bitmap(FG_BG(COLOR_BLACK,COLOR_BLACK));
while(1) {
introScreen();
initGame();
gameLoop();
gameOver();
}
}

22
tools/build.bat Normal file
View File

@ -0,0 +1,22 @@
@rem === BUILD SCRIPT ===
@SET TARGET=%1
@echo ======================== VIC20 ===================================================
call kickc -targetdir ..\kickc\ -t VIC20_8K -D=VIC20 %TARGET%.c -o out\%TARGET%_vic20.prg -e
copy out\%TARGET%.prg out\%TARGET%_vic20.prg
@echo ======================== APPLE 1 =================================================
call kickc -targetdir ..\kickc\ -t apple1 -D=APPLE1 %TARGET%.c -o out\%TARGET%_apple1.prg -e
@rem builds the apple1 eprom file
call node ..\tools\mkeprom out out\%TARGET%_apple1.bin
@rem clean up files
@del out\apple1_codeseg.bin
@del out\apple1_dataseg.bin
@del out\*.vs
@del out\*.klog
@del out\*.vs
@del out\*.dbg
@del out\%TARGET%.prg

39
tools/mkeprom.js Normal file
View File

@ -0,0 +1,39 @@
/*
const fs = require('fs');
function prg2bin(){
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');
if(process.argv.length < 3) {
console.log("usage: node mkeprom.js <inputdir> <outputfile>");
console.log(" <inputdir> is the directory containing 'apple1_codeseg.bin' and 'apple1_dataseg.bin'");
console.log(" <outputfile> is outfile binary eprom file ($4000-$7FFF)");
process.exit(0);
}
let file_in = process.argv[2];
let file_out = process.argv[3];
let code = fs.readFileSync(`${file_in}/apple1_codeseg.bin`);
let data = fs.readFileSync(`${file_in}/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(file_out, prg);
console.log(`bindump: written to ${file_out}`);

9
tools/prg2bin.js Normal file
View File

@ -0,0 +1,9 @@
const fs = require('fs');
let prg = fs.readFileSync("test_apple1.prg");
prg = prg.slice(2);
fs.writeFileSync("test_apple1.bin",prg);
console.log("bin written");