mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
gb: ported climber.c
This commit is contained in:
1 parent
aef3898aa7
commit
52d5f4a86c
6 files changed
+991
-1
No files matched your search
@@ -153,6 +153,9 @@ byte a8_get_stick(byte n) {
|
||||
#endif
|
||||
|
||||
void a8_waitvsync(void) {
|
||||
#if __CC65__
|
||||
waitvsync();
|
||||
#else
|
||||
#if defined(__ATARI5200__)
|
||||
volatile byte* rtc = (volatile byte*)0x0002; /* RTCLOK+1 */
|
||||
#else
|
||||
@@ -160,4 +163,5 @@ void a8_waitvsync(void) {
|
||||
#endif
|
||||
byte t = *rtc;
|
||||
while (*rtc == t) ;
|
||||
#endif
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
* - a POKEY melody driven from the vblank interrupt
|
||||
*/
|
||||
|
||||
//#tooldef ld cfgfile=atari.cfg
|
||||
//#symbol ld __RESERVED_MEMORY__=0x900
|
||||
|
||||
//#link "a8lib.c"
|
||||
//#link "a8lib_dli.c"
|
||||
//#link "a8lib_pmg.c"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
* PAD scanlines of overlap below, so it can scroll without leaving the page.
|
||||
*/
|
||||
|
||||
//#tooldef ld cfgfile=atari.cfg
|
||||
//#symbol ld __RESERVED_MEMORY__=0x900
|
||||
|
||||
//#link "a8lib.c"
|
||||
//#link "a8lib_scroll.c"
|
||||
|
||||
|
||||
@@ -0,0 +1,976 @@
|
||||
/*
|
||||
Climber for Game Boy - port of presets/nes/climber.c.
|
||||
|
||||
Climb a randomly generated building from the basement to the roof:
|
||||
jump on ladders, dodge (or bounce off) the other climbers, watch out
|
||||
for holes in the floors, and pick up whatever you find on the way.
|
||||
Reach the roof to rescue the person waiting there.
|
||||
|
||||
Porting notes:
|
||||
- The NES version scrolls a 60-row stage using two nametables and a
|
||||
VRAM update buffer. The Game Boy background is one 32x32 tile map
|
||||
(256 px), so we keep a 32-row window of the stage resident and
|
||||
redraw the row that enters the window whenever the camera crosses
|
||||
a tile boundary. VRAM writes are queued and flushed right after
|
||||
the next vblank.
|
||||
- Tiles come from presets/nes/chr_generic.s. NES and GB are both 2bpp,
|
||||
but the NES stores the two bitplanes in separate 8-byte blocks while
|
||||
GB interleaves them, so the bytes were re-packed (see the array
|
||||
headers, which also drive the Asset Editor previews).
|
||||
- The stage/scene logic is unchanged from the NES version. Types are
|
||||
the GBDK ones; bitfields were replaced by plain bytes.
|
||||
- CGB palettes color the floor, ladders/items, text and actors. On DMG
|
||||
everything falls back to the four-shade BGP/OBP palettes.
|
||||
- Background music was dropped; the four sound effects are simple PSG
|
||||
beeps.
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/metasprites.h"
|
||||
#include "gb/cgb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
typedef uint8_t byte;
|
||||
typedef uint16_t word;
|
||||
typedef int8_t sbyte;
|
||||
|
||||
///// DEFINES
|
||||
|
||||
#define COLS 20 // floor width in tiles
|
||||
#define ROWS 60 // total scrollable height in tiles
|
||||
|
||||
#define MAX_FLOORS 20 // total # of floors in a stage
|
||||
#define GAPSIZE 4 // gap size in tiles
|
||||
#define BOTTOM_FLOOR_Y 2 // offset for bottommost floor
|
||||
|
||||
#define MAX_ACTORS 8 // max # of moving actors
|
||||
#define SCREEN_H 144 // screen height in pixels
|
||||
#define SCREEN_Y_BOTTOM 128 // screen Y of a floor at level pixel 0
|
||||
#define ACTOR_MIN_X 8 // leftmost position of actor
|
||||
#define ACTOR_MAX_X 140 // rightmost position of actor
|
||||
#define ACTOR_SCROLL_UP_Y 56 // min Y position to scroll up
|
||||
#define ACTOR_SCROLL_DOWN_Y 80 // max Y position to scroll down
|
||||
#define JUMP_VELOCITY 18 // Y velocity when jumping
|
||||
|
||||
// The map holds a 32-row window of the stage. Keep a few rows of margin
|
||||
// on either side of the 18 visible rows so a missed frame never shows a
|
||||
// blank line.
|
||||
#define WINDOW_LO 6
|
||||
#define WINDOW_HI 25
|
||||
|
||||
// constants for various tiles (indices into bg_tiles)
|
||||
#define CH_BLANK 0x00
|
||||
#define CH_FLOOR 0x01
|
||||
#define CH_LADDER 0x05
|
||||
#define CH_ITEM 0x07 // base of item type 1; +4 per type
|
||||
#define CH_BORDER 0x13
|
||||
#define CH_BASEMENT 0x14
|
||||
|
||||
// sprite frames, in the order packed into sprite_tiles
|
||||
// map the NES joystick bits onto the Game Boy d-pad
|
||||
#define PAD_A J_A
|
||||
#define PAD_LEFT J_LEFT
|
||||
#define PAD_RIGHT J_RIGHT
|
||||
#define PAD_UP J_UP
|
||||
#define PAD_DOWN J_DOWN
|
||||
|
||||
#define SPR_BASE 0x7b // first sprite tile (after the 91 font tiles)
|
||||
#define SPR_STAND 0
|
||||
#define SPR_RUN1 1
|
||||
#define SPR_RUN2 2
|
||||
#define SPR_RUN3 3
|
||||
#define SPR_JUMP 4
|
||||
#define SPR_CLIMB 5
|
||||
#define SPR_FALL 6
|
||||
#define SPR_RESCUE 7
|
||||
|
||||
// sprite palettes: CGB palette number plus the DMG OBP1 select bit
|
||||
#define PAL_ENEMY 0x00 // CGB palette 0 / OBP0
|
||||
#define PAL_PLAYER 0x11 // CGB palette 1 / OBP1
|
||||
#define PAL_RESCUE 0x12 // CGB palette 2 / OBP1
|
||||
|
||||
///// SOUND
|
||||
|
||||
// indices of sound effects (0..3)
|
||||
typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex;
|
||||
|
||||
void psg_init(void) {
|
||||
NR52_REG = 0x80; // sound on
|
||||
NR50_REG = 0x77; // master volume
|
||||
NR51_REG = 0xFF; // all channels to both outputs
|
||||
NR10_REG = 0x00; // pulse 1 sweep off
|
||||
}
|
||||
|
||||
void beep(word per, byte vol) {
|
||||
NR11_REG = 0x80; // 50% duty
|
||||
NR12_REG = (vol << 4) | 0x01; // decay envelope
|
||||
NR13_REG = (byte)(per & 0xff);
|
||||
NR14_REG = (byte)(0x80 | ((per >> 8) & 7)); // trigger
|
||||
}
|
||||
|
||||
void play_sfx(byte id) {
|
||||
switch (id) {
|
||||
case SND_START: beep(0x480, 10); break;
|
||||
case SND_JUMP: beep(0x700, 9); break;
|
||||
case SND_COIN: beep(0x680, 12); break;
|
||||
case SND_HIT: beep(0x300, 12); break;
|
||||
}
|
||||
}
|
||||
|
||||
///// GLOBALS
|
||||
|
||||
// vertical scroll amount in pixels
|
||||
static int scroll_pixel_yy = 0;
|
||||
|
||||
// vertical scroll amount in tiles (scroll_pixel_yy / 8)
|
||||
static byte scroll_tile_y = 0;
|
||||
|
||||
// last screen Y position of player sprite
|
||||
static byte player_screen_y = 0;
|
||||
|
||||
// score (BCD)
|
||||
static byte score = 0;
|
||||
|
||||
// hit flash timer
|
||||
static byte flash_timer = 0;
|
||||
static byte flash_on = 0;
|
||||
|
||||
// is this a Color Game Boy?
|
||||
static byte cgb_mode = 0;
|
||||
|
||||
// next free OAM sprite index
|
||||
static byte oam_off = 0;
|
||||
|
||||
// pending tile-map rows, flushed during the next vblank
|
||||
#define MAX_PENDING 8
|
||||
static byte pending_rows[MAX_PENDING];
|
||||
static byte pending_count = 0;
|
||||
|
||||
// 16-bit LCG. rand8() alone has a short cycle, so rndint() uses the
|
||||
// full 16 bits (matching the NES version's use of rand()).
|
||||
static word rnd = 0xCACE;
|
||||
byte rand8(void) {
|
||||
rnd = rnd * 17 + 53;
|
||||
return (byte)(rnd >> 8);
|
||||
}
|
||||
word rand16(void) {
|
||||
rnd = rnd * 17 + 53;
|
||||
return rnd;
|
||||
}
|
||||
|
||||
///// GRAPHICS DATA
|
||||
|
||||
/*{w:8,h:8,bpp:1,count:21,brev:1,np:2,pofs:1,sl:2}*/
|
||||
const uint8_t bg_tiles[] = {
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xff,0x00,0x80,0x7f,0x80,0x7f,0x9f,0x7f,0x90,0x7f,0x97,0x7f,0x97,0x7f,0x97,0x7f,
|
||||
0x97,0x7f,0x97,0x7f,0x97,0x7f,0x97,0x78,0x9f,0x7f,0xbf,0x60,0xff,0x40,0x00,0x00,
|
||||
0xfe,0x00,0x02,0xfe,0x06,0xfc,0xfe,0xf8,0x0e,0xf8,0xfe,0xe8,0xfe,0xe8,0xfe,0xe8,
|
||||
0xfe,0xe8,0xfe,0xe8,0xfe,0xe8,0xfe,0x08,0xfe,0xf8,0xfe,0x00,0xfe,0x00,0x00,0x00,
|
||||
0xb0,0xe0,0xb0,0xe0,0xbf,0xe0,0xb0,0xff,0xbf,0xef,0xbf,0xe0,0xb0,0xe0,0xb0,0xe0,
|
||||
0x0b,0x0e,0x0b,0x0e,0xfb,0x0e,0x0b,0xfe,0xfb,0xf6,0xfb,0x0e,0x0b,0x0e,0x0b,0x0e,
|
||||
0x00,0x0f,0x0f,0x30,0x3f,0x4d,0x3f,0x6d,0x7f,0xad,0x00,0xff,0x7f,0x80,0x7f,0xad,
|
||||
0x7f,0xad,0x7f,0xad,0x7f,0xad,0x00,0xff,0x7f,0x80,0x7f,0xad,0x7f,0xad,0x00,0xff,
|
||||
0x00,0xf0,0xf0,0x0c,0xfc,0xb2,0xfc,0xb6,0xfe,0xb7,0x00,0xff,0xfe,0x01,0xfe,0xb7,
|
||||
0xea,0xb5,0xe2,0xbd,0xfe,0xa1,0x00,0xff,0xfe,0x01,0xfe,0xb7,0xfe,0xb7,0x00,0xff,
|
||||
0x00,0x01,0x22,0x63,0x4c,0x3f,0x1f,0x20,0x36,0x2d,0x3f,0x29,0x5f,0x60,0x17,0xec,
|
||||
0xff,0x68,0x7f,0x20,0x36,0x0d,0x3f,0x29,0x1f,0x20,0x5c,0x77,0x22,0x43,0x00,0x01,
|
||||
0x80,0x00,0xc4,0x86,0xf2,0xdc,0xfc,0x04,0xdc,0xb0,0xfc,0x24,0xfa,0x06,0xd8,0x37,
|
||||
0xff,0x26,0xfe,0x04,0xdc,0xb0,0xfc,0x20,0xfc,0x04,0xfa,0xcc,0xc4,0x86,0x80,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x1c,0x1c,0x3e,0x26,0x7f,0x2f,0x7f,0x3f,0x7f,0x3f,0x7f,
|
||||
0x1f,0x3f,0x0f,0x1f,0x07,0x0f,0x03,0x07,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x38,0x38,0x74,0x4c,0xfa,0xdc,0xfa,0xf4,0xfa,0xf4,0xfa,
|
||||
0xe8,0xf4,0xd0,0xe8,0xa0,0xd0,0x40,0xa0,0x80,0x40,0x00,0x80,0x00,0x00,0x00,0x00,
|
||||
0x3c,0x38,0x7e,0x44,0x7e,0x5c,0x7e,0x54,0x7e,0x5c,0x7e,0x40,0x7e,0x3c,0x3e,0x00,
|
||||
0x01,0xff,0x01,0xff,0x01,0xff,0x01,0xff,0x01,0xff,0x01,0xff,0x01,0xff,0xfe,0x00
|
||||
};
|
||||
|
||||
// 8 16x16 frames (7 player poses + the rescuee), each four 8x8 tiles
|
||||
// in UL, LL, UR, LR order.
|
||||
/*{w:16,h:16,bpp:1,count:8,brev:1,np:2,pofs:1,sl:2,wpimg:64,remap:[5,1,2,3,4,0,6,7,8,9,10,11,12]}*/
|
||||
const uint8_t sprite_tiles[] = {
|
||||
0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x32,0x2D,0x70,0x4F,0x38,0x37,0x18,0x07,
|
||||
0x0F,0x0F,0x1F,0x1F,0x1F,0x1F,0x13,0x1C,0x03,0x0C,0x0E,0x00,0x0E,0x0E,0x0F,0x0F,
|
||||
0xC8,0xC8,0xFC,0x34,0xFC,0x04,0xF8,0x08,0x40,0xA0,0x40,0xF0,0x00,0xE0,0x00,0xC0,
|
||||
0xC0,0xC0,0xE0,0xE0,0xE0,0xE0,0xC0,0x20,0xC0,0x20,0xC0,0x00,0xE0,0xE0,0x70,0x70,
|
||||
0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x32,0x2D,0x70,0x4F,0x38,0x37,0x18,0x07,
|
||||
0x1F,0x1F,0x1F,0x3F,0x0F,0x3F,0x0F,0x00,0x3F,0x00,0x7C,0x60,0x30,0x30,0x18,0x18,
|
||||
0xC8,0xC8,0xFC,0x34,0xFC,0x04,0xF8,0x08,0x40,0xA0,0x40,0xF0,0x00,0xE0,0x00,0xC0,
|
||||
0xC0,0xCC,0xF0,0xFC,0xF8,0xF8,0xE4,0x04,0xFC,0x0C,0xFC,0x0C,0x7C,0x0C,0x00,0x00,
|
||||
0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x32,0x2D,0x70,0x4F,0x38,0x37,0x18,0x07,
|
||||
0x0F,0x0F,0x1F,0x1F,0x1F,0x1F,0x13,0x1C,0x03,0x0C,0x0E,0x00,0x0E,0x0E,0x0F,0x0F,
|
||||
0xC8,0xC8,0xFC,0x34,0xFC,0x04,0xF8,0x08,0x40,0xA0,0x40,0xF0,0x00,0xE0,0x00,0xC0,
|
||||
0xC0,0xC0,0xC0,0xC0,0xC0,0xE0,0xC0,0x20,0xC0,0x00,0xC0,0xC0,0xE0,0xE0,0x00,0x00,
|
||||
0x00,0x00,0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x32,0x2D,0x70,0x4F,0x18,0x17,
|
||||
0x18,0x07,0x1F,0x1F,0x0F,0x3F,0x0F,0x37,0x3F,0x20,0x7B,0x60,0x43,0x43,0x03,0x03,
|
||||
0x00,0x00,0xC8,0xC8,0xFC,0x34,0xFC,0x04,0xF8,0x08,0x40,0xA0,0x40,0xF0,0x00,0xE0,
|
||||
0x00,0xC0,0xE0,0xF8,0xE0,0xF8,0xC0,0xC0,0xC0,0x00,0x80,0x00,0x80,0x80,0xC0,0xC0,
|
||||
0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x32,0x2D,0x70,0x4F,0x38,0x37,0x18,0x1F,
|
||||
0x3F,0x3F,0x3F,0x7F,0x0F,0x6F,0x7F,0x60,0x7F,0x60,0x7E,0x60,0x40,0x40,0x00,0x00,
|
||||
0xC8,0xC8,0xFC,0x34,0xFC,0x04,0xF8,0x08,0x40,0xA0,0x40,0xB0,0x00,0xE0,0x00,0xCC,
|
||||
0xD0,0xDC,0xF8,0xF8,0xF0,0xF0,0xC4,0x04,0xFC,0x0C,0xFC,0x0C,0x7C,0x0C,0x00,0x00,
|
||||
0x07,0x07,0x1F,0x08,0x1F,0x10,0x3F,0x20,0x3F,0x20,0x7F,0x40,0x0F,0x30,0x7F,0x60,
|
||||
0x7F,0x7F,0x3F,0x3F,0x1F,0x1F,0x0F,0x00,0x0F,0x00,0x0F,0x06,0x0F,0x0F,0x0F,0x0F,
|
||||
0xE0,0xE0,0xF0,0x10,0xF8,0x08,0xFC,0x04,0xFC,0x04,0xFE,0x02,0xFC,0x0C,0xFC,0x04,
|
||||
0xF8,0xF8,0xF8,0xF8,0xF8,0xC0,0xF8,0x00,0xF0,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x17,0x17,0x3F,0x28,0x1F,0x10,0x3F,0x20,0x3A,0x27,0x19,0x66,0x1A,0x75,0x7C,0x63,
|
||||
0x7F,0x7F,0x3F,0x3F,0x5F,0x5F,0x7F,0x60,0x7F,0x60,0x7F,0x61,0x1E,0x1E,0x00,0x00,
|
||||
0xE8,0xE8,0xFC,0x14,0xF8,0x08,0xFC,0x04,0x5C,0xE4,0x9E,0x62,0x5C,0xAC,0x3C,0xC7,
|
||||
0xFC,0xFF,0xFE,0xFE,0xF0,0xF0,0xF0,0x10,0xF8,0x08,0xF8,0x08,0xE0,0xE0,0x70,0x70,
|
||||
0x0F,0x0F,0x1F,0x1F,0x3F,0x3F,0x3D,0x3F,0x38,0x3F,0x38,0x3F,0x3C,0x3F,0x16,0x17,
|
||||
0xF2,0x0D,0xF1,0x0E,0xF1,0x1E,0x1F,0x10,0x3F,0x20,0x7B,0x44,0x31,0x2E,0x10,0x1F,
|
||||
0xF0,0xF0,0xF8,0xF8,0xBC,0xFC,0x14,0xFC,0xA0,0x58,0xA0,0x58,0x08,0xF8,0x1C,0xFC,
|
||||
0x20,0xE0,0xF0,0x10,0xF8,0x08,0xF8,0x08,0xFC,0x04,0xDE,0x22,0x8C,0x74,0x84,0xFC
|
||||
};
|
||||
|
||||
// CGB palettes: 3 background palettes (floor, ladders/items, text),
|
||||
// then 3 sprite palettes (enemy, player, rescuee).
|
||||
/*{pal:555,n:12,bpw:16}*/
|
||||
palette_color_t bkg_palettes[12] = {
|
||||
0x2041, 0x5106, 0x7261, 0x7fff,
|
||||
0x2041, 0x7148, 0x025f, 0x7fff,
|
||||
0x2041, 0x39ce, 0x4210, 0x7fff,
|
||||
};
|
||||
/*{pal:555,n:12,bpw:16}*/
|
||||
palette_color_t spr_palettes[12] = {
|
||||
0x0000, 0x109c, 0x625f, 0x2388,
|
||||
0x0000, 0x1084, 0x025f, 0x2388,
|
||||
0x0000, 0x39ce, 0x731f, 0x4bf2,
|
||||
};
|
||||
/*{pal:555,n:12,bpw:16}*/
|
||||
palette_color_t bkg_flash[12] = {
|
||||
0x7fff, 0x7fff, 0x7fff, 0x7fff,
|
||||
0x7fff, 0x7fff, 0x7fff, 0x7fff,
|
||||
0x7fff, 0x7fff, 0x7fff, 0x7fff,
|
||||
};
|
||||
/*{pal:555,n:12,bpw:16}*/
|
||||
palette_color_t spr_flash[12] = {
|
||||
0x0000, 0x7fff, 0x7fff, 0x7fff,
|
||||
0x0000, 0x7fff, 0x7fff, 0x7fff,
|
||||
0x0000, 0x7fff, 0x7fff, 0x7fff,
|
||||
};
|
||||
|
||||
///// GAME LOGIC
|
||||
|
||||
// struct definition for a single floor
|
||||
typedef struct Floor {
|
||||
byte ypos; // # of tiles from ground
|
||||
byte height; // # of tiles to next floor
|
||||
byte gap; // X position of gap (in 2-tile units, 0 = none)
|
||||
byte ladder1; // X position of first ladder
|
||||
byte ladder2; // X position of second ladder
|
||||
byte objtype; // item type (FloorItem)
|
||||
byte objpos; // X position of object
|
||||
byte _reserved; // pad to 8 bytes
|
||||
} Floor;
|
||||
|
||||
// various items the player can pick up
|
||||
typedef enum FloorItem { ITEM_NONE, ITEM_MINE, ITEM_HEART, ITEM_POWER };
|
||||
|
||||
// array of floors
|
||||
Floor floors[MAX_FLOORS];
|
||||
|
||||
// random byte between (a ... b-1)
|
||||
byte rndint(byte a, byte b) {
|
||||
return (rand16() % (b-a)) + a;
|
||||
}
|
||||
|
||||
// is this x (pixel) position within the gap <gap>?
|
||||
byte is_in_gap(byte x, byte gap) {
|
||||
if (gap) {
|
||||
byte x1 = gap*16 + 4;
|
||||
return (x > x1 && x < x1+GAPSIZE*8-4);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// is this ladder at (tile) position x within the gap?
|
||||
byte ladder_in_gap(byte x, byte gap) {
|
||||
return gap && x >= gap && x < gap+GAPSIZE*2;
|
||||
}
|
||||
|
||||
// create floors at start of game
|
||||
void make_floors(void) {
|
||||
byte i;
|
||||
byte y = BOTTOM_FLOOR_Y;
|
||||
Floor* prevlev = &floors[0];
|
||||
for (i=0; i<MAX_FLOORS; i++) {
|
||||
Floor* lev = &floors[i];
|
||||
lev->height = rndint(2,5)*2;
|
||||
do {
|
||||
// only have gaps in higher floors
|
||||
lev->gap = i>=5 ? rndint(0,8) : 0;
|
||||
} while (ladder_in_gap(prevlev->ladder1, lev->gap) ||
|
||||
ladder_in_gap(prevlev->ladder2, lev->gap));
|
||||
do {
|
||||
lev->ladder1 = rndint(1,10);
|
||||
lev->ladder2 = rndint(1,10);
|
||||
} while (ladder_in_gap(lev->ladder1, lev->gap) ||
|
||||
ladder_in_gap(lev->ladder2, lev->gap));
|
||||
if (i > 0) {
|
||||
lev->objtype = rndint(1,4);
|
||||
do {
|
||||
lev->objpos = rndint(1,10);
|
||||
} while (ladder_in_gap(lev->objpos, lev->gap));
|
||||
} else {
|
||||
lev->objtype = 0;
|
||||
lev->objpos = 0;
|
||||
}
|
||||
lev->ypos = y;
|
||||
y += lev->height;
|
||||
prevlev = lev;
|
||||
}
|
||||
// top floor is special
|
||||
floors[MAX_FLOORS-1].height = 15;
|
||||
floors[MAX_FLOORS-1].gap = 0;
|
||||
floors[MAX_FLOORS-1].ladder1 = 0;
|
||||
floors[MAX_FLOORS-1].ladder2 = 0;
|
||||
floors[MAX_FLOORS-1].objtype = 0;
|
||||
}
|
||||
|
||||
// get Y pixel position for a given floor
|
||||
word get_floor_yy(byte floor) {
|
||||
return floors[floor].ypos * 8 + 16;
|
||||
}
|
||||
|
||||
// get Y ceiling position for a given floor
|
||||
word get_ceiling_yy(byte floor) {
|
||||
return (floors[floor].ypos + floors[floor].height) * 8 + 16;
|
||||
}
|
||||
|
||||
///// ACTORS
|
||||
|
||||
typedef enum ActorState {
|
||||
INACTIVE, STANDING, WALKING, CLIMBING, JUMPING, FALLING, PACING
|
||||
};
|
||||
|
||||
typedef enum ActorType {
|
||||
ACTOR_PLAYER, ACTOR_ENEMY, ACTOR_RESCUE
|
||||
};
|
||||
|
||||
typedef struct Actor {
|
||||
word yy; // Y position in pixels (16 bit)
|
||||
byte x; // X position in pixels (8 bit)
|
||||
byte floor; // floor index
|
||||
byte state; // ActorState
|
||||
sbyte yvel; // Y velocity (when jumping)
|
||||
sbyte xvel; // X velocity (when jumping)
|
||||
byte type; // ActorType
|
||||
byte pal; // palette selector
|
||||
byte dir; // direction (0=right, 1=left)
|
||||
byte onscreen; // is actor onscreen?
|
||||
} Actor;
|
||||
|
||||
Actor actors[MAX_ACTORS]; // all actors
|
||||
|
||||
// player run sequence, indexed by ((x>>1)&7)
|
||||
const byte run_frame[8] = { SPR_RUN1, SPR_RUN2, SPR_RUN3,
|
||||
SPR_RUN1, SPR_RUN2, SPR_RUN3, SPR_RUN1, SPR_RUN2 };
|
||||
|
||||
///// FORWARD DECLARATIONS
|
||||
|
||||
void draw_floor_line(byte row_height);
|
||||
void create_actors_on_floor(byte floor_index);
|
||||
void refresh_floor(byte floor);
|
||||
void set_palettes(byte flash);
|
||||
|
||||
///// TILE MAP
|
||||
|
||||
// queue a stage row to redraw during the next vblank
|
||||
void queue_row(byte row_height) {
|
||||
if (pending_count < MAX_PENDING)
|
||||
pending_rows[pending_count++] = row_height;
|
||||
}
|
||||
|
||||
void flush_rows(void) {
|
||||
byte i;
|
||||
for (i=0; i<pending_count; i++)
|
||||
draw_floor_line(pending_rows[i]);
|
||||
pending_count = 0;
|
||||
}
|
||||
|
||||
// which CGB background palette does this tile use?
|
||||
byte tile_attr(byte t) {
|
||||
if (t >= CH_LADDER && t <= CH_ITEM+11) return 1; // ladders + items
|
||||
if (t >= FONT_TILE_BASE && t < SPR_BASE) return 2; // text
|
||||
return 0; // floor, blank, walls
|
||||
}
|
||||
|
||||
// map row (0..31) for a level row counted from the bottom
|
||||
#define MAPROW(h) ((32 - ((h) & 31)) & 31)
|
||||
|
||||
// draw a stage line into the tile map
|
||||
// 0 == bottom of stage
|
||||
void draw_floor_line(byte row_height) {
|
||||
byte buf[COLS]; // tile buffer
|
||||
byte attrs[COLS]; // CGB attribute buffer
|
||||
byte floor = 0; // floor counter
|
||||
byte dy = 0xff; // height in rows above floor
|
||||
byte i; // loop counter
|
||||
byte mrow;
|
||||
// clear buffer (areas above the top floor are blank)
|
||||
memset(buf, 0, sizeof(buf));
|
||||
// loop over all floors
|
||||
for (floor=0; floor<MAX_FLOORS; floor++) {
|
||||
Floor* lev = &floors[floor];
|
||||
// compute height in rows above floor
|
||||
dy = row_height - lev->ypos;
|
||||
// if below bottom floor (in basement)
|
||||
if (dy >= 255 - BOTTOM_FLOOR_Y) dy = 0;
|
||||
// does this floor intersect the desired row?
|
||||
if (dy < lev->height) {
|
||||
// first two rows (floor)?
|
||||
if (dy <= 1) {
|
||||
// iterate through all columns
|
||||
for (i=0; i<COLS; i+=2) {
|
||||
if (dy) {
|
||||
buf[i] = CH_FLOOR; // upper-left
|
||||
buf[i+1] = CH_FLOOR+2; // upper-right
|
||||
} else {
|
||||
buf[i] = CH_FLOOR+1; // lower-left
|
||||
buf[i+1] = CH_FLOOR+3; // lower-right
|
||||
}
|
||||
}
|
||||
// is there a gap? if so, clear tiles
|
||||
if (lev->gap)
|
||||
memset(buf+lev->gap*2, 0, GAPSIZE);
|
||||
} else {
|
||||
// clear buffer
|
||||
memset(buf, 0, sizeof(buf));
|
||||
// draw walls
|
||||
if (floor < MAX_FLOORS-1) {
|
||||
buf[0] = CH_FLOOR+1; // left side
|
||||
buf[COLS-1] = CH_FLOOR; // right side
|
||||
}
|
||||
// draw ladders
|
||||
if (lev->ladder1) {
|
||||
buf[lev->ladder1*2] = CH_LADDER;
|
||||
buf[lev->ladder1*2+1] = CH_LADDER+1;
|
||||
}
|
||||
if (lev->ladder2) {
|
||||
buf[lev->ladder2*2] = CH_LADDER;
|
||||
buf[lev->ladder2*2+1] = CH_LADDER+1;
|
||||
}
|
||||
}
|
||||
// draw object, if it exists
|
||||
if (lev->objtype) {
|
||||
byte ch = CH_ITEM + (lev->objtype-1)*4;
|
||||
if (dy == 2) {
|
||||
buf[lev->objpos*2] = ch+1; // bottom-left
|
||||
buf[lev->objpos*2+1] = ch+3; // bottom-right
|
||||
} else if (dy == 3) {
|
||||
buf[lev->objpos*2] = ch+0; // top-left
|
||||
buf[lev->objpos*2+1] = ch+2; // top-right
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// write the row into the tile map
|
||||
mrow = MAPROW(row_height);
|
||||
set_bkg_tiles(0, mrow, COLS, 1, buf);
|
||||
if (cgb_mode) {
|
||||
for (i=0; i<COLS; i++)
|
||||
attrs[i] = tile_attr(buf[i]);
|
||||
VBK_REG = 1;
|
||||
set_bkg_tiles(0, mrow, COLS, 1, attrs);
|
||||
VBK_REG = 0;
|
||||
}
|
||||
// create actors on this floor, if needed
|
||||
if (dy == 0 && floor >= 2)
|
||||
create_actors_on_floor(floor);
|
||||
}
|
||||
|
||||
// draw the initial 32-row window of the stage at the current scroll
|
||||
void draw_entire_stage(void) {
|
||||
int h;
|
||||
for (h=scroll_tile_y-WINDOW_LO; h<=scroll_tile_y+WINDOW_HI; h++) {
|
||||
wait_vbl_done();
|
||||
draw_floor_line((byte)h);
|
||||
}
|
||||
}
|
||||
|
||||
// set scrolling position
|
||||
void set_scroll_pixel_yy(int yy) {
|
||||
byte new_tile = yy >> 3;
|
||||
// did we cross a tile boundary? then the window slid one row
|
||||
if (new_tile != scroll_tile_y) {
|
||||
if (new_tile > scroll_tile_y)
|
||||
queue_row(new_tile + WINDOW_HI); // new top of window
|
||||
else
|
||||
queue_row(new_tile - WINDOW_LO); // new bottom of window
|
||||
scroll_tile_y = new_tile;
|
||||
}
|
||||
scroll_pixel_yy = yy;
|
||||
SCY_REG = (120 - yy) & 0xff;
|
||||
SCX_REG = 0;
|
||||
}
|
||||
|
||||
// redraw a floor when object picked up
|
||||
void refresh_floor(byte floor) {
|
||||
byte y = floors[floor].ypos;
|
||||
queue_row(y+2); // redraw 3rd line
|
||||
queue_row(y+3); // redraw 4th line
|
||||
}
|
||||
|
||||
///// DRAWING
|
||||
|
||||
// draw a 2x2 sprite at oam_off, using sprite_tiles[frame*4..]
|
||||
void put_2x2(byte x, byte y, byte frame, byte props, byte flip) {
|
||||
byte i, col, row, px, py, tilebase, oam;
|
||||
tilebase = SPR_BASE + frame*4;
|
||||
for (i=0; i<4; i++) {
|
||||
col = i >> 1; // 0 = left, 1 = right
|
||||
row = i & 1; // 0 = top, 1 = bottom
|
||||
px = x + ((flip ? (1-col) : col) << 3);
|
||||
py = y + (row << 3);
|
||||
oam = oam_off + i;
|
||||
set_sprite_tile(oam, tilebase+i);
|
||||
set_sprite_prop(oam, props | (flip ? S_FLIPX : 0));
|
||||
move_sprite(oam, px+8, py+16);
|
||||
}
|
||||
}
|
||||
|
||||
// draw an actor, return # of sprites used
|
||||
byte draw_actor(byte i) {
|
||||
struct Actor* a = &actors[i];
|
||||
byte frame, props, flip;
|
||||
int screen_y;
|
||||
// get screen Y position of actor
|
||||
screen_y = SCREEN_Y_BOTTOM - a->yy + scroll_pixel_yy;
|
||||
// is it offscreen?
|
||||
if (screen_y > SCREEN_H+8 || screen_y < -18) {
|
||||
a->onscreen = 0;
|
||||
return 0;
|
||||
}
|
||||
switch (a->state) {
|
||||
case INACTIVE:
|
||||
a->onscreen = 0;
|
||||
return 0;
|
||||
case STANDING: frame = SPR_STAND; break;
|
||||
case WALKING: frame = run_frame[(a->x >> 1) & 7]; break;
|
||||
case JUMPING: frame = SPR_JUMP; break;
|
||||
case FALLING: frame = SPR_FALL; break;
|
||||
case CLIMBING: frame = SPR_CLIMB; break;
|
||||
case PACING: frame = SPR_RESCUE; break;
|
||||
default: frame = SPR_STAND; break;
|
||||
}
|
||||
if (a->type == ACTOR_RESCUE) props = PAL_RESCUE;
|
||||
else if (a->type == ACTOR_ENEMY) props = PAL_ENEMY;
|
||||
else props = PAL_PLAYER;
|
||||
flip = a->dir;
|
||||
if (a->state == CLIMBING) flip = (a->yy & 4) ? 1 : 0;
|
||||
put_2x2(a->x, (byte)screen_y, frame, props, flip);
|
||||
if (i == 0)
|
||||
player_screen_y = (byte)screen_y;
|
||||
a->onscreen = 1;
|
||||
return 4;
|
||||
}
|
||||
|
||||
// draw the scoreboard, right now just two digits
|
||||
void draw_scoreboard(void) {
|
||||
set_sprite_tile(oam_off, '0' + (score >> 4));
|
||||
set_sprite_prop(oam_off, PAL_PLAYER);
|
||||
move_sprite(oam_off, 24+8, 4+16);
|
||||
oam_off++;
|
||||
set_sprite_tile(oam_off, '0' + (score & 0xf));
|
||||
set_sprite_prop(oam_off, PAL_PLAYER);
|
||||
move_sprite(oam_off, 32+8, 4+16);
|
||||
oam_off++;
|
||||
}
|
||||
|
||||
// draw all sprites
|
||||
void refresh_sprites(void) {
|
||||
byte i;
|
||||
oam_off = 0;
|
||||
for (i=0; i<MAX_ACTORS; i++)
|
||||
oam_off += draw_actor(i);
|
||||
draw_scoreboard();
|
||||
hide_sprites_range(oam_off, 40);
|
||||
}
|
||||
|
||||
///// GAME LOGIC (cont.)
|
||||
|
||||
// put an actor on a floor, if its slot is empty
|
||||
void create_actors_on_floor(byte floor_index) {
|
||||
byte actor_index = (floor_index % (MAX_ACTORS-1)) + 1;
|
||||
struct Actor* a = &actors[actor_index];
|
||||
if (!a->onscreen) {
|
||||
Floor *floor = &floors[floor_index];
|
||||
a->state = STANDING;
|
||||
a->type = ACTOR_ENEMY;
|
||||
a->x = rand8();
|
||||
a->yy = get_floor_yy(floor_index);
|
||||
a->floor = floor_index;
|
||||
a->onscreen = 1;
|
||||
// rescue person on top of the building
|
||||
if (floor_index == MAX_FLOORS-1) {
|
||||
a->type = ACTOR_RESCUE;
|
||||
a->state = PACING;
|
||||
a->x = 0;
|
||||
a->pal = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if ladder is close to X position, return ladder X position, otherwise 0
|
||||
byte is_ladder_close(byte actor_x, byte ladder_pos) {
|
||||
byte ladder_x;
|
||||
if (ladder_pos == 0)
|
||||
return 0;
|
||||
ladder_x = ladder_pos * 16;
|
||||
return ((byte)(actor_x - ladder_x) < 16) ? ladder_x : 0;
|
||||
}
|
||||
|
||||
// get the closest ladder to the player
|
||||
byte get_closest_ladder(byte player_x, byte floor_index) {
|
||||
Floor* floor;
|
||||
byte x;
|
||||
if (floor_index >= MAX_FLOORS) return 0;
|
||||
floor = &floors[floor_index];
|
||||
x = is_ladder_close(player_x, floor->ladder1);
|
||||
if (x) return x;
|
||||
x = is_ladder_close(player_x, floor->ladder2);
|
||||
if (x) return x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// put the player on the ladder, and move up or down (floor_adjust)
|
||||
byte mount_ladder(Actor* player, sbyte floor_adjust) {
|
||||
byte x = get_closest_ladder(player->x + 8, player->floor + floor_adjust);
|
||||
if (x) {
|
||||
player->x = x;
|
||||
player->state = CLIMBING;
|
||||
player->floor += floor_adjust;
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// should we scroll the screen upward?
|
||||
void check_scroll_up(void) {
|
||||
if (player_screen_y < ACTOR_SCROLL_UP_Y)
|
||||
set_scroll_pixel_yy(scroll_pixel_yy + 1);
|
||||
}
|
||||
|
||||
// should we scroll the screen downward?
|
||||
void check_scroll_down(void) {
|
||||
if (player_screen_y > ACTOR_SCROLL_DOWN_Y && scroll_pixel_yy > 0)
|
||||
set_scroll_pixel_yy(scroll_pixel_yy - 1);
|
||||
}
|
||||
|
||||
// actor falls down a floor
|
||||
void fall_down(struct Actor* actor) {
|
||||
actor->floor--;
|
||||
actor->state = FALLING;
|
||||
actor->xvel = 0;
|
||||
actor->yvel = 0;
|
||||
}
|
||||
|
||||
// move an actor (player or enemies)
|
||||
// joystick - game controller mask
|
||||
// scroll - if true, we should scroll screen (is player)
|
||||
void move_actor(struct Actor* actor, byte joystick, byte scroll) {
|
||||
switch (actor->state) {
|
||||
|
||||
case STANDING:
|
||||
case WALKING:
|
||||
// left/right has priority over climbing
|
||||
if (joystick & PAD_A) {
|
||||
actor->state = JUMPING;
|
||||
actor->xvel = 0;
|
||||
actor->yvel = JUMP_VELOCITY;
|
||||
if (joystick & PAD_LEFT) actor->xvel = -1;
|
||||
if (joystick & PAD_RIGHT) actor->xvel = 1;
|
||||
// play sound for player
|
||||
if (scroll) play_sfx(SND_JUMP);
|
||||
} else if (joystick & PAD_LEFT) {
|
||||
actor->x--;
|
||||
actor->dir = 1;
|
||||
actor->state = WALKING;
|
||||
} else if (joystick & PAD_RIGHT) {
|
||||
actor->x++;
|
||||
actor->dir = 0;
|
||||
actor->state = WALKING;
|
||||
} else if (joystick & PAD_UP) {
|
||||
mount_ladder(actor, 0); // state -> CLIMBING
|
||||
} else if (joystick & PAD_DOWN) {
|
||||
mount_ladder(actor, -1); // state -> CLIMBING, floor -= 1
|
||||
} else {
|
||||
actor->state = STANDING;
|
||||
}
|
||||
if (scroll) {
|
||||
check_scroll_up();
|
||||
check_scroll_down();
|
||||
}
|
||||
break;
|
||||
|
||||
case CLIMBING:
|
||||
if (joystick & PAD_UP) {
|
||||
if (actor->yy >= get_ceiling_yy(actor->floor)) {
|
||||
actor->floor++;
|
||||
actor->state = STANDING;
|
||||
} else {
|
||||
actor->yy++;
|
||||
}
|
||||
} else if (joystick & PAD_DOWN) {
|
||||
if (actor->yy <= get_floor_yy(actor->floor)) {
|
||||
actor->state = STANDING;
|
||||
} else {
|
||||
actor->yy--;
|
||||
}
|
||||
}
|
||||
if (scroll) {
|
||||
check_scroll_up();
|
||||
check_scroll_down();
|
||||
}
|
||||
break;
|
||||
|
||||
case FALLING:
|
||||
if (scroll) {
|
||||
check_scroll_up();
|
||||
check_scroll_down();
|
||||
}
|
||||
// fall through
|
||||
case JUMPING:
|
||||
actor->x += actor->xvel;
|
||||
actor->yy += actor->yvel/4;
|
||||
actor->yvel -= 1;
|
||||
if (actor->yy <= get_floor_yy(actor->floor)) {
|
||||
actor->yy = get_floor_yy(actor->floor);
|
||||
actor->state = STANDING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// don't allow player to travel past left/right edges of screen
|
||||
if (actor->x > ACTOR_MAX_X) actor->x = ACTOR_MAX_X; // we wrapped around right edge
|
||||
if (actor->x < ACTOR_MIN_X) actor->x = ACTOR_MIN_X;
|
||||
// if player lands in a gap, they fall (switch to FALLING state)
|
||||
if (actor->state <= WALKING &&
|
||||
is_in_gap(actor->x, floors[actor->floor].gap)) {
|
||||
fall_down(actor);
|
||||
}
|
||||
}
|
||||
|
||||
// should we pickup an object? only player does this
|
||||
void pickup_object(Actor* actor) {
|
||||
Floor* floor = &floors[actor->floor];
|
||||
byte objtype = floor->objtype;
|
||||
// only pick up if there's an object, and if we're walking or standing
|
||||
if (objtype && actor->state <= WALKING) {
|
||||
byte objx = floor->objpos * 16;
|
||||
// is the actor close to the object?
|
||||
if (actor->x >= objx && actor->x < objx+16) {
|
||||
// clear the item from the floor and redraw
|
||||
floor->objtype = 0;
|
||||
refresh_floor(actor->floor);
|
||||
// did we hit a mine?
|
||||
if (objtype == ITEM_MINE) {
|
||||
// we hit a mine, fall down
|
||||
fall_down(actor);
|
||||
play_sfx(SND_HIT);
|
||||
flash_timer = 8; // flash
|
||||
} else {
|
||||
// we picked up an object, add to score (BCD)
|
||||
score = (score + 1) & 0xff;
|
||||
if ((score & 0x0f) > 9) score = (score + 6) & 0xff;
|
||||
play_sfx(SND_COIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read joystick 0 and move the player
|
||||
void move_player(void) {
|
||||
byte joy = joypad();
|
||||
if (joy & J_B) joy |= J_A; // B also jumps
|
||||
move_actor(&actors[0], joy, 1);
|
||||
pickup_object(&actors[0]);
|
||||
}
|
||||
|
||||
// returns absolute value of x
|
||||
byte iabs(int x) {
|
||||
return x >= 0 ? x : -x;
|
||||
}
|
||||
|
||||
// check to see if actor collides with any non-player actor
|
||||
byte check_collision(Actor* a) {
|
||||
byte i;
|
||||
byte afloor = a->floor;
|
||||
// can't fall through basement
|
||||
if (afloor == 0) return 0;
|
||||
// can't collide if already falling
|
||||
if (a->state == FALLING) return 0;
|
||||
// iterate through entire list of actors
|
||||
for (i=1; i<MAX_ACTORS; i++) {
|
||||
Actor* b = &actors[i];
|
||||
// actors must be on same floor and within 8 pixels
|
||||
if (b->onscreen &&
|
||||
afloor == b->floor &&
|
||||
iabs(a->yy - b->yy) < 8 &&
|
||||
iabs(a->x - b->x) < 8) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///// TEXT
|
||||
|
||||
// write a single character into the tile map with a CGB palette
|
||||
void put_char_pal(byte x, byte y, char c, byte pal) {
|
||||
set_bkg_tiles(x, y, 1, 1, (uint8_t*)&c);
|
||||
if (cgb_mode) {
|
||||
VBK_REG = 1;
|
||||
set_bkg_tiles(x, y, 1, 1, &pal);
|
||||
VBK_REG = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// map row that currently sits at screen row sr
|
||||
byte screen_row_to_map(byte sr) {
|
||||
return (((SCY_REG >> 3) + sr) & 31);
|
||||
}
|
||||
|
||||
const char* RESCUE_TEXT =
|
||||
"Is this a rescue?\n"
|
||||
"I am just hanging\n"
|
||||
"on this building.\n"
|
||||
"Get lost!!!";
|
||||
|
||||
// draw a message on the screen, one character at a time
|
||||
void type_message(const char* charptr) {
|
||||
char ch;
|
||||
byte x, y;
|
||||
x = 2;
|
||||
y = screen_row_to_map(3);
|
||||
while ((ch = *charptr++)) {
|
||||
if (ch == '\n') {
|
||||
x = 2;
|
||||
y = (y+1) & 31;
|
||||
} else {
|
||||
put_char_pal(x, y, ch, 2);
|
||||
x++;
|
||||
}
|
||||
play_sfx(SND_HIT); // typewriter sound
|
||||
wait_vbl_done();
|
||||
wait_vbl_done();
|
||||
wait_vbl_done();
|
||||
}
|
||||
}
|
||||
|
||||
// reward scene when player reaches roof
|
||||
void rescue_scene(void) {
|
||||
byte i;
|
||||
// make player face to the left
|
||||
actors[0].dir = 1;
|
||||
actors[0].state = STANDING;
|
||||
refresh_sprites();
|
||||
type_message(RESCUE_TEXT);
|
||||
// wait 2 seconds
|
||||
for (i=0; i<100; i++)
|
||||
wait_vbl_done();
|
||||
}
|
||||
|
||||
// game loop
|
||||
void play_scene(void) {
|
||||
byte i;
|
||||
// initialize actors array
|
||||
memset(actors, 0, sizeof(actors));
|
||||
actors[0].state = STANDING;
|
||||
actors[0].type = ACTOR_PLAYER;
|
||||
actors[0].pal = 3;
|
||||
actors[0].x = 64;
|
||||
actors[0].floor = 0;
|
||||
actors[0].yy = get_floor_yy(0);
|
||||
// put actor at bottom
|
||||
scroll_tile_y = 0;
|
||||
set_scroll_pixel_yy(0);
|
||||
// draw initial view of level into tile map
|
||||
draw_entire_stage();
|
||||
// repeat until player reaches the roof
|
||||
while (actors[0].floor != MAX_FLOORS-1) {
|
||||
// prepare sprites and read input
|
||||
refresh_sprites();
|
||||
move_player();
|
||||
// move all the actors
|
||||
for (i=1; i<MAX_ACTORS; i++)
|
||||
move_actor(&actors[i], rand8(), 0);
|
||||
// see if the player hit another actor
|
||||
if (check_collision(&actors[0])) {
|
||||
fall_down(&actors[0]);
|
||||
play_sfx(SND_HIT);
|
||||
flash_timer = 8;
|
||||
}
|
||||
// frame boundary: flush queued rows and update the flash
|
||||
wait_vbl_done();
|
||||
if (pending_count) flush_rows();
|
||||
if ((flash_timer != 0) != flash_on) {
|
||||
flash_on = (flash_timer != 0);
|
||||
set_palettes(flash_on);
|
||||
}
|
||||
if (flash_timer) flash_timer--;
|
||||
}
|
||||
// player reached goal; reward scene
|
||||
rescue_scene();
|
||||
}
|
||||
|
||||
///// SETUP
|
||||
|
||||
// choose the CGB palettes if we're on a Color Game Boy, else the
|
||||
// four-shade DMG palettes (BGP/OBP0/OBP1).
|
||||
void set_palettes(byte flash) {
|
||||
if (flash) {
|
||||
set_bkg_palette(0, 3, bkg_flash);
|
||||
set_sprite_palette(0, 3, spr_flash);
|
||||
} else {
|
||||
set_bkg_palette(0, 3, bkg_palettes);
|
||||
set_sprite_palette(0, 3, spr_palettes);
|
||||
}
|
||||
BGP_REG = flash ? 0x1b : 0xe4;
|
||||
OBP0_REG = flash ? 0x1b : 0xe4;
|
||||
OBP1_REG = flash ? 0x1b : 0xd2;
|
||||
}
|
||||
|
||||
// set up PPU
|
||||
void setup_graphics(void) {
|
||||
cgb_mode = DEVICE_SUPPORTS_COLOR;
|
||||
DISPLAY_OFF;
|
||||
set_bkg_data(0, sizeof(bg_tiles)/16, bg_tiles);
|
||||
set_sprite_data(SPR_BASE, sizeof(sprite_tiles)/16, sprite_tiles);
|
||||
font_init();
|
||||
set_palettes(0);
|
||||
LCDC_REG = LCDCF_ON | LCDCF_BGON |
|
||||
LCDCF_OBJ8 | LCDCF_OBJON | LCDCF_WINOFF;
|
||||
hide_sprites_range(0, 40);
|
||||
// clear the background map (and its CGB attributes)
|
||||
fill_bkg_rect(0, 0, 32, 32, 0);
|
||||
if (cgb_mode) {
|
||||
VBK_REG = 1;
|
||||
fill_bkg_rect(0, 0, 32, 32, 0);
|
||||
VBK_REG = 0;
|
||||
}
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
}
|
||||
|
||||
// main program
|
||||
void main(void) {
|
||||
psg_init();
|
||||
while (1) {
|
||||
setup_graphics(); // setup PPU, clear screen
|
||||
play_sfx(SND_START); // play starting sound
|
||||
make_floors(); // make random level
|
||||
play_scene(); // play the level
|
||||
}
|
||||
}
|
||||
@@ -504,10 +504,12 @@ export class Atari800 extends BasicScanlineMachine implements AcceptsPaddleInput
|
||||
pc = ((pc + 1) & 0x3ff) | (pc & ~0x3ff);
|
||||
return b;
|
||||
}
|
||||
let visited = new Set();
|
||||
let dlist = [];
|
||||
let y = 0;
|
||||
for (let i=0; i<256 && y<240; i++) {
|
||||
let pc0 = pc;
|
||||
visited.add(pc);
|
||||
let op = nextInsn(); // get mode
|
||||
let mode = op & 0xf;
|
||||
let debugmsg = "" // "op=" + hex(op);
|
||||
@@ -532,10 +534,11 @@ export class Atari800 extends BasicScanlineMachine implements AcceptsPaddleInput
|
||||
let dlarg_lo = nextInsn();
|
||||
let dlarg_hi = nextInsn();
|
||||
debugmsg += " $" + hex(dlarg_hi) + "" + hex(dlarg_lo);
|
||||
if (jmp) pc = dlarg_lo + dlarg_hi*256;
|
||||
}
|
||||
}
|
||||
dlist.push("$"+hex(pc0) + " y=" + y + " " + debugmsg);
|
||||
if (jmp) break;
|
||||
if (jmp && visited.has(pc)) break;
|
||||
y += lines;
|
||||
}
|
||||
return dlist;
|
||||
|
||||
@@ -12,6 +12,7 @@ const GB_PRESETS: Preset[] = [
|
||||
{ id: 'scroll.c', name: 'Scrolling' },
|
||||
{ id: 'music.c', name: 'Music Player' },
|
||||
{ id: 'siegegame.c', name: 'Siege Game' },
|
||||
{ id: 'climber.c', name: 'Climber Game' },
|
||||
{ id: 'chase.c', name: "Shiru's Chase Game" },
|
||||
{ id: 'pakupaku.c', name: 'Paku Paku' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)', category: 'assembler' },
|
||||
|
||||
Reference in new issue
Block a user