254 lines
5.3 KiB
C
254 lines
5.3 KiB
C
#ifndef EZNES_H
|
|
#define EZNES_H
|
|
/**
|
|
EZnes: a set of helpers for easy NES indie development in pure C
|
|
for CC65 (in text mode)
|
|
|
|
Created by Luxferre in 2026, released into public domain
|
|
*/
|
|
|
|
#include <nes.h>
|
|
#include <joystick.h>
|
|
#include <conio.h>
|
|
|
|
#define u8 unsigned char
|
|
#define u16 unsigned short
|
|
#define s8 signed char
|
|
#define s16 signed short
|
|
|
|
/* Generic constants */
|
|
|
|
#ifdef PAL
|
|
#define CPU 1662607 /* PAL */
|
|
#else
|
|
#define CPU 1789773 /* NTSC */
|
|
#endif
|
|
|
|
#define SCREEN_WIDTH 32
|
|
#define SCREEN_HEIGHT 28
|
|
#define SCREEN_CENTER_X (SCREEN_WIDTH >> 1)
|
|
#define SCREEN_CENTER_Y (SCREEN_HEIGHT >> 1)
|
|
|
|
#define EZJOY_UP 1
|
|
#define EZJOY_DN 2
|
|
#define EZJOY_LT 3
|
|
#define EZJOY_RT 4
|
|
#define EZJOY_A 5
|
|
#define EZJOY_B 6
|
|
#define EZJOY_SL 7
|
|
#define EZJOY_ST 8
|
|
#define EZJOY_COMBO 9
|
|
|
|
#define EZ_CORNER_UL (char) 20
|
|
#define EZ_CORNER_UR (char) 18
|
|
#define EZ_CORNER_DL (char) 17
|
|
#define EZ_CORNER_DR (char) 8
|
|
|
|
#define NES_BLACK 0x0F
|
|
#define NES_WHITE 0x30
|
|
#define NES_RED 0x15
|
|
#define NES_GREEN 0x28
|
|
#define NES_YELLOW 0x2A
|
|
|
|
/* RNG stuff */
|
|
static u16 _ez_entropy = 0;
|
|
|
|
u16 ezrand() {
|
|
_ez_entropy = 1 + ((_ez_entropy >> 15) | (_ez_entropy << 1));
|
|
return ((u16) rand()) ^ _ez_entropy;
|
|
}
|
|
|
|
/* Graphics section */
|
|
|
|
void ppu_off() {
|
|
PPU.mask &= 0xE7;
|
|
}
|
|
|
|
void ppu_on() {
|
|
waitvsync();
|
|
PPU.scroll = 0;
|
|
PPU.mask |= 0x1A;
|
|
}
|
|
|
|
u8 joy_input_wait() {
|
|
u8 last_joy = 0, joy, pressed;
|
|
/* wait until the previous key is released */
|
|
while(joy_read(JOY_1));
|
|
/* wait for a new press */
|
|
while(1) {
|
|
++_ez_entropy;
|
|
joy = joy_read(JOY_1);
|
|
pressed = joy & ~last_joy;
|
|
if(pressed & JOY_UP_MASK) return EZJOY_UP;
|
|
if(pressed & JOY_DOWN_MASK) return EZJOY_DN;
|
|
if(pressed & JOY_LEFT_MASK) return EZJOY_LT;
|
|
if(pressed & JOY_RIGHT_MASK) return EZJOY_RT;
|
|
if(pressed & JOY_BTN_1_MASK) return EZJOY_A;
|
|
if(pressed & JOY_BTN_2_MASK) return EZJOY_B;
|
|
if(pressed & JOY_BTN_3_MASK) return EZJOY_SL;
|
|
if(pressed & JOY_BTN_4_MASK) return EZJOY_ST;
|
|
last_joy = joy;
|
|
waitvsync();
|
|
}
|
|
}
|
|
|
|
/* palette loader */
|
|
void init_palette(u8 bg1, u8 bg2, u8 bg3, u8 bg4, u8 c1, u8 c2, u8 c3, u8 c4) {
|
|
u8 i, pal[16] = {0};
|
|
pal[0] = bg1;
|
|
pal[4] = bg2;
|
|
pal[8] = bg3;
|
|
pal[12] = bg4;
|
|
pal[3] = c1;
|
|
pal[7] = c2;
|
|
pal[11] = c3;
|
|
pal[15] = c4;
|
|
PPU.vram.address = 0x3F;
|
|
PPU.vram.address = 0;
|
|
for(i=0; i<16; i++) PPU.vram.data = pal[i];
|
|
}
|
|
|
|
/* 4x4 char block colorization method (palette is 0 to 3) */
|
|
void colorxy(u8 x, u8 y, u8 palette) {
|
|
u16 addr;
|
|
u8 attr_byte;
|
|
addr = 0x23C0 + ((y >> 2) << 3) + (x >> 2);
|
|
attr_byte = palette | (palette << 2) | (palette << 4) | (palette << 6);
|
|
waitvsync();
|
|
PPU.vram.address = (u8) (addr >> 8);
|
|
PPU.vram.address = (u8) (addr & 255);
|
|
PPU.vram.data = attr_byte;
|
|
}
|
|
|
|
/* Sound section */
|
|
|
|
u16 get_timer_by_freq(u16 freq) {
|
|
return (u16) ((CPU / 16 / freq) - 1);
|
|
}
|
|
|
|
void play_pulse(u8 idx, u16 freq, u8 len) {
|
|
u16 tm = get_timer_by_freq(freq);
|
|
if(len > 31) len = 31;
|
|
if(idx > 1) idx = 0;
|
|
APU.pulse[idx].period_low = tm & 255;
|
|
APU.pulse[idx].len_period_high = (tm >> 8) | (len << 3);
|
|
}
|
|
|
|
void play_jingle(u16 n1f1,
|
|
u16 n1f2,
|
|
u16 n2f1,
|
|
u16 n2f2,
|
|
u16 n3f1,
|
|
u16 n3f2) {
|
|
u16 i, dl = 2000;
|
|
play_pulse(0, n1f1, 2);
|
|
play_pulse(1, n1f2, 2);
|
|
for(i=0;i<dl;++i);
|
|
play_pulse(0, n2f1, 2);
|
|
play_pulse(1, n2f2, 2);
|
|
for(i=0;i<dl;++i);
|
|
play_pulse(0, n3f1, 2);
|
|
play_pulse(1, n3f2, 2);
|
|
}
|
|
|
|
void init_sound() {
|
|
APU.status = 3;
|
|
APU.pulse[0].ramp = 0x8;
|
|
APU.pulse[1].ramp = 0x8;
|
|
APU.pulse[0].control = 0x9F;
|
|
APU.pulse[1].control = 0x9F;
|
|
waitvsync();
|
|
}
|
|
|
|
/* Game initialization section */
|
|
|
|
void init_game(u8 bg1, u8 bg2, u8 bg3, u8 bg4, u8 c1, u8 c2, u8 c3, u8 c4) {
|
|
init_sound();
|
|
bgcolor(COLOR_BLACK);
|
|
textcolor(0);
|
|
init_palette(bg1, bg2, bg3, bg4, c1, c2, c3, c4);
|
|
joy_install(joy_static_stddrv);
|
|
clrscr();
|
|
}
|
|
|
|
/* wait until Start is pressed */
|
|
void wait_for_start() {
|
|
u8 joy;
|
|
while(1) {
|
|
++_ez_entropy;
|
|
joy = joy_read(JOY_1);
|
|
if(JOY_START(joy)) break;
|
|
waitvsync();
|
|
}
|
|
}
|
|
|
|
/* randomize until Start is pressed */
|
|
void wait_for_start_rand() {
|
|
u8 joy;
|
|
#ifdef _randomize
|
|
_randomize();
|
|
#endif
|
|
_ez_entropy = rand();
|
|
while(1) {
|
|
++_ez_entropy;
|
|
joy = joy_read(JOY_1);
|
|
if(JOY_START(joy)) break;
|
|
waitvsync();
|
|
}
|
|
srand(_ez_entropy);
|
|
}
|
|
|
|
/* wait for Start or a button combination */
|
|
u8 wait_for_start_or_combo(u8 combo_mask) {
|
|
u8 last_joy = 0, joy, pressed;
|
|
/* wait until the previous key is released */
|
|
while(joy_read(JOY_1));
|
|
/* wait for a new press */
|
|
while(1) {
|
|
++_ez_entropy;
|
|
joy = joy_read(JOY_1);
|
|
pressed = joy & ~last_joy;
|
|
if(pressed == combo_mask) return EZJOY_COMBO;
|
|
if(pressed & JOY_BTN_4_MASK) return EZJOY_ST;
|
|
last_joy = joy;
|
|
waitvsync();
|
|
}
|
|
}
|
|
|
|
/* Generic helper methods */
|
|
|
|
/* Array shuffle method */
|
|
void shuffle(s8 *array, u16 n) {
|
|
u16 i, j, t;
|
|
if(n > 1) {
|
|
for(i=n-1;i>0;--i) {
|
|
j = (u16) (ezrand()%(i+1));
|
|
t = array[j];
|
|
array[j] = array[i];
|
|
array[i] = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Delay by n cycles */
|
|
void delay_cycles(u16 n) {
|
|
u16 i;
|
|
for(i=0;i<n;++i);
|
|
}
|
|
|
|
/* Draw a proper rectangle with corners */
|
|
void rect(u8 x, u8 y, u8 w, u8 h) {
|
|
u8 bl_y = y + h - 1, tr_x = x + w - 1;
|
|
cputcxy(x, y, EZ_CORNER_UL);
|
|
chlinexy(x + 1, y, w - 2);
|
|
cputcxy(tr_x, y, EZ_CORNER_UR);
|
|
chlinexy(x + 1, bl_y, w - 2);
|
|
cvlinexy(x, y + 1, h - 2);
|
|
cputcxy(x, bl_y, EZ_CORNER_DL);
|
|
cvlinexy(tr_x, y + 1, h - 2);
|
|
cputcxy(tr_x, bl_y, EZ_CORNER_DR);
|
|
}
|
|
|
|
#endif
|
|
|