1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-06-02 00:41:42 +00:00
kickc/src/test/kc/complex/borderline_pacman/pacman.c
2024-01-02 19:21:16 +01:00

1362 lines
49 KiB
C

// Camelot Borderline Entry
// Pacman made with 9 sprites in in the borders
// The level is constructed from tiles that are 2MC-pixels * 4 lines of rendered graphics.
// Coordinate systems used in the code
// x_col: X column. Each X-column is 1 byte of data wide. This represents 4 MC pixels. Values [0-24].
// x_tile: X tile. Each X-tile is 4 bits of data wide. This represents 2 MC pixels. Values [0-49]. x_tile = 2*x_col
// x_fine: X fine. Each x-fine is 2 bits of data wide. This represents a single MC pixel. Values [0-99]. x_fine = 2*x_tile = 4*x_col
// x_pos: X position. Each x-position is 2 bits of data wide. This represents a single MC pixel. Values [0-99]. x_pos = x_fine
// y_tile: Y tile. Each Y-tile is 4 rendered lines of graphics high. Values [0-36].
// y_fine: Y fine. Each Y-fine is 2 rendered lines of graphics high. Values [0-74]. y_fine = 2*y_tile
// y_pos: Y position. Each Y-position is 1 rendered lines of graphics high. Values [0-149]. y_pos = 2*y_fine = 4*y_tile
// A level is 50 * 37 tiles large. The first 2 rendered lines are not shown. Since 9 sprites are utilized every second line is empty/black.
// The level covers 400px * 292px on the screen, which is the entire visible area on screen showing most pixels - except 2-8 pixels on the x-axis depending on which article you trust.
#pragma target(c64)
#pragma link("pacman.ld")
#pragma start_address(0x0810)
#pragma interrupt(hardware_clobber)
#include <c64.h>
#include <6502.h>
#include <string.h>
#include "byteboozer.h"
#include "code-merger.c"
#include "pacman-sounds.c"
// Graphics Bank 1
// Address of the sprites
char * const BANK_1 = (char*)0x4000;
// Address of the sprites
char * const SPRITES_1 = (char*)0x6000;
// Use sprite pointers on all screens (0x43f8, 0x47f8, ...)
char * const SCREENS_1 = (char*)0x4000;
// Graphics Bank 2
// Address of the sprites
char * const BANK_2 = (char*)0xc000;
// Address of the sprites
char * const SPRITES_2 = (char*)0xe000;
// Use sprite pointers on all screens (0x43f8, 0x47f8, ...)
char * const SCREENS_2 = (char*)0xc000;
// The location where the logic code will be located before merging
char * const LOGIC_CODE_UNMERGED = (char*)0xe000;
// The location where the screen raster code will be located before merging
char * const RASTER_CODE_UNMERGED = (char*)0x6000;
// The location where the screen raster code will be located when running
char * const RASTER_CODE = (char*)0x8000;
// Address of the (decrunched) splash screen
char * const SPLASH = (char*)0x4000;
// Address for the victory graphics
char * const WIN_GFX = (char*)0xa700;
// Address for the gameover graphics
char * const GAMEOVER_GFX = (char*)0xa700;
// Address used by (decrunched) tiles
char * const LEVEL_TILES = (char*)0x4800;
char * const TILES_LEFT = LEVEL_TILES+0x0a00;
char * const TILES_RIGHT = LEVEL_TILES+0x0a80;
char * const TILES_TYPE = LEVEL_TILES+0x0b00;
// The different tile types in TILES_TYPE
enum TILE_TYPE {
EMPTY=0,
PILL=1,
POWERUP=2,
WALL=4
};
// Address used for table containing available directions for all tiles
// TABLE LEVEL_TILES_DIRECTIONS[64*37]
// The level data is organized as 37 rows of 64 bytes. Each row is 50 bytes containing DIRECTION bits plus 14 unused bytes to achieve 64-byte alignment.
char * const LEVEL_TILES_DIRECTIONS = (char*)0x3e00;
// Address of the (decrunched) splash screen
const char BOB_ROW_SIZE = 0x80;
char * const BOB_MASK_LEFT = (char*)0x5400;
char * const BOB_MASK_RIGT = BOB_MASK_LEFT+BOB_ROW_SIZE*6;
char * const BOB_PIXEL_LEFT = BOB_MASK_LEFT+BOB_ROW_SIZE*12;
char * const BOB_PIXEL_RIGT = BOB_MASK_LEFT+BOB_ROW_SIZE*18;
// Tables pointing to the graphics.
// Each page represents one X column (1 byte wide, 4 MC pixels)
// On each page:
// - 0xNN00-0xNN4A : low-byte of the graphics for (X-column, Y-fine)
// - 0xNN50-0xNN9A : high-byte of the graphics for (X-column, Y-fine)
// - 0xNNA0-0xNNEA : index into RENDER_YPOS_INC for incrementing the y-pos.
char * const RENDER_INDEX = (char*)0xb600;
#include "pacman-render.c"
#include "pacman-logic-data.c"
// Sprite settings used for the top/side/bottom sprites.
// Used for achieving single-color sprites on the splash and multi-color sprites in the game
volatile char top_sprites_color;
volatile char top_sprites_mc;
volatile char side_sprites_color;
volatile char side_sprites_mc;
volatile char bottom_sprites_color;
volatile char bottom_sprites_mc;
#pragma data_seg(Init)
#include "pacman-tiles.c"
#include "pacman-bobs.c"
#include "pacman-splash.c"
#include "pacman-win.c"
#include "pacman-gameover.c"
#include "pacman-logic-code.c"
#include "pacman-raster-code.c"
#pragma data_seg(Data)
// Upper memory location used during decrunching
char * const INTRO_MUSIC_CRUNCHED_UPPER = (char*)0xa700;
// Size of the crunched music
const unsigned int INTRO_MUSIC_CRUNCHED_SIZE = 0x0600;
// Address of the music during run-time
char* const INTRO_MUSIC = (char*)0x3000;
// Pointer to procedure
typedef void (*PROC_PTR)(void);
// Pointer to the music init routine
PROC_PTR const musicInit = (PROC_PTR) INTRO_MUSIC+0x00;
// Pointer to the music play routine
PROC_PTR const musicPlay = (PROC_PTR) INTRO_MUSIC+0x06;
#pragma data_seg(Init)
// SID tune
// Pacman 2 channel music by Metal/Camelot
char INTRO_MUSIC_CRUNCHED[] = kickasm(resource "pacman-2chn-simpler.prg", uses INTRO_MUSIC) {{
.modify B2() {
.pc = INTRO_MUSIC "INTRO MUSIC"
.const music = LoadBinary("pacman-2chn-simpler.prg", BF_C64FILE)
.fill music.getSize(), music.get(i)
}
}};
#pragma data_seg(Data)
void main() {
// Show the splash screen
splash_run();
for(;;) {
// Run the gameplay
gameplay_run();
// Show victory or game over image
done_run();
}
}
// Show Victory or Game Over Image
// Returns when the user clicks the joystick button
void done_run() {
// Stop the game play
game_playable = 0;
// Set phase to intro
phase = 0;
// Stop any sound
for(char i=0;i<0x2f;i++)
((char*)SID)[i] = 0;
// Move the bobs to the center to avoid interference while rendering the level
for(char i=0;i<4;i++) {
bobs_xcol[i] = 10;
bobs_yfine[i] = 45;
bobs_bob_id[i] = 0;
}
// Init music
asm { lda #0 }
(*musicInit)();
if(pacman_wins) {
// decrunch win graphics
byteboozer_decrunch(WIN_GFX_CRUNCHED);
} else {
// decrunch game over graphics
byteboozer_decrunch(GAMEOVER_GFX_CRUNCHED);
}
// Show the win graphics
char * gfx = WIN_GFX;
for(char xcol=0;xcol<25;xcol++) {
for(char ypos=0;ypos<25;ypos++) {
// Render 8px x 1px
char pixels = *gfx++;
render(xcol, ypos, pixels);
}
}
// Wait for fire
music_play_next = 0;
while(!joyfire()) {
// While playing music
if(music_play_next) {
//VICII->BG_COLOR=1;
(*musicPlay)();
//VICII->BG_COLOR=0;
music_play_next = 0;
}
}
}
// Initializes all data for the splash and shows the splash.
// Returns when the splash is complete and the user clicks the joystidk #2 button
void splash_run() {
// Stop kernel IRQ
SEI();
// Disable CIA 1 Timer IRQ
CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR_ALL;
// Disable kernal & basic & IO
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_ALL;
// Reset memory to avoid crashes
memset((char*)0x4000, 0, 0xc00);
// Decrunch raster code
byteboozer_decrunch(RASTER_CODE_CRUNCHED);
// Decrunch logic code
byteboozer_decrunch(LOGIC_CODE_CRUNCHED);
// Merge the raster with the logic-code
merge_code(RASTER_CODE, RASTER_CODE_UNMERGED, LOGIC_CODE_UNMERGED);
// Clear the graphics banks
memset(BANK_1+0x2000, 0x00, 0x1fff);
memset(BANK_2, 0x00, 0x3fff);
// Initialize the renderer tables
init_render_index();
// decrunch splash screen
byteboozer_decrunch(SPLASH_CRUNCHED);
// Show the splash screen
splash_show();
// Clear the graphics bank
memset(BANK_1, 0x00, 0x1fff);
// Initialize bobs_restore to "safe" values
init_bobs_restore();
// decrunch bobs graphics tables
byteboozer_decrunch(BOB_GRAPHICS_CRUNCHED);
// Set sprite pointers on all screens (in both graphics banks)
init_sprite_pointers();
// Move the crunched music to upper memory before decrunching it
memcpy(INTRO_MUSIC_CRUNCHED_UPPER, INTRO_MUSIC_CRUNCHED, INTRO_MUSIC_CRUNCHED_SIZE);
// zero-fill the entire Init segment
//memset(LEVEL_TILES_CRUNCHED, 0, INTRO_MUSIC_CRUNCHED+INTRO_MUSIC_CRUNCHED_SIZE-LEVEL_TILES_CRUNCHED);
// decrunch intro music
byteboozer_decrunch(INTRO_MUSIC_CRUNCHED_UPPER);
// Zero-fill the upper memory
memset(INTRO_MUSIC_CRUNCHED_UPPER, 0, INTRO_MUSIC_CRUNCHED_SIZE);
// Disable kernal & basic - enable IO
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Sprite positions
unsigned int sprites_xpos[] = { 0x1e7, 0x13f, 0x10f, 0x0df, 0x0af, 0x07f, 0x04f, 0x01f } ;
char msb = 0;
for(char i=0;i<8;i++) {
SPRITES_YPOS[i*2] = 7;
unsigned int xpos = sprites_xpos[i];
SPRITES_XPOS[i*2] = (char)xpos;
msb /= 2;
if(BYTE1(xpos)) msb |=0x80;
}
// Set initial graphics bank
CIA2->PORT_A = toDd00(SCREENS_1);
// Set initial render/restore buffer
canvas_base_hi = BYTE1(SPRITES_2);
bobs_restore_base = NUM_BOBS*SIZE_BOB_RESTORE;
// Select first screen
VICII->MEMORY = toD018(SCREENS_1, SCREENS_1);
VICII->SPRITES_XMSB = msb;
VICII->SPRITES_ENABLE = 0xff;
VICII->SPRITES_EXPAND_X = 0xff;
VICII->BORDER_COLOR = BLACK;
VICII->BG_COLOR = BLACK;
VICII->SPRITES_MCOLOR1 = BLUE;
VICII->SPRITES_MCOLOR2 = RED;
// On the splash screen sprites all sprites are SC - except sprite #0,1 on the top/bottom of the screen
top_sprites_mc = 0x03;
side_sprites_mc = 0x00;
bottom_sprites_mc = 0x03;;
// On the splash top/bottom sc-sprites are yellow and side-sprites are blue
top_sprites_color = YELLOW;
side_sprites_color = BLUE;
bottom_sprites_color = YELLOW;
// Set the initial top colors/MC
VICII->SPRITES_MC = top_sprites_mc;
// Set initial Sprite Color
for(char i=0;i<8;i++)
SPRITES_COLOR[i] = top_sprites_color;
// Set VICII CONTROL2 ($d016) to 8 to allow ASL, LSR to be used for opening the border
VICII->CONTROL2 = 0x08;
// Move the bobs to the center to avoid interference while rendering the level
for(char i=0;i<4;i++) {
bobs_xcol[i] = 10;
bobs_yfine[i] = 45;
bobs_bob_id[i] = 0;
}
// Disable SID CH#3
asm {
lda #1
sta INTRO_MUSIC+$69
}
// Init music
asm { lda #0 }
(*musicInit)();
// Set phase to intro
phase = 0;
// Start a hyperscreen with no badlines and open borders
// Set screen height to 25 lines (preparing for the hyperscreen), enable display
// Set an illegal mode to prevent any character graphics
VICII->CONTROL1 = VICII_RSEL|VICII_DEN|VICII_ECM|VICII_BMM;
// Wait for line 0xfa (lower border)
while(VICII->RASTER!=0xfa) ;
// Open lower/upper border using RSEL - and disable all graphics (except sprites)
// Set up RASTER IRQ to start at irq_screen_top() (RST8=0)
VICII->CONTROL1 &= ~(VICII_RST8|VICII_RSEL|VICII_DEN) ;
VICII->RASTER = IRQ_SCREEN_TOP_LINE;
*HARDWARE_IRQ = &irq_screen_top;
// Enable Raster Interrupt
VICII->IRQ_ENABLE = IRQ_RASTER;
// Acknowledge any timer IRQ
asm { lda CIA1_INTERRUPT }
// Acknowledge any VIC IRQ
*IRQ_STATUS = 0x0f;
// Start the IRQ again
CLI();
// Prepare for joystick control
joyinit();
// Wait for fire
music_play_next = 0;
while(!joyfire()) {
// While playing music
if(music_play_next) {
//VICII->BG_COLOR=1;
(*musicPlay)();
//VICII->BG_COLOR=0;
music_play_next = 0;
}
}
}
// Initialize all data for gameplay and runs the game.
// Exits when the user has won or lost
void gameplay_run() {
// Turn off raster during transition
SEI();
// Stop any sound
for(char i=0;i<0x2f;i++)
((char*)SID)[i] = 0;
// Pacman has not won yet
pacman_wins = 0;
// Pacman has 3 lives
pacman_lives = 3;
// During transition all sprites are black
VICII->SPRITES_MCOLOR1 = BLACK;
VICII->SPRITES_MCOLOR2 = BLACK;
for(char i=0;i<8;i++)
SPRITES_COLOR[i] = BLACK;
// decrunch level tiles
byteboozer_decrunch(LEVEL_TILES_CRUNCHED);
// Initialize tile directions
init_level_tile_directions();
// Set sprite pointers on all screens (in both graphics banks)
init_sprite_pointers();
// Show the level
pill_count = level_show();
// During gameplay all sprites are MC.
top_sprites_mc = 0xff;
side_sprites_mc = 0xff;
bottom_sprites_mc = 0xff;
// During gameplay all sprites are yellow
top_sprites_color = YELLOW;
side_sprites_color = YELLOW;
bottom_sprites_color = YELLOW;
// Set the initial top colors/MC
VICII->SPRITES_MC = top_sprites_mc;
// Set initial Sprite Color
for(char i=0;i<8;i++)
SPRITES_COLOR[i] = top_sprites_color;
// Set sprite MC-colors for the game
VICII->SPRITES_MCOLOR1 = BLUE;
VICII->SPRITES_MCOLOR2 = RED;
// Set phase to game
phase = 1;
// Spawn pacman and all ghosts
spawn_all();
// Initialize the game sound
pacman_sound_init();
// Start the game play
game_playable = 1;
// Turn on raster after transition
// Start a hyperscreen with no badlines and open borders
// Set screen height to 25 lines (preparing for the hyperscreen), enable display
VICII->CONTROL1 = VICII_RSEL|VICII_DEN|VICII_ECM|VICII_BMM;
// Wait at least one frames for DEN to take effect
while(VICII->RASTER!=0xfb) ;
while(VICII->RASTER!=0xfa) ;
// Open lower/upper border using RSEL - and disable all graphics (except sprites)
// Set up RASTER IRQ to start at irq_screen_top() (RST8=0)
VICII->CONTROL1 &= ~(VICII_RST8|VICII_RSEL|VICII_DEN) ;
VICII->RASTER = IRQ_SCREEN_TOP_LINE;
*HARDWARE_IRQ = &irq_screen_top;
// Enable Raster Interrupt
VICII->IRQ_ENABLE = IRQ_RASTER;
// Acknowledge any timer IRQ
asm { lda CIA1_INTERRUPT }
// Acknowledge any VIC IRQ
*IRQ_STATUS = 0x0f;
// Start the IRQ again
CLI();
// Wait for the player to wins or looses
for(;;)
if(pacman_wins || pacman_lives==0)
return;
}
// The number of pills left
volatile unsigned int pill_count;
// 1 When pacman wins
volatile char pacman_wins = 0;
// The number of pacman lives left
volatile char pacman_lives = 3;
// Signal for playing th next music frame during the intro
volatile char music_play_next = 0;
// Prepare for joystick control
void joyinit() {
// Joystick Read Mode
CIA1->PORT_A_DDR = 0x00;
}
// Return 1 if joy #2 fire is pressed
char joyfire() {
if( (CIA1->PORT_A & 0x10) == 0 ) {
return 1;
} else {
return 0;
}
}
// Initialize keyboard for reading
void kbinit() {
// Keyboard Matrix Columns Write Mode
CIA1->PORT_A_DDR = 0xff;
// Keyboard Matrix Columns Read Mode
CIA1->PORT_B_DDR = 0x00;
}
// Return true if there's a key waiting, return false if not
unsigned char kbhit (void) {
// Write 00 (all columns)
CIA1->PORT_A = 0;
// Read 00 (all rows)
return ~CIA1->PORT_B;
}
// Show the splash screen
void splash_show() {
// Show splash screen
char * splash = SPLASH;
for(char xcol=0;xcol<25;xcol++) {
for(char ypos=0;ypos<147;ypos++) {
// Render 8px x 1px
char pixels = *splash++;
render(xcol, ypos, pixels);
}
}
}
// Show the level by rendering all tiles
// Returns the number of pills on the level
unsigned int level_show() {
unsigned int count = 0;
char * level = LEVEL_TILES;
for(char ytile=0;ytile<37;ytile++) {
for(char xcol=0, xtile=0; xcol<25; xcol++) {
char tile_left = level[xtile++];
if(TILES_TYPE[tile_left]==PILL) count++;
char tile_right = level[xtile++];
if(TILES_TYPE[tile_right]==PILL) count++;
render_tiles(xcol, ytile, tile_left, tile_right);
}
level += 0x40;
}
return count;
}
// Offset of the LEVEL_TILE data within the LEVEL_TILE data (each row is 64 bytes of data)
unsigned int LEVEL_YTILE_OFFSET[37] = {
0x0000, 0x0040, 0x0080, 0x00c0,
0x0100, 0x0140, 0x0180, 0x01c0,
0x0200, 0x0240, 0x0280, 0x02c0,
0x0300, 0x0340, 0x0380, 0x03c0,
0x0400, 0x0440, 0x0480, 0x04c0,
0x0500, 0x0540, 0x0580, 0x05c0,
0x0600, 0x0640, 0x0680, 0x06c0,
0x0700, 0x0740, 0x0780, 0x07c0,
0x0800, 0x0840, 0x0880, 0x08c0,
0x0900
};
// Get the level tile at a given (xtile, ytile) position
// Returns the TILE ID
// If xtile of ytile is outside the legal range the empty tile (0) is returned
char level_tile_get(char xtile, char ytile) {
if(xtile>49 || ytile>36) return 0;
char* ytiles = LEVEL_TILES + LEVEL_YTILE_OFFSET[ytile];
return ytiles[xtile];
}
// Get the open directions at a given (xtile, ytile) position
// Returns the open DIRECTIONs as bits
// If xtile of ytile is outside the legal range the empty tile (0) is returned
char level_tile_directions(char xtile, char ytile) {
if(xtile>49 || ytile>36) return 0;
char* ytiles = LEVEL_TILES_DIRECTIONS + LEVEL_YTILE_OFFSET[ytile];
return ytiles[xtile];
}
// Initialize the LEVEL_TILES_DIRECTIONS table with bits representing all open (non-blocked) movement directions from a tile
void init_level_tile_directions() {
char * directions = LEVEL_TILES_DIRECTIONS;
for(char ytile=0;ytile<37;ytile++) {
for(char xtile=0; xtile<50; xtile++) {
char open_directions = 0;
if(TILES_TYPE[level_tile_get(xtile-1, ytile)]!=WALL) open_directions |= LEFT;
if(TILES_TYPE[level_tile_get(xtile+1, ytile)]!=WALL) open_directions |= RIGHT;
if(TILES_TYPE[level_tile_get(xtile, ytile-1)]!=WALL) open_directions |= UP;
if(TILES_TYPE[level_tile_get(xtile, ytile+1)]!=WALL) open_directions |= DOWN;
directions[xtile] = open_directions;
}
directions += 0x40;
}
}
// Initialize sprite pointers on all screens (in both graphics banks)
void init_sprite_pointers() {
const char SPRITE_ID_0 = (SPRITES_1&0x3fff)/0x40;
char sprites_id[] = { 0x00, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10 };
char * sprites_ptr_1 = SCREENS_1+OFFSET_SPRITE_PTRS;
char * sprites_ptr_2 = SCREENS_2+OFFSET_SPRITE_PTRS;
for(char screen=0;screen<14;screen++) {
for(char sprite=0; sprite<8; sprite++) {
char sprite_id = SPRITE_ID_0 + screen + sprites_id[sprite];
sprites_ptr_1[sprite] = sprite_id;
sprites_ptr_2[sprite] = sprite_id;
}
sprites_ptr_1 += 0x400;
sprites_ptr_2 += 0x400;
}
}
// 0: intro, 1: game
volatile char phase = 0;
// The double buffer frame (0=BANK_1, 1=BANK_2)
volatile char frame = 0;
// The raster line for irq_screen_top()
const char IRQ_SCREEN_TOP_LINE = 0x05;
// Interrupt Routine at Screen Top
__interrupt void irq_screen_top() {
kickasm(uses HARDWARE_IRQ, uses RASTER, uses IRQ_STATUS, uses IRQ_RASTER, uses VICII_CONTROL1, uses VICII_RSEL, uses VICII_MEMORY, clobbers "AX") {{
// Stabilize the raster by using the double IRQ method
// Acknowledge the IRQ
lda #IRQ_RASTER
sta IRQ_STATUS
// Set-up IRQ for the next line
inc RASTER
// Point IRQ to almost stable code
lda #<stable
sta HARDWARE_IRQ
lda #>stable
sta HARDWARE_IRQ+1
tsx // Save stack pointer
cli // Reenable interrupts
// Wait for new IRQ using NOP's to ensure minimal jitter when it hits
.fill 15, NOP
.align $20
stable:
txs // Restore stack pointer
ldx #9 // Wait till the raster has almost crossed to the next line (48 cycles)
!: dex
bne !-
nop
lda RASTER
cmp RASTER
bne !+ // And correct the last cycle of potential jitter
!:
// Raster is now completely stable! (Line 0x007 cycle 7)
}}
asm {
// Call the screen raster code
jsr RASTER_CODE
}
// Move sprites back to the top
VICII->SPRITE0_Y =7;
VICII->SPRITE1_Y =7;
VICII->SPRITE2_Y =7;
VICII->SPRITE3_Y =7;
VICII->SPRITE4_Y =7;
VICII->SPRITE5_Y =7;
VICII->SPRITE6_Y =7;
VICII->SPRITE7_Y =7;
// Select first screen (graphics bank not important since layout in the banks is identical)
VICII->MEMORY = toD018(SCREENS_1, SCREENS_1);
// Set the top sprites color/MC
VICII->SPRITES_MC = top_sprites_mc;
VICII->SPRITE0_COLOR = top_sprites_color;
VICII->SPRITE1_COLOR = top_sprites_color;
// Move to next frame
frame = (frame+1) & 1;
if(frame) {
// Change graphics bank
CIA2->PORT_A = toDd00(SCREENS_2);
// Set the next canvas base address
canvas_base_hi = BYTE1(SPRITES_1);
bobs_restore_base = 0;
} else {
// Change graphics bank
CIA2->PORT_A = toDd00(SCREENS_1);
// Set the next canvas base address
canvas_base_hi = BYTE1(SPRITES_2);
bobs_restore_base = NUM_BOBS*SIZE_BOB_RESTORE;
}
if(phase==0) {
// intro phase
// Play intro music
music_play_next = 1;
} else {
// Game phase
// Perform game logic
game_logic();
// Play sounds
pacman_sound_play();
}
// Acknowledge the IRQ
VICII->IRQ_STATUS = IRQ_RASTER;
// Trigger IRQ at screen top again
VICII->RASTER = IRQ_SCREEN_TOP_LINE;
*HARDWARE_IRQ = &irq_screen_top;
}
// Movement directions.
// Each active direction is also a bit enabling efficient storage of potential directions from a tile
// The directions are also used as indices into the animation frames for the BOBs.
// The directions are also arranged so they match the C64 joystick #2 CIA#1 PORT A bits shifted twice.
enum DIRECTION {
STOP=0,
UP=4,
DOWN=8,
LEFT=16,
RIGHT=32
};
// Used to choose a single direction when presented with multiple potential directions.
// Used to eliminate diagonal joy directions and convert them to a single direction
// Priority: (4)up, (8)down, (16)left, (32)right
__align(0x40) char DIRECTION_SINGLE[0x40] = {
0x00, 0x00, 0x00, 0x00, // 00
0x04, 0x04, 0x04, 0x04, // 04
0x08, 0x08, 0x08, 0x08, // 08
0x04, 0x04, 0x04, 0x04, // 0c: 4 wins
0x10, 0x10, 0x10, 0x10, // 10
0x04, 0x04, 0x04, 0x04, // 14: 4 wins
0x08, 0x08, 0x08, 0x08, // 18: 8 wins
0x04, 0x04, 0x04, 0x04, // 1c: 4 wins
0x20, 0x20, 0x20, 0x20, // 20
0x04, 0x04, 0x04, 0x04, // 24: 4 wins
0x08, 0x08, 0x08, 0x08, // 28: 8 wins
0x04, 0x04, 0x04, 0x04, // 2c: 4 wins
0x10, 0x10, 0x10, 0x10, // 30: 10 wins
0x04, 0x04, 0x04, 0x04, // 34: 4 wins
0x08, 0x08, 0x08, 0x08, // 38: 8 wins
0x04, 0x04, 0x04, 0x04, // 3c: 4 wins
};
// Used to eliminate a single direction (the one that the ghost came from)
// The value DIRECTION_ELIMINATE[current_direction] is ANDed onto the open directions to remove the current direction
__align(0x40) char DIRECTION_ELIMINATE[0x21] = {
0xff, 0x00, 0x00, 0x00, // 00
0xf7, 0x00, 0x00, 0x00, // 04: Moving Up - eliminate Down from the open directions
0xfb, 0x00, 0x00, 0x00, // 08: Moving Down - eliminate Up from the open directions
0x00, 0x00, 0x00, 0x00, // 0c
0xdf, 0x00, 0x00, 0x00, // 10: Moving Left - eliminate Right from the open directions
0x00, 0x00, 0x00, 0x00, // 14
0x00, 0x00, 0x00, 0x08, // 18
0x00, 0x00, 0x00, 0x00, // 1c
0xef // 20 Moving Right - eliminate Left from the open directions
};
// Used to reverse direction direction (when a ghost changes between chase and scatter)
__align(0x40) char DIRECTION_REVERSE[0x21] = {
0x00, 0x00, 0x00, 0x00, // 00
0x08, 0x00, 0x00, 0x00, // 04: Moving Up
0x04, 0x00, 0x00, 0x00, // 08: Moving Down
0x00, 0x00, 0x00, 0x00, // 0c
0x20, 0x00, 0x00, 0x00, // 10: Moving Left
0x00, 0x00, 0x00, 0x00, // 14
0x00, 0x00, 0x00, 0x08, // 18
0x00, 0x00, 0x00, 0x00, // 1c
0x10 // 20 Moving Right
};
// The animation frames for pacman. The index into this is DIRECTION + anim_frame_idx.
__align(0x40) char pacman_frames[] = {
8, 8, 8, 8, // 0: Stop
8, 24, 20, 24, // 4: Up
8, 32, 28, 32, // 8: Down
0, 0, 0, 0, // Unused
8, 4, 0, 4, // 16: Left
0, 0, 0, 0, // Unused
0, 0, 0, 0, // Unused
0, 0, 0, 0, // Unused
8, 12, 16, 12, // 32: Right
};
// The animation frames for ghost. The index into thos is DIRECTION + anim_frame_idx.
__align(0x80) char ghost_frames[] = {
0, 0, 0, 0, // 00: Stop RED
60, 64, 60, 64, // 04: Up RED
52, 56, 52, 56, // 08: Down RED
0, 0, 0, 0, // 0c: Unused RED
44, 48, 44, 48, // 10: Left RED
0, 0, 0, 0, // 14 Unused RED
0, 0, 0, 0, // 18 Unused RED
0, 0, 0, 0, // 1c Unused RED
36, 40, 36, 40, // 20: Right RED
0, 0, 0, 0, // 24 Unused RED
0, 0, 0, 0, // 28 Unused RED
0, 0, 0, 0, // 2c Unused RED
0, 0, 0, 0, // 30 Unused RED
0, 0, 0, 0, // 34 Unused RED
0, 0, 0, 0, // 38 Unused RED
0, 0, 0, 0, // 3c Unused RED
0, 0, 0, 0, // 40: Stop BLUE
92, 96, 92, 96, // 44: Up BLUE
84, 88, 84, 88, // 48: Down BLUE
0, 0, 0, 0, // 4c Unused BLUE
76, 80, 76, 80, // 50: Left BLUE
0, 0, 0, 0, // 54 Unused BLUE
0, 0, 0, 0, // 58 Unused BLUE
0, 0, 0, 0, // 5c Unused BLUE
68, 72, 68, 72, // 60: Right BLUE
};
// The animation frame IDX (within the current direction) [0-3]
volatile char anim_frame_idx = 0;
// Pacman x fine position (0-99).
volatile char pacman_xfine = 45;
// Pacman y fine position (0-70).
volatile char pacman_yfine = 35;
// The pacman movement current direction
volatile enum DIRECTION pacman_direction = STOP;
// Pacman movement substep (0: on tile, 1: between tiles).
volatile char pacman_substep = 0;
// Ghost target modes
// See https://gameinternals.com/understanding-pac-man-ghost-behavior
enum GHOSTS_MODE {
CHASE=0,
SCATTER=1,
FRIGHTENED=2
};
// Mode determining ghost target mode. 0: chase, 1: scatter
volatile enum GHOSTS_MODE ghosts_mode = 1;
// Counts frames to change ghost mode (7 seconds scatter, 20 seconds chase )
volatile char ghosts_mode_count = 0;
// Ghost 1 x fine position (0-99).
volatile char ghost1_xfine = 45;
// Ghost 1 y fine position (0-70).
volatile char ghost1_yfine = 35;
// Ghost 1 movement current direction
volatile enum DIRECTION ghost1_direction = STOP;
// Ghost 1 movement substep (0: on tile, 1: between tiles).
volatile char ghost1_substep = 0;
// Ghost 1 movement should be reversed (0: normal, 1: reverse direction)
volatile char ghost1_reverse = 0;
// Ghost 1 respawn timer
volatile char ghost1_respawn = 0;
// Ghost 2 x fine position (0-99).
volatile char ghost2_xfine = 45;
// Ghost 2 y fine position (0-70).
volatile char ghost2_yfine = 35;
// Ghost 2 movement current direction
volatile enum DIRECTION ghost2_direction = STOP;
// Ghost 2 movement substep (0: on tile, 1: between tiles).
volatile char ghost2_substep = 0;
// Ghost 2 movement should be reversed (0: normal, 1: reverse direction)
volatile char ghost2_reverse = 0;
// Ghost 2 respawn timer
volatile char ghost2_respawn = 0;
// Ghost 3 x fine position (0-99).
volatile char ghost3_xfine = 45;
// Ghost 3 y fine position (0-70).
volatile char ghost3_yfine = 35;
// Ghost 3 movement current direction
volatile enum DIRECTION ghost3_direction = STOP;
// Ghost 3 movement substep (0: on tile, 1: between tiles).
volatile char ghost3_substep = 0;
// Ghost 3 movement should be reversed (0: normal, 1: reverse direction)
volatile char ghost3_reverse = 0;
// Ghost 3 respawn timer
volatile char ghost3_respawn = 0;
// Ghost 4 x fine position (0-99).
volatile char ghost4_xfine = 45;
// Ghost 4 y fine position (0-70).
volatile char ghost4_yfine = 35;
// Ghost 4 movement current direction
volatile enum DIRECTION ghost4_direction = STOP;
// Ghost 4 movement substep (0: on tile, 1: between tiles).
volatile char ghost4_substep = 0;
// Ghost 4 movement should be reversed (0: normal, 1: reverse direction)
volatile char ghost4_reverse = 0;
// Ghost 4 respawn timer
volatile char ghost4_respawn = 0;
// Game logic sub-step [0-7]. Each frame a different sub-step is animated
volatile char game_logic_substep = 0;
// 1 when the game is playable and characters should move around
volatile char game_playable = 0;
// Perform game logic such as moving pacman and ghosts
void game_logic() {
// Exit if game is not playable
if(game_playable==0) return;
// Move to next sub-step
game_logic_substep = (game_logic_substep+1)&7;
if(game_logic_substep==0) {
// Animate pacman
// Move pacman in the current direction (unless he is stopped)
if(pacman_direction==RIGHT) {
++pacman_xfine;
} else if(pacman_direction==DOWN) {
++pacman_yfine;
} else if(pacman_direction==LEFT) {
--pacman_xfine;
} else if(pacman_direction==UP) {
--pacman_yfine;
}
if(pacman_substep==0 && pacman_direction!=STOP) {
// Pacman was on a tile and is moving, so he is now between tiles
pacman_substep = 1;
// Enable the eating sound whenever pacman is moving
pacman_ch1_enabled = 1;
// Teleport if we are in the magic positions
if(pacman_xfine==1) {
pacman_xfine = 97;
} else if(pacman_xfine==97) {
pacman_xfine = 1;
}
} else {
// Pacman is on a (new) tile
pacman_substep = 0;
// Examine open directions from the new tile to determine next action
char pacman_xtile = pacman_xfine/2;
char pacman_ytile = pacman_yfine/2;
char open_directions = level_tile_directions(pacman_xtile, pacman_ytile);
// Read joystick#2 - arrange bits to match DIRECTION
char joy_directions = ((CIA1->PORT_A & 0x0f)^0x0f)*4;
if(joy_directions!=0) {
char new_direction = DIRECTION_SINGLE[joy_directions&open_directions];
if(new_direction!=0)
pacman_direction = new_direction;
}
// Stop pacman if the current direction is no longer open
pacman_direction &= open_directions;
}
} else if(game_logic_substep==1) {
// Ghost spawn timer
if(ghost1_respawn) {
if(--ghost1_respawn==0) {
// Spawn ghost 1
ghost1_direction = DOWN; ghost1_xfine = 96; ghost1_yfine = 2; ghost1_substep = 0;
}
} else {
// Ghost 1 animation
// Move in the current direction (unless he is stopped)
if(ghost1_direction==RIGHT) {
++ghost1_xfine;
} else if(ghost1_direction==DOWN) {
++ghost1_yfine;
} else if(ghost1_direction==LEFT) {
--ghost1_xfine;
} else if(ghost1_direction==UP) {
--ghost1_yfine;
}
if(ghost1_substep==0 && ghost1_direction!=STOP) {
// Ghost was on a tile and is moving, so he is now between tiles
ghost1_substep = 1;
// Teleport if we are in the magic positions
if(ghost1_xfine==1) {
ghost1_xfine = 97;
} else if(ghost1_xfine==97) {
ghost1_xfine = 1;
}
} else {
// Ghost is on a tile
ghost1_substep = 0;
if(ghost1_reverse) {
// If we are changing between scatter & chase then reverse the direction
ghost1_direction = DIRECTION_REVERSE[ghost1_direction];
ghost1_reverse = 0;
} else {
// Examine open directions from the new tile to determine next action
char ghost1_xtile = ghost1_xfine/2;
char ghost1_ytile = ghost1_yfine/2;
char open_directions = level_tile_directions(ghost1_xtile, ghost1_ytile);
// Eliminate the direction ghost came from
open_directions &= DIRECTION_ELIMINATE[ghost1_direction];
if(ghosts_mode==FRIGHTENED) {
// Choose a random direction between the open directions
ghost1_direction = DIRECTION_SINGLE[open_directions];
} else {
char target_xtile;
char target_ytile;
if(ghosts_mode==SCATTER) {
target_xtile = 2;
target_ytile = 2;
} else {
// "Blinky" Choose the direction bringine me closer to pacman. https://gameinternals.com/understanding-pac-man-ghost-behavior
target_xtile = pacman_xfine/2;
target_ytile = pacman_yfine/2;
}
ghost1_direction = choose_direction( open_directions, ghost1_xtile, ghost1_ytile, target_xtile, target_ytile );
}
}
}
}
} else if(game_logic_substep==2) {
// Ghost spawn timer
if(ghost2_respawn) {
if(--ghost2_respawn==0) {
// Spawn ghost
ghost2_direction = LEFT; ghost2_xfine = 96; ghost2_yfine = 70; ghost2_substep = 0;
}
} else {
// Move in the current direction (unless he is stopped)
if(ghost2_direction==RIGHT) {
++ghost2_xfine;
} else if(ghost2_direction==DOWN) {
++ghost2_yfine;
} else if(ghost2_direction==LEFT) {
--ghost2_xfine;
} else if(ghost2_direction==UP) {
--ghost2_yfine;
}
if(ghost2_substep==0 && ghost2_direction!=STOP) {
// Ghost was on a tile and is moving, so he is now between tiles
ghost2_substep = 1;
// Teleport if we are in the magic positions
if(ghost2_xfine==1) {
ghost2_xfine = 97;
} else if(ghost2_xfine==97) {
ghost2_xfine = 1;
}
} else {
// Ghost is on a tile
ghost2_substep = 0;
if(ghost2_reverse) {
// If we are changing between scatter & chase then reverse the direction
ghost2_direction = DIRECTION_REVERSE[ghost2_direction];
ghost2_reverse = 0;
} else {
// Examine open directions from the new tile to determine next action
char ghost2_xtile = ghost2_xfine/2;
char ghost2_ytile = ghost2_yfine/2;
char open_directions = level_tile_directions(ghost2_xtile, ghost2_ytile);
// Eliminate the direction ghost came from
open_directions &= DIRECTION_ELIMINATE[ghost2_direction];
if(ghosts_mode==FRIGHTENED) {
// Choose a random direction between the open directions
ghost2_direction = DIRECTION_SINGLE[open_directions];
} else {
char target_xtile;
char target_ytile;
if(ghosts_mode==SCATTER) {
target_xtile = 2;
target_ytile = 2;
} else {
// "Blinky" Choose the direction bringine me closer to pacman. https://gameinternals.com/understanding-pac-man-ghost-behavior
target_xtile = pacman_xfine/2;
target_ytile = pacman_yfine/2;
}
ghost2_direction = choose_direction( open_directions, ghost2_xtile, ghost2_ytile, target_xtile, target_ytile );
}
}
}
}
} else if(game_logic_substep==4) {
// Ghost spawn timer
if(ghost3_respawn) {
if(--ghost3_respawn==0) {
// Spawn ghost
ghost3_direction = UP; ghost3_xfine = 2; ghost3_yfine = 70; ghost3_substep = 0;
}
} else {
// Move in the current direction (unless he is stopped)
if(ghost3_direction==RIGHT) {
++ghost3_xfine;
} else if(ghost3_direction==DOWN) {
++ghost3_yfine;
} else if(ghost3_direction==LEFT) {
--ghost3_xfine;
} else if(ghost3_direction==UP) {
--ghost3_yfine;
}
if(ghost3_substep==0 && ghost3_direction!=STOP) {
// Ghost was on a tile and is moving, so he is now between tiles
ghost3_substep = 1;
// Teleport if we are in the magic positions
if(ghost3_xfine==1) {
ghost3_xfine = 97;
} else if(ghost3_xfine==97) {
ghost3_xfine = 1;
}
} else {
// Ghost is on a tile
ghost3_substep = 0;
if(ghost3_reverse) {
// If we are changing between scatter & chase then reverse the direction
ghost3_direction = DIRECTION_REVERSE[ghost3_direction];
ghost3_reverse = 0;
} else {
// Examine open directions from the new tile to determine next action
char ghost3_xtile = ghost3_xfine/2;
char ghost3_ytile = ghost3_yfine/2;
char open_directions = level_tile_directions(ghost3_xtile, ghost3_ytile);
// Eliminate the direction ghost came from
open_directions &= DIRECTION_ELIMINATE[ghost3_direction];
if(ghosts_mode==FRIGHTENED) {
// Choose a random direction between the open directions
ghost3_direction = DIRECTION_SINGLE[open_directions];
} else {
char target_xtile;
char target_ytile;
if(ghosts_mode==SCATTER) {
target_xtile = 2;
target_ytile = 2;
} else {
// "Blinky" Choose the direction bringine me closer to pacman. https://gameinternals.com/understanding-pac-man-ghost-behavior
target_xtile = pacman_xfine/2;
target_ytile = pacman_yfine/2;
}
ghost3_direction = choose_direction( open_directions, ghost3_xtile, ghost3_ytile, target_xtile, target_ytile );
}
}
}
}
} else if(game_logic_substep==5) {
// Ghost spawn timer
if(ghost4_respawn) {
if(--ghost4_respawn==0) {
// Spawn ghost
ghost4_direction = RIGHT; ghost4_xfine = 2; ghost4_yfine = 2; ghost4_substep = 0;
}
} else {
// Move in the current direction (unless he is stopped)
if(ghost4_direction==RIGHT) {
++ghost4_xfine;
} else if(ghost4_direction==DOWN) {
++ghost4_yfine;
} else if(ghost4_direction==LEFT) {
--ghost4_xfine;
} else if(ghost4_direction==UP) {
--ghost4_yfine;
}
if(ghost4_substep==0 && ghost4_direction!=STOP) {
// Ghost was on a tile and is moving, so he is now between tiles
ghost4_substep = 1;
// Teleport if we are in the magic positions
if(ghost4_xfine==1) {
ghost4_xfine = 97;
} else if(ghost4_xfine==97) {
ghost4_xfine = 1;
}
} else {
// Ghost is on a tile
ghost4_substep = 0;
if(ghost4_reverse) {
// If we are changing between scatter & chase then reverse the direction
ghost4_direction = DIRECTION_REVERSE[ghost4_direction];
ghost4_reverse = 0;
} else {
// Examine open directions from the new tile to determine next action
char ghost4_xtile = ghost4_xfine/2;
char ghost4_ytile = ghost4_yfine/2;
char open_directions = level_tile_directions(ghost4_xtile, ghost4_ytile);
// Eliminate the direction ghost came from
open_directions &= DIRECTION_ELIMINATE[ghost4_direction];
if(ghosts_mode==FRIGHTENED) {
// Choose a random direction between the open directions
ghost4_direction = DIRECTION_SINGLE[open_directions];
} else {
char target_xtile;
char target_ytile;
if(ghosts_mode==SCATTER) {
target_xtile = 2;
target_ytile = 2;
} else {
// "Blinky" Choose the direction bringine me closer to pacman. https://gameinternals.com/understanding-pac-man-ghost-behavior
target_xtile = pacman_xfine/2;
target_ytile = pacman_yfine/2;
}
ghost4_direction = choose_direction( open_directions, ghost4_xtile, ghost4_ytile, target_xtile, target_ytile );
}
}
}
}
} else if(game_logic_substep==6) {
// Update ghosts mode
ghosts_mode_count++;
char do_reverse = 0;
if(ghosts_mode==SCATTER) {
if(ghosts_mode_count>50) {
ghosts_mode = CHASE;
ghosts_mode_count = 0;
do_reverse = 1;
}
} else if(ghosts_mode==CHASE) {
if(ghosts_mode_count>150) {
ghosts_mode = SCATTER;
ghosts_mode_count = 0;
do_reverse = 1;
}
} else if(ghosts_mode==FRIGHTENED) {
// Frightened mode
if(ghosts_mode_count>50) {
ghosts_mode = CHASE;
ghosts_mode_count = 0;
do_reverse = 1;
}
}
// Reverse direction for all ghosts if needed
if(do_reverse) {
ghost1_reverse = 1;
ghost2_reverse = 1;
ghost3_reverse = 1;
ghost4_reverse = 1;
}
// Examine if pacman is on a pill tile - and handle it
char pacman_xtile = pacman_xfine/2;
char pacman_ytile = pacman_yfine/2;
char* ytiles = LEVEL_TILES + LEVEL_YTILE_OFFSET[pacman_ytile];
char tile_id = ytiles[pacman_xtile];
if(TILES_TYPE[tile_id]==PILL) {
// Empty the tile
ytiles[pacman_xtile] = EMPTY;
// Ask the logic code renderer to update the tile
logic_tile_xcol = pacman_xtile/2;
logic_tile_ptr = ytiles + (pacman_xtile & 0xfe);
logic_tile_yfine = pacman_ytile*2;
// Decrease the number of pills
if(--pill_count==0)
pacman_wins = 1;;
} else if(TILES_TYPE[tile_id]==POWERUP) {
// Empty the tile
ytiles[pacman_xtile] = EMPTY;
// Ask the logic code renderer to update the tile
logic_tile_xcol = pacman_xtile/2;
logic_tile_ptr = ytiles + (pacman_xtile & 0xfe);
logic_tile_yfine = pacman_ytile*2;
// Start power-up mode
ghosts_mode = FRIGHTENED;
ghosts_mode_count = 0;
}
// Check if anyone dies
if(ABS[pacman_xfine-ghost1_xfine]<2 && ABS[pacman_yfine-ghost1_yfine]<2) {
if(ghosts_mode==FRIGHTENED) {
// ghost dies
ghost1_direction = STOP; ghost1_xfine = 50; ghost1_yfine = 35; ghost1_substep = 0; ghost1_respawn = 50;
} else {
// pacman dies
pacman_lives--;
spawn_all();
}
} else if(ABS[pacman_xfine-ghost2_xfine]<2 && ABS[pacman_yfine-ghost2_yfine]<2) {
if(ghosts_mode==FRIGHTENED) {
ghost2_direction = STOP; ghost2_xfine = 50; ghost2_yfine = 35; ghost2_substep = 0; ghost2_respawn = 50;
} else {
// pacman dies
pacman_lives--;
spawn_all();
}
} else if(ABS[pacman_xfine-ghost3_xfine]<2 && ABS[pacman_yfine-ghost3_yfine]<2) {
if(ghosts_mode==FRIGHTENED) {
ghost3_direction = STOP; ghost3_xfine = 50; ghost3_yfine = 35; ghost3_substep = 0; ghost3_respawn = 50;
} else {
// pacman dies
pacman_lives--;
spawn_all();
}
} else if(ABS[pacman_xfine-ghost4_xfine]<2 && ABS[pacman_yfine-ghost4_yfine]<2) {
if(ghosts_mode==FRIGHTENED) {
ghost4_direction = STOP; ghost4_xfine = 50; ghost4_yfine = 35; ghost4_substep = 0; ghost4_respawn = 50;
} else {
// pacman dies
pacman_lives--;
spawn_all();
}
}
} else if(game_logic_substep==3 || game_logic_substep==7) {
// Update animation and bobs
anim_frame_idx = (anim_frame_idx+1) & 3;
char pacman_bob_xfine = pacman_xfine-1;
bobs_xcol[0] = pacman_bob_xfine/4;
bobs_yfine[0] = pacman_yfine-1;
bobs_bob_id[0] = pacman_frames[pacman_direction|anim_frame_idx] + (pacman_bob_xfine&3);
char ghost_frame_idx = anim_frame_idx;
if(ghosts_mode==FRIGHTENED)
ghost_frame_idx |= 0x40;
char ghost1_bob_xfine = ghost1_xfine-1;
bobs_xcol[1] = ghost1_bob_xfine/4;
bobs_yfine[1] = ghost1_yfine-1;
bobs_bob_id[1] = ghost_frames[ghost1_direction|ghost_frame_idx] + (ghost1_bob_xfine&3);
char ghost2_bob_xfine = ghost2_xfine-1;
bobs_xcol[2] = ghost2_bob_xfine/4;
bobs_yfine[2] = ghost2_yfine-1;
bobs_bob_id[2] = ghost_frames[ghost2_direction|ghost_frame_idx] + (ghost2_bob_xfine&3);
char ghost3_bob_xfine = ghost3_xfine-1;
bobs_xcol[3] = ghost3_bob_xfine/4;
bobs_yfine[3] = ghost3_yfine-1;
bobs_bob_id[3] = ghost_frames[ghost3_direction|ghost_frame_idx] + (ghost3_bob_xfine&3);
char ghost4_bob_xfine = ghost4_xfine-1;
bobs_xcol[4] = ghost4_bob_xfine/4;
bobs_yfine[4] = ghost4_yfine-1;
bobs_bob_id[4] = ghost_frames[ghost4_direction|ghost_frame_idx] + (ghost4_bob_xfine&3);
}
}
// Lookup the absolute value of a signed number
// PRE_ and POST_ are used to ensure lookup of ABS-1,y works for y=0 and ABS+1,y works for y=0xff
__export __align(0x100) char ABS_PRE[1] = { 1 };
__export char ABS[0x100] = kickasm {{
.for(var i=0;i<$100;i++) {
.var x = (i<$80)?i:($100-i);
.byte abs(x)
}
}};
__export char ABS_POST[1] = { 0 };
// Choose the open direction that brings the ghost closest to the target
// Uses Manhattan distance calculation
char choose_direction( char open_directions, char ghost_xtile, char ghost_ytile, char target_xtile, char target_ytile ) {
char xdiff = ghost_xtile-target_xtile;
char ydiff = ghost_ytile-target_ytile;
char direction = STOP;
char dist_min = 0xff;
if(open_directions&UP) {
char dist_up = ABS[xdiff] + ABS[ydiff-1];
if(dist_up<dist_min) {
direction = UP;
dist_min = dist_up;
}
}
if(open_directions&DOWN) {
char dist_down = ABS[xdiff] + ABS[ydiff+1];
if(dist_down<dist_min) {
direction = DOWN;
dist_min = dist_down;
}
}
if(open_directions&LEFT) {
char dist_left = ABS[xdiff-1] + ABS[ydiff];
if(dist_left<dist_min) {
direction = LEFT;
dist_min = dist_left;
}
}
if(open_directions&RIGHT) {
char dist_right = ABS[xdiff+1] + ABS[ydiff];
if(dist_right<dist_min) {
direction = RIGHT;
dist_min = dist_right;
}
}
return direction;
}
// Spawn pacman and all ghosts
void spawn_all() {
ghosts_mode_count = 0;
pacman_substep = 0;
ghost1_substep = 0;
ghost2_substep = 0;
ghost3_substep = 0;
ghost4_substep = 0;
pacman_direction = STOP;
ghost1_direction = STOP;
ghost2_direction = STOP;
ghost3_direction = STOP;
ghost4_direction = STOP;
pacman_xfine = 50;
ghost1_xfine = 50;
ghost2_xfine = 50;
ghost3_xfine = 50;
ghost4_xfine = 50;
ghost1_yfine = 35;
ghost2_yfine = 35;
ghost3_yfine = 35;
ghost4_yfine = 35;
pacman_yfine = 62;
ghost1_respawn = 10;
ghost2_respawn = 20;
ghost3_respawn = 30;
ghost4_respawn = 40;
}