Add Game Boy presets for Chase and Paku Paku, update hello.sgb paths, and improve palette handling

This commit is contained in:
MikeDX committed 2026-07-31 00:02:05 +01:00
1 parent 3c73a8ab20
commit 23cf476d31
16 files changed
+2140 -20

No files matched your search

+1430
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef __BCD_H_INCLUDE
#define __BCD_H_INCLUDE
#include <types.h>
#include "types.h"
#include <stdint.h>
/** @file bcd.h
+1 -1
View File
@@ -17,7 +17,7 @@
#ifndef _CGB_H
#define _CGB_H
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Macro to create a CGB palette color entry out of 5-bit color components.
+2 -2
View File
@@ -30,7 +30,7 @@
#ifndef __DRAWING_H
#define __DRAWING_H
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Size of the screen in pixels */
@@ -56,7 +56,7 @@
#define SIGNED 1
#define UNSIGNED 0
#include <types.h>
#include "types.h"
/** Print the string 'str' with no interpretation
@see gotogxy()
+1 -1
View File
@@ -12,7 +12,7 @@
#ifndef __EMU_DEBUG_INCLUDE
#define __EMU_DEBUG_INCLUDE
#include <types.h>
#include "types.h"
/** Macro to display a message in the emulator debug message window
+1 -1
View File
@@ -8,7 +8,7 @@
#ifndef __GBDECOMPRESS_H_INCLUDE
#define __GBDECOMPRESS_H_INCLUDE
#include <types.h>
#include "types.h"
#include <stdint.h>
/** gb-decompress data from sour into dest
+1 -1
View File
@@ -10,7 +10,7 @@
#define _ISR_H_INCLUDE_
#include <stdint.h>
#include <types.h>
#include "types.h"
// #define VECTOR_VBL 0x40 // you can not define raw vector for VBlank interrupt
#define VECTOR_STAT 0x48 /**< Address for the STAT interrupt vector */
+1 -1
View File
@@ -68,7 +68,7 @@
#define _METASPRITES_H_INCLUDE
#include <gb/hardware.h>
#include <types.h>
#include "types.h"
#include <stdint.h>
/** Metasprite sub-item structure
+1 -1
View File
@@ -6,7 +6,7 @@
#ifndef _SGB_H
#define _SGB_H
#include <types.h>
#include "types.h"
#include <stdint.h>
#define SGB_PAL_01 0x00U /**< SGB Command: Set SGB Palettes 0 & 1 */
+1 -1
View File
@@ -1,5 +1,5 @@
.include "gingerbread.sgb"
.include "gb/gingerbread.sgb"
; Set the size of the ROM file here. 0 means 32 kB, 1 means 64 kB, 2 means 128 kB and so on.
ROM_SIZE = 1
+631
View File
@@ -0,0 +1,631 @@
/*
* Paku Paku for Game Boy — port of presets/pacman/pakupaku.c
*
* One-lane chase inspired by crisp-game-lib "PAKU PAKU":
* A / Start = reverse Left / Right = face that way
* Collect dots; power pellet makes the ghost edible.
* Movement/AI match the Pac-Man hardware port; graphics from pacman.5e/5f
* (regenerate with: python3 scripts/gen_gb_pakupaku_gfx.py).
*/
//#link "gb/sfr.sgb"
//#link "gb/crt0.sgb"
//#resource "gb/global.sgb"
#include <stdint.h>
#include "gb/types.h"
#include "gb/hardware.h"
#include "gb/gb.h"
typedef uint8_t byte;
typedef uint16_t word;
typedef int8_t sbyte;
#define FIELD_MAX 152 /* arcade 208 scaled to GB width */
#define PLAYER_SPD 2
#define NUM_DOTS 18
#define HIT_DIST 12
#define ROW_ABOVE 8
#define DOT_TILE_Y 9
#define ROW_BELOW 10
#define CORRIDOR_Y 68
/* BG tiles from pacman.5e (after font 0..34) */
#define BKG_BASE 40
#define T_WALL_ABOVE (BKG_BASE + 0)
#define T_WALL_BELOW (BKG_BASE + 1)
#define T_DOT (BKG_BASE + 2)
#define T_POWER (BKG_BASE + 3)
/*
* Arcade pacman.5f sprites → GB 16×16 (two 8×16 OAM each).
* Each frame is 4 tiles at SPR_BASE: L-top, L-bot, R-top, R-bot.
* Regenerate: python3 scripts/gen_gb_pakupaku_gfx.py
*/
#define SPR_BASE 48
#define SPR_FRAME(i) ((byte)(SPR_BASE + (i) * 4))
#define SP_OPEN SPR_FRAME(0)
#define SP_MID SPR_FRAME(1)
#define SP_CLOSED SPR_FRAME(2)
#define SP_DEATH0 SPR_FRAME(3) /* 11 frames: 3..13 */
#define SP_DEATH_N 11
#define SP_GHOST0 SPR_FRAME(14)
#define SP_GHOST1 SPR_FRAME(15)
#define SP_SCARED0 SPR_FRAME(16)
#define SP_SCARED1 SPR_FRAME(17)
#define SP_EYES SPR_FRAME(18)
#define SPR_TILE_COUNT 76
typedef struct {
byte tx;
byte active;
byte is_power;
} Dot;
word rnd = 0xCACE;
word score;
byte multiplier;
byte player_x;
sbyte player_vx;
byte enemy_x;
sbyte enemy_eye_vx;
word power_ticks;
word anim_ticks;
byte btn_prev;
byte game_over;
byte waka;
Dot dots[NUM_DOTS];
byte joy_left, joy_right, joy_fire, joy_start;
byte sfx_timer;
byte sfx_kind; /* 0=off 1/2=waka 3=eat 4=death 6=power */
byte death_phase;
const uint8_t font_1bpp[] = {
/* 0 space */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* 1 '0' */ 0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00,
/* 2 '1' */ 0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00,
/* 3 '2' */ 0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00,
/* 4 '3' */ 0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00,
/* 5 '4' */ 0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00,
/* 6 '5' */ 0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00,
/* 7 '6' */ 0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00,
/* 8 '7' */ 0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00,
/* 9 '8' */ 0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00,
/*10 '9' */ 0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00,
/*11 'A' */ 0x3C,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
/*12 'C' */ 0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00,
/*13 'E' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00,
/*14 'F' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00,
/*15 'G' */ 0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00,
/*16 'H' */ 0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
/*17 'I' */ 0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,
/*18 'K' */ 0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00,
/*19 'L' */ 0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00,
/*20 'M' */ 0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00,
/*21 'N' */ 0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00,
/*22 'O' */ 0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
/*23 'P' */ 0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00,
/*24 'R' */ 0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00,
/*25 'S' */ 0x3C,0x66,0x60,0x3C,0x06,0x66,0x3C,0x00,
/*26 'T' */ 0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00,
/*27 'U' */ 0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
/*28 'V' */ 0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00,
/*29 'W' */ 0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00,
/*30 'X' */ 0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00,
/*31 'D' */ 0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00,
/*32 '-' */ 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,
/*33 '!' */ 0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00,
/*34 '/' */ 0x06,0x06,0x0C,0x18,0x30,0x60,0x60,0x00,
};
const uint8_t bkg_tiles[] = {
/*{w:8,h:8,bpp:1,count:4,brev:1,np:2,pofs:1,sl:2}*/
0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x3c,0x00,0x7e,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x7e,0x00,0x3c,0x00,
};
const uint8_t sprite_tiles[] = {
/*{w:8,h:8,bpp:1,count:76,brev:1,np:2,pofs:1,sl:2}*/
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7e,0x7e,0x7c,0x7c,0x78,0x78,
0x7c,0x7c,0x7e,0x7e,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x78,0x78,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,
0x00,0x00,0xe0,0xe0,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x70,0x70,0x78,0x78,0x7c,0x7c,0x7e,0x7e,
0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x1c,0x1c,0x3c,0x3c,0x7c,0x7c,0xfc,0xfc,
0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0xf0,0xf0,0xf8,0xf8,0xfe,0xfe,
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x1e,0x1e,0x3e,0x3e,0xfe,0xfe,
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0xfc,0xfc,
0xff,0xff,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x0e,0x7e,0x7e,
0xfe,0xfe,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,
0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0x1e,
0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0f,0x0f,0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xe0,0xe0,0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x03,0x0f,0x0f,0x7f,0x7f,0xff,0xff,0x7e,0x7e,0x1c,0x1c,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0xe0,0xe0,0xfc,0xfc,0xfe,0xfe,0xfc,0xfc,0x70,0x70,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x7f,0x7f,0x7e,0x7e,0x3c,0x3c,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x80,0xe0,0xe0,0xf0,0xf0,0xfc,0xfc,0xfc,0xfc,0x78,0x78,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x03,0x03,0x07,0x07,0x07,0x07,0x0f,0x0f,0x1f,0x1f,0x0e,0x0e,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0xe0,0xe0,0xf0,0xf0,0xe0,0xe0,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x01,0x03,0x03,0x03,0x03,0x03,0x03,0x07,0x07,0x02,0x02,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0xc0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x12,0x12,
0x08,0x08,0x00,0x00,0x30,0x30,0x00,0x00,0x08,0x08,0x10,0x10,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80,0x80,
0x10,0x10,0x20,0x20,0x00,0x00,0x18,0x18,0x00,0x00,0x20,0x20,0x90,0x90,0x40,0x40,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x0f,0x00,0x0c,0x00,0x0c,0x00,
0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x3c,0x00,0x30,0x00,0x30,0x00,
0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
byte rand8(void) {
rnd = rnd * 17 + 53;
return (byte)(rnd >> 8);
}
byte abs_diff(byte a, byte b) {
return (a > b) ? (byte)(a - b) : (byte)(b - a);
}
byte glyph(char c) {
if (c == ' ') return 0;
if (c >= '0' && c <= '9') return (byte)(1 + (c - '0'));
switch (c) {
case 'A': return 11; case 'C': return 12; case 'E': return 13;
case 'F': return 14; case 'G': return 15; case 'H': return 16;
case 'I': return 17; case 'K': return 18; case 'L': return 19;
case 'M': return 20; case 'N': return 21; case 'O': return 22;
case 'P': return 23; case 'R': return 24; case 'S': return 25;
case 'T': return 26; case 'U': return 27; case 'V': return 28;
case 'W': return 29; case 'X': return 30; case 'D': return 31;
case '-': return 32; case '!': return 33; case '/': return 34;
default: return 0;
}
}
void put_str(byte x, byte y, const char* s) {
while (*s) set_bkg_tile_xy(x++, y, glyph(*s++));
}
void put_digit(byte x, byte y, byte d) {
set_bkg_tile_xy(x, y, (byte)(1 + (d % 10)));
}
void clrscr(void) {
byte x, y;
for (y = 0; y < 32; y++)
for (x = 0; x < 32; x++)
set_bkg_tile_xy(x, y, 0);
}
void hide_all_sprites(void) {
byte i;
for (i = 0; i < 40; i++) move_sprite(i, 0, 0);
}
void read_controls(void) {
byte j = joypad();
joy_left = (j & J_LEFT) != 0;
joy_right = (j & J_RIGHT) != 0;
joy_fire = (j & J_A) != 0;
joy_start = (j & J_START) != 0;
}
void psg_init(void) {
NR52_REG = 0x80;
NR50_REG = 0x77;
NR51_REG = 0xFF;
NR10_REG = 0x00;
}
void sfx_off(void) {
sfx_timer = 0;
sfx_kind = 0;
NR12_REG = 0x00;
NR14_REG = 0x80;
NR22_REG = 0x00;
NR24_REG = 0x80;
}
void beep(word per, byte vol, byte len) {
/* ch1 one-shot */
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = (byte)((vol << 4) | 0x01);
NR13_REG = (byte)(per & 0xff);
NR14_REG = (byte)(0x80 | ((per >> 8) & 0x07));
sfx_timer = len;
}
void play_sfx(byte id) {
sfx_kind = id;
if (id == 4) death_phase = 0;
switch (id) {
case 1: beep(0x6C0, 10, 3); break; /* waka hi */
case 2: beep(0x650, 9, 3); break; /* waka lo */
case 3: beep(0x700, 12, 12); break; /* eat ghost */
case 4: beep(0x480, 12, 40); break; /* death */
case 6: beep(0x720, 11, 8); break; /* power */
default: sfx_off(); break;
}
}
void tick_sfx(void) {
if (!sfx_timer) return;
--sfx_timer;
if (!sfx_timer && sfx_kind == 4) {
death_phase++;
if (death_phase < 4) {
beep((word)(0x480 - death_phase * 0x40), 10, 10);
sfx_kind = 4;
} else {
death_phase = 0;
sfx_kind = 0;
}
}
}
void wait_vblank(void) {
wait_vbl_done();
tick_sfx();
}
void wait_frames(byte n) {
while (n--) wait_vblank();
}
void wait_for_button(void) {
read_controls();
while (!joy_fire && !joy_start) { wait_vblank(); read_controls(); }
while (joy_fire || joy_start) { wait_vblank(); read_controls(); }
}
void draw_hud(void) {
put_str(0, 0, "PAKU PAKU");
put_str(0, 1, "SCORE");
put_digit(6, 1, (byte)((score / 1000) % 10));
put_digit(7, 1, (byte)((score / 100) % 10));
put_digit(8, 1, (byte)((score / 10) % 10));
put_digit(9, 1, (byte)(score % 10));
put_str(12, 1, "X");
put_digit(13, 1, multiplier > 9 ? 9 : multiplier);
put_str(0, 17, "A=TURN");
}
void draw_corridor(void) {
byte x;
for (x = 0; x < 20; x++) {
set_bkg_tile_xy(x, ROW_ABOVE, T_WALL_ABOVE);
set_bkg_tile_xy(x, DOT_TILE_Y, 0);
set_bkg_tile_xy(x, ROW_BELOW, T_WALL_BELOW);
}
}
void add_dots(void) {
byte i, pi;
pi = (player_x > 80) ? (byte)(1 + (rand8() % 7)) : (byte)(10 + (rand8() % 7));
for (i = 0; i < NUM_DOTS; i++) {
dots[i].tx = (byte)(i + 1);
dots[i].active = 1;
dots[i].is_power = (i == pi);
}
if (multiplier < 99) multiplier++;
}
void draw_dots(void) {
byte i;
for (i = 0; i < NUM_DOTS; i++) {
if (!dots[i].active) {
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
continue;
}
if (dots[i].is_power) {
if ((anim_ticks >> 3) & 1)
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_POWER);
else
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
} else {
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_DOT);
}
}
}
void collect_dots(void) {
byte i, ptx;
ptx = (byte)((player_x + 8) >> 3);
if (ptx < 1) ptx = 1;
if (ptx > NUM_DOTS) ptx = NUM_DOTS;
for (i = 0; i < NUM_DOTS; i++) {
if (!dots[i].active) continue;
if (dots[i].tx != ptx) continue;
dots[i].active = 0;
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
if (dots[i].is_power) {
play_sfx(6);
if (!enemy_eye_vx) power_ticks = 200;
} else {
waka ^= 1;
play_sfx(waka ? 1 : 2);
}
score += multiplier;
}
}
byte dots_left(void) {
byte i, n = 0;
for (i = 0; i < NUM_DOTS; i++)
if (dots[i].active) n++;
return n;
}
byte pac_frame(void) {
byte ai = (byte)(anim_ticks & 3);
if (ai == 0) return SP_OPEN;
if (ai == 1) return SP_MID;
if (ai == 2) return SP_CLOSED;
return SP_MID;
}
void put_actor16(byte i0, byte frame, byte flip, byte prop, byte x, byte y) {
/* Two 8×16 sprites = one 16×16 arcade sprite; swap halves when flipped. */
byte i1 = (byte)(i0 + 1);
byte p = prop;
byte oam0, oam1;
if (flip) p |= S_FLIPX;
set_sprite_tile(i0, frame);
set_sprite_tile(i1, (byte)(frame + 2));
set_sprite_prop(i0, p);
set_sprite_prop(i1, p);
if (flip) {
oam0 = (byte)(x + 16);
oam1 = (byte)(x + 8);
} else {
oam0 = (byte)(x + 8);
oam1 = (byte)(x + 16);
}
/* Hide if past the right edge (OAM X wraps). */
if (x > 152) {
move_sprite(i0, 0, 0);
move_sprite(i1, 0, 0);
return;
}
move_sprite(i0, oam0, (byte)(y + 16));
move_sprite(i1, oam1, (byte)(y + 16));
}
void draw_actors(void) {
byte ghost, gprop, pflip, gflip;
sbyte evx;
pflip = (player_vx < 0) ? 1 : 0;
put_actor16(0, pac_frame(), pflip, 0, player_x, CORRIDOR_Y);
if (enemy_eye_vx) {
ghost = SP_EYES;
gflip = (enemy_eye_vx < 0) ? 1 : 0;
gprop = 0;
} else if (power_ticks) {
ghost = ((anim_ticks >> 1) & 1) ? SP_SCARED1 : SP_SCARED0;
gflip = 0;
gprop = 0;
/* blink near end of power */
if (power_ticks < 60 && (anim_ticks & 0x08)) {
move_sprite(2, 0, 0);
move_sprite(3, 0, 0);
return;
}
} else {
ghost = ((anim_ticks >> 1) & 1) ? SP_GHOST1 : SP_GHOST0;
evx = (player_x > enemy_x) ? 1 : -1;
gflip = (evx < 0) ? 1 : 0;
gprop = 0;
}
put_actor16(2, ghost, gflip, gprop, enemy_x, CORRIDOR_Y);
}
void title_screen(void) {
byte x;
clrscr();
hide_all_sprites();
move_bkg(0, 0);
/* corridor stripe behind the demo sprites */
for (x = 0; x < 20; x++) {
set_bkg_tile_xy(x, 6, T_WALL_ABOVE);
set_bkg_tile_xy(x, 7, 0);
set_bkg_tile_xy(x, 8, T_WALL_BELOW);
}
put_str(5, 2, "PAKU PAKU");
put_actor16(0, SP_OPEN, 0, 0, 52, 48);
put_actor16(2, SP_GHOST0, 0, 0, 92, 48);
put_str(4, 11, "A TO TURN");
put_str(2, 13, "EAT DOTS EAT GHOST");
put_str(4, 16, "PRESS START");
wait_for_button();
sfx_off();
}
void show_game_over(void) {
byte frame_i, t, frame;
hide_all_sprites();
put_actor16(0, SP_OPEN, 0, 0, player_x, CORRIDOR_Y);
play_sfx(4);
/* Arcade: 11 frames × 8 vblanks */
for (frame_i = 0; frame_i < SP_DEATH_N; frame_i++) {
frame = (byte)(SP_DEATH0 + (byte)(frame_i * 4));
for (t = 0; t < 8; t++) {
put_actor16(0, frame, 0, 0, player_x, CORRIDOR_Y);
wait_vblank();
read_controls();
if (joy_fire || joy_start) goto death_done;
}
}
death_done:
hide_all_sprites();
put_str(5, 7, "GAME OVER");
put_str(4, 10, "PRESS START");
wait_for_button();
sfx_off();
}
void reset_round(void) {
player_x = 48;
player_vx = 1;
enemy_x = 128;
enemy_eye_vx = 0;
power_ticks = 0;
anim_ticks = 0;
btn_prev = 1;
game_over = 0;
waka = 0;
sfx_off();
draw_corridor();
add_dots();
}
void game_loop(void) {
byte btn;
sbyte evx;
byte espd;
clrscr();
hide_all_sprites();
move_bkg(0, 0);
draw_corridor();
draw_hud();
reset_round();
while (!game_over) {
wait_vblank();
anim_ticks++;
read_controls();
btn = (joy_fire || joy_start) ? 1 : 0;
if (btn && !btn_prev) player_vx = (sbyte)-player_vx;
btn_prev = btn;
if (joy_left) player_vx = -1;
if (joy_right) player_vx = 1;
/* Same wrap as pacman pakupaku.c */
player_x = (byte)(player_x + player_vx * PLAYER_SPD);
if (player_x < 4) player_x = FIELD_MAX;
else if (player_x > FIELD_MAX) player_x = 4;
collect_dots();
if (!dots_left()) add_dots();
/* Ghost AI — identical rules to presets/pacman/pakupaku.c */
if (enemy_eye_vx) {
evx = enemy_eye_vx;
espd = 3;
} else if (power_ticks) {
evx = (player_x > enemy_x) ? (sbyte)-1 : 1;
espd = 1;
} else {
evx = (player_x > enemy_x) ? 1 : -1;
espd = 2;
}
if (evx > 0) {
if (enemy_x < FIELD_MAX - 2) enemy_x = (byte)(enemy_x + espd);
else if (enemy_eye_vx) enemy_eye_vx = 0;
} else {
if (enemy_x > 6) enemy_x = (byte)(enemy_x - espd);
else if (enemy_eye_vx) enemy_eye_vx = 0;
}
if (!enemy_eye_vx && abs_diff(player_x, enemy_x) < HIT_DIST) {
if (power_ticks) {
play_sfx(3);
score += (word)(10 * (multiplier ? multiplier : 1));
enemy_eye_vx = (player_x > 80) ? (sbyte)-1 : 1;
power_ticks = 0;
if (multiplier < 99) multiplier++;
} else {
game_over = 1;
}
}
if (power_ticks) power_ticks--;
draw_dots();
draw_hud();
draw_actors();
}
}
void setup_graphics(void) {
DISPLAY_OFF;
set_bkg_1bpp_data(0, 35, font_1bpp);
set_bkg_data(BKG_BASE, 4, bkg_tiles);
set_sprite_data(SPR_BASE, SPR_TILE_COUNT, sprite_tiles);
SPRITES_8x16;
BGP_REG = 0xE4;
OBP0_REG = 0xE4;
OBP1_REG = 0xE4;
psg_init();
HIDE_WIN;
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
}
void main(void) {
setup_graphics();
score = 0;
multiplier = 0;
while (1) {
title_screen();
score = 0;
multiplier = 0;
game_loop();
show_game_over();
}
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Shared Game Boy DMG (olive LCD) palette.
*
* Shade 0 = lightest (LCD pixel off / "white"), shade 3 = darkest (pixel on).
* Values are packed for Canvas ImageData as Uint32 little-endian RGBA (= 0xAABBGGRR).
*/
export function rgbToPixel(r: number, g: number, b: number): number {
return 0xFF000000 | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff);
}
/** Four DMG shades, lightest → darkest (matches BGP/OBP shade indices). */
export const DMG_PALETTE: number[] = [
rgbToPixel(0xd0, 0xd8, 0x84), // Lightest (warm mint)
rgbToPixel(0x87, 0xa8, 0x4c), // Light mid
rgbToPixel(0x52, 0x6b, 0x34), // Dark mid
rgbToPixel(0x2d, 0x31, 0x22), // Darkest (deep olive)
];
/** RGB888 triples matching DMG_PALETTE (for boytacean remap / tooling). */
export const DMG_PALETTE_RGB: [number, number, number][] = [
[0xd0, 0xd8, 0x84],
[0x87, 0xa8, 0x4c],
[0x52, 0x6b, 0x34],
[0x2d, 0x31, 0x22],
];
/** Asset-editor / PREDEF_PALETTES entries (RGB without alpha; callers OR 0xff000000). */
export const DMG_PALETTE_RGB24: number[] = [
0xd0d884,
0x87a84c,
0x526b34,
0x2d3122,
];
/** Map a greyscale / near-grey RGB888 pixel to a DMG shade (0=light … 3=dark). */
export function dmgShadeFromRgb(r: number, g: number, b: number): number {
var y = (r + g + b) / 3;
if (y >= 192) return 0;
if (y >= 128) return 1;
if (y >= 64) return 2;
return 3;
}
+10 -2
View File
@@ -14,7 +14,7 @@ export interface EditorContext {
}
export type SelectablePalette = {
node: PixNode
node: PixNode | null
name: string
palette: Uint32Array
}
@@ -224,7 +224,11 @@ export function validateAssetData(datastr: string, fmt): string | null {
maxOffset = Math.max(maxOffset, offset);
}
}
var required = maxOffset + (nplanes > 1 ? (nplanes - 1) * pofs : 0) + 1 + skip;
// Planar formats (e.g. NES pofs>=wpimg) store extra planes past each image block.
// Interleaved formats (GB/SMS pofs < wpimg) already include plane bytes in maxOffset.
var planeExtent = (nplanes > 1) ? (nplanes - 1) * pofs : 0;
if (planeExtent < wpimg) planeExtent = 0;
var required = maxOffset + planeExtent + 1 + skip;
if (words.length != required) {
return `Expected ${required} value(s), found ${words.length}`;
@@ -341,6 +345,10 @@ export function convertPaletteFormat(palbytes: UintArray, palfmt: PixelEditorPal
// TODO: illegal colors?
const PREDEF_PALETTES = {
// LCD polarity: index 0 = lightest (pixel off), 3 = darkest (pixel on)
'gb': [
0xd0d884, 0x87a84c, 0x526b34, 0x2d3122,
],
'nes': [
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
+13
View File
@@ -1,6 +1,7 @@
import { newDiv, ProjectView } from "./baseviews";
import { platform_id, current_project, projectWindows } from "../ui";
import { DMG_PALETTE } from "../../common/gbpalette";
import { FileData } from "../../common/workertypes";
import { hex, safeident, rgb2bgr } from "../../common/util";
import * as pixed from "../pixeleditor";
@@ -80,6 +81,18 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
node = node.right;
}
});
// Game Boy: default to LCD green scale (0=light … 3=dark) when no project palette
if (result.length == 0 && platform_id && platform_id.startsWith('gb')) {
if (matchlen == 4) {
result.push({ node: null, name: "DMG Green", palette: new Uint32Array(DMG_PALETTE) });
} else if (matchlen == 2) {
result.push({
node: null,
name: "DMG Green",
palette: new Uint32Array([DMG_PALETTE[0], DMG_PALETTE[3]]),
});
}
}
return result;
}
+1 -8
View File
@@ -2,16 +2,9 @@
import { SM83, SM83State } from "../common/cpu/SM83";
import { BasicScanlineMachine, Bus } from "../common/devices";
import { newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
import { DMG_PALETTE } from "../common/gbpalette";
import { hex } from "../common/util";
// Game Boy DMG palette: 4 shades of green (darkest to lightest)
const DMG_PALETTE: number[] = [
0xFFd0d884, // Lightest (Warm Mint)
0xFF87a84c, // Light Mid
0xFF526b34, // Dark Mid
0xFF2d3122, // Darkest (Deep Olive)
];
// Convert GBC 15-bit RGB (xBBBBBGGGGGRRRRR) to 32-bit ARGB
function cgbColorToARGB(lo: number, hi: number): number {
var r5 = lo & 0x1F;
+2
View File
@@ -7,6 +7,8 @@ const GB_PRESETS: Preset[] = [
{ id: 'hello.c', name: 'Hello World (C)' },
{ id: 'testdrawing.c', name: 'Drawing Routines (C)' },
{ id: 'testphys.c', name: 'Sprite Test (C)' },
{ id: 'chase.c', name: "Shiru's Chase Game" },
{ id: 'pakupaku.c', name: 'Paku Paku' },
{ id: 'hello.sgb', name: 'Hello World (ASM)' },
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
];