From 087759549d585c0e84b809e2b92d124cdefb8dd3 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Sat, 19 Sep 2026 13:02:57 +0200 Subject: [PATCH] atari8: updated presets, added 5th player --- presets/atari8-800/common.c | 60 +++++++- presets/atari8-800/common.h | 23 ++- presets/atari8-800/libdemo.c | 38 +++-- presets/atari8-800/siegegame.c | 4 +- presets/atari8-800/tgidemo.c | 248 +++++++++++++++++++++++++++++++++ src/platform/atari8.ts | 1 + 6 files changed, 358 insertions(+), 16 deletions(-) create mode 100644 presets/atari8-800/tgidemo.c diff --git a/presets/atari8-800/common.c b/presets/atari8-800/common.c index 77899cb9..79dfbe41 100644 --- a/presets/atari8-800/common.c +++ b/presets/atari8-800/common.c @@ -378,6 +378,60 @@ void a8_pmg_clear_collisions(void) { GTIA_WRITE.hitclr = 0; } +static byte pmg_sizem; + +void a8_pmg_5th_enable(byte on) { + if (on) { + GTIA_WRITE.prior |= PRIOR_5TH_PLAYER; + GTIA_WRITE.gractl |= GRACTL_MISSLES; + GTIA_WRITE.sizem = 0; /* every missile 2 px wide, so they tile */ + } else { + GTIA_WRITE.prior &= (byte)~PRIOR_5TH_PLAYER; + } + pmg_sizem = 0; +} + +/* + 0: 8 color clocks (1 per pixel) + 1: 16 color clocks (2 per pixel) + 3: 32 color clocks (4 per pixel) +*/ +void a8_pmg_5th_size(byte size) { + size &= 3; + pmg_sizem = size; + size |= size << 2; + size |= size << 4; + GTIA_WRITE.sizem = size; +} + +static const byte pmg_size_tab[4] = { 2, 4, 2, 8 }; + +void a8_pmg_5th_x(byte x) { + byte* m = (byte*)>IA_WRITE.hposm0; + byte inc = pmg_size_tab[pmg_sizem]; + m[0] = x; + x += inc; + m[1] = x; + x += inc; + m[2] = x; + x += inc; + m[3] = x; +} + +void a8_pmg_5th_shape(const byte* shape, byte len, byte y) { + byte* p = a8_pmg_missile(0) + y; + while (len--) { + /* GRAFM packs two bits per missile, M0 in bits 1:0, and each missile + shifts its pair out MSB first, so the leftmost pixel is bit 1. Swap + the bit pairs to accept the usual player order (bit 7 = leftmost). */ + byte s = *shape++; + *p++ = (byte)(((s >> 6) & 0x03) + | ((s >> 2) & 0x0c) + | ((s << 2) & 0x30) + | ((s << 6) & 0xc0)); + } +} + /*==========================================================================*/ /* POKEY */ @@ -466,16 +520,16 @@ void a8_set_colpm(byte i, byte c) { /* Input and utility */ /*==========================================================================*/ -byte a8_console(void) { +byte a8_get_console(void) { return GTIA_READ.consol; } -byte a8_trigger(byte n) { +byte a8_get_trigger(byte n) { return (byte)(((byte*)>IA_READ.trig0)[n] & 1 ? 0 : 1); } #if !defined(__ATARI5200__) -byte a8_stick(byte n) { +byte a8_get_stick(byte n) { byte v = n ? PIA.portb : PIA.porta; return (byte)((~v) & 0x1f); } diff --git a/presets/atari8-800/common.h b/presets/atari8-800/common.h index 19b614ce..5d0c8c78 100644 --- a/presets/atari8-800/common.h +++ b/presets/atari8-800/common.h @@ -232,6 +232,23 @@ byte a8_pmg_hit_pf(byte i); /* player i hit playfield? */ byte a8_pmg_hit_pl(void); /* any player-player hit mask */ void a8_pmg_clear_collisions(void); +/* The four missiles can be combined into one extra, 8-pixel-wide "5th + player", colored with COLPF3 (PRIOR bit 4). It shares the missile + hardware, so the per-missile helpers above don't work while it is on. + The four missiles sit two pixels apart, M0 leftmost. Like players, + the shape lives in the P/M area (ANTIC reloads GRAFM from memory every + scanline), so write it with a8_pmg_5th_shape() rather than GRAFM. */ +#ifndef PRIOR_5TH_PLAYER +#define PRIOR_5TH_PLAYER 0x10 +#endif +void a8_pmg_5th_enable(byte on); +/* x is the left edge of the 8-pixel player. */ +void a8_pmg_5th_x(byte x); +/* Copy a shape into the missile area at vertical byte offset y. len and + orientation are just like a8_pmg_set_shape(): bit 7 is the left pixel. */ +void a8_pmg_5th_shape(const byte* shape, byte len, byte y); +void a8_pmg_5th_size(byte size); + /*==========================================================================*/ /* POKEY sound */ @@ -272,13 +289,13 @@ void a8_set_colpm(byte i, byte c); /*==========================================================================*/ /* Console keys: bit0 start, bit1 select, bit2 option (0 = pressed). */ -byte a8_console(void); +byte a8_get_console(void); /* Trigger n (0..3): nonzero when pressed. */ -byte a8_trigger(byte n); +byte a8_get_trigger(byte n); #if !defined(__ATARI5200__) /* Joystick 0/1 direction+fire bits (JOY_xxx_MASK order). 0 if none. */ -byte a8_stick(byte n); +byte a8_get_stick(byte n); #endif /* Wait for the start of the next frame. (cc65 provides waitvsync(), diff --git a/presets/atari8-800/libdemo.c b/presets/atari8-800/libdemo.c index d477e718..c48b7b86 100644 --- a/presets/atari8-800/libdemo.c +++ b/presets/atari8-800/libdemo.c @@ -58,6 +58,7 @@ static void build_display(void) { void main(void) { byte x = 128; byte dx = 2; + byte i; fill_screen(); build_display(); @@ -73,10 +74,18 @@ void main(void) { a8_dli_install(); a8_pmg_init(A8_PMG_DOUBLE); - a8_pmg_set_x(0, x); - a8_pmg_set_color(0, A8_COLOR(HUE_GREY, 0x0f)); - a8_pmg_set_size(0, A8_PMG_NORMAL); - a8_pmg_set_shape(0, ball, sizeof(ball), 0x38); + // set up all 4 sprites + for (i=0; i<4; i++) { + a8_pmg_set_x(i, x); + a8_pmg_set_color(i, A8_COLOR(HUE_GREY, 0x0f-i)); + a8_pmg_set_size(i, A8_PMG_NORMAL); + a8_pmg_set_shape(i, ball, sizeof(ball), i*16 + 8); + } + // combine missiles into 5th sprite + // colors are taken from the 4 sprites + a8_pmg_5th_enable(1); + a8_pmg_5th_x(100); + a8_pmg_5th_shape(ball, sizeof(ball), 0x48); // set interrupt and start music a8_pokey_init(); @@ -89,12 +98,25 @@ void main(void) { x += dx; if (x < 48) { x = 48; dx = 2; } if (x > 208) { x = 208; dx = (byte)-2; } - a8_pmg_set_x(0, x); - - if (a8_trigger(0)) { - a8_pmg_set_size(0, A8_PMG_QUADW); + // set sprite sizes if joystick moved or trigger pushed + if (a8_get_trigger(0)) { + a8_pmg_5th_size(A8_PMG_QUADW); + } else { + a8_pmg_5th_size(A8_PMG_NORMAL); + } + if (a8_get_stick(0)) { + a8_pmg_set_size(a8_get_stick(0) & 3, A8_PMG_QUADW); } else { a8_pmg_set_size(0, A8_PMG_NORMAL); + a8_pmg_set_size(1, A8_PMG_NORMAL); + a8_pmg_set_size(2, A8_PMG_NORMAL); + a8_pmg_set_size(3, A8_PMG_NORMAL); } + // set X positions + a8_pmg_set_x(0, x); + a8_pmg_set_x(1, x); + a8_pmg_set_x(2, x); + a8_pmg_set_x(3, x); + a8_pmg_5th_x(255-x); } } diff --git a/presets/atari8-800/siegegame.c b/presets/atari8-800/siegegame.c index 0b367c8d..4955d878 100644 --- a/presets/atari8-800/siegegame.c +++ b/presets/atari8-800/siegegame.c @@ -18,8 +18,8 @@ typedef signed char sbyte; typedef unsigned short word; // pointer to screen memory -// TODO: use OS.savmsc -static byte** SAVMSC = (byte**) 0x58; +// pointer to screen memory +static byte** SAVMSC = &OS.savmsc; byte getchar(byte x, byte y) { return (*SAVMSC)[x+y*COLS]; // lookup value @ cursor address diff --git a/presets/atari8-800/tgidemo.c b/presets/atari8-800/tgidemo.c new file mode 100644 index 00000000..105cfeef --- /dev/null +++ b/presets/atari8-800/tgidemo.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +//#tooldef ld cfgfile=atari.cfg +//#symbol ld __RESERVED_MEMORY__=8192 + +/* Color values passed to TGI functions are indices into the default palette. */ +#define COLOR_BACK 0 +#define COLOR_FORE 1 + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Driver stuff */ +static unsigned MaxX; +static unsigned MaxY; +static unsigned AspectRatio; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static void CheckError (const char* S) +{ + unsigned char Error = tgi_geterror (); + + if (Error != TGI_ERR_OK) { + cputs("Error "); + cputs(S); + if (doesclrscrafterexit ()) { + cgetc (); + } + exit (EXIT_FAILURE); + } +} + + + +/* + * Note that everywhere else in the TGI API, colors are referred to via an index + * to the current palette. + * + * TGI_COLOR_ values can be used (ONLY!) for setting the palette, using them + * with other TGI functions only works by chance, on some targets. + */ +static void DoPalette (int n) +{ + static const unsigned char Palette[4][2] = { + { TGI_COLOR_BLACK, TGI_COLOR_BLUE }, + { TGI_COLOR_WHITE, TGI_COLOR_BLACK }, + { TGI_COLOR_RED, TGI_COLOR_BLACK }, + }; + tgi_setpalette (Palette[n]); +} + + +static void DoCircles (void) +{ + unsigned char I; + unsigned char Color = COLOR_BACK; + const unsigned X = MaxX / 2; + const unsigned Y = MaxY / 2; + const unsigned Limit = (X < Y) ? Y : X; + + tgi_setcolor (COLOR_FORE); + tgi_clear (); + tgi_line (0, 0, MaxX, MaxY); + tgi_line (0, MaxY, MaxX, 0); + while (!kbhit ()) { + Color = (Color == COLOR_FORE) ? COLOR_BACK : COLOR_FORE; + tgi_setcolor (Color); + for (I = 10; I <= Limit; I += 10) { + tgi_ellipse (X, Y, I, tgi_imulround (I, AspectRatio)); + } + } + while (kbhit ()) { + cgetc (); + } + cgetc (); +} + + + +static void DoCheckerboard (void) +{ + unsigned X, Y; + unsigned char Color = COLOR_BACK; + + tgi_clear (); + + while (1) { + for (Y = 0; Y <= MaxY; Y += 10) { + for (X = 0; X <= MaxX; X += 10) { + Color = (Color == COLOR_FORE) ? COLOR_BACK : COLOR_FORE; + tgi_setcolor (Color); + tgi_bar (X, Y, X+9, Y+9); + if (kbhit ()) { + cgetc (); + return; + } + } + Color = Color == COLOR_FORE ? COLOR_BACK : COLOR_FORE; + } + Color = Color == COLOR_FORE ? COLOR_BACK : COLOR_FORE; + } +} + + + +static void DoDiagram (void) +{ + int XOrigin, YOrigin; + int Amp; + int X, Y; + unsigned I; + + tgi_setcolor (COLOR_FORE); + tgi_clear (); + + /* Determine zero and amplitude */ + YOrigin = MaxY / 2; + XOrigin = 10; + Amp = (MaxY - 19) / 2; + + /* Y axis */ + tgi_line (XOrigin, 10, XOrigin, MaxY-10); + tgi_line (XOrigin-2, 12, XOrigin, 10); + tgi_lineto (XOrigin+2, 12); + + /* X axis */ + tgi_line (XOrigin, YOrigin, MaxX-10, YOrigin); + tgi_line (MaxX-12, YOrigin-2, MaxX-10, YOrigin); + tgi_lineto (MaxX-12, YOrigin+2); + + /* Sine */ + tgi_gotoxy (XOrigin, YOrigin); + for (I = 0; I <= 360; I += 5) { + + /* Calculate the next points */ + X = (int) (((long) (MaxX - 19) * I) / 360); + Y = (int) (((long) Amp * -_sin (I)) / 256); + + /* Draw the line */ + tgi_lineto (XOrigin + X, YOrigin + Y); + } + + while (kbhit ()) { + cgetc (); + } + cgetc (); +} + + + +static void DoLines (void) +{ + unsigned X; + const unsigned Min = (MaxX < MaxY) ? MaxX : MaxY; + + tgi_setcolor (COLOR_FORE); + tgi_clear (); + + for (X = 0; X <= Min; X += 10) { + tgi_line (0, 0, Min, X); + tgi_line (0, 0, X, Min); + tgi_line (Min, Min, 0, Min-X); + tgi_line (Min, Min, Min-X, 0); + } + + while (kbhit ()) { + cgetc (); + } + cgetc (); +} + +#define DRIVER_COUNT 11 + +const void* drivers[DRIVER_COUNT] = { + atr3_tgi, + atr4_tgi, + atr5_tgi, + atr6_tgi, + atr7_tgi, + atr8_tgi, + atr9_tgi, + atr10_tgi, + atr11_tgi, + atr14_tgi, + atr15_tgi, +}; + +int main (void) +{ + unsigned char Border; + unsigned int driverIndex; + + /* Set the palette, set the border color */ + Border = bordercolor (COLOR_BLACK); + + for (driverIndex = 0; driverIndex < DRIVER_COUNT; driverIndex++) { + /* Install the driver */ + tgi_install (drivers[driverIndex]); + CheckError ("tgi_install"); + + // __RESERVED_MEMORY__ can't override on atari-cart.cfg + //OS.appmhi = (unsigned char*)0x2000; + tgi_init (); + CheckError ("tgi_init"); + + /* Get stuff from the driver */ + MaxX = tgi_getmaxx (); + MaxY = tgi_getmaxy (); + AspectRatio = tgi_getaspectratio (); + + /* Do graphics stuff */ + + /* use default palette */ + DoCircles (); + DoPalette (0); + DoCheckerboard (); + DoPalette (1); + DoDiagram (); + DoPalette (2); + DoLines (); + + /* Uninstall the driver */ + tgi_uninstall (); + } + + /* Reset the border */ + (void) bordercolor (Border); + + return EXIT_SUCCESS; +} diff --git a/src/platform/atari8.ts b/src/platform/atari8.ts index aeb672bb..7ec01d53 100644 --- a/src/platform/atari8.ts +++ b/src/platform/atari8.ts @@ -9,6 +9,7 @@ var Atari8_PRESETS : Preset[] = [ {id:'hellopm.dasm', name:'Hello Sprites'}, {id:'helloconio.c', name:'Text Mode', category:'C'}, {id:'siegegame.c', name:'Siege Game'}, + {id:'tgidemo.c', name:'TGI Demo'}, {id:'hellodlist.c', name:'Display List'}, {id:'libdemo.c', name:'Library Demo'}, ];