Offloaded some NES routines to eznes.h

This commit is contained in:
Luxferre
2026-01-11 14:27:20 +02:00
parent a4f54caaec
commit f1eecc00f1
2 changed files with 219 additions and 171 deletions
+179
View File
@@ -0,0 +1,179 @@
#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 char
#define s16 short
/* Generic constants */
#define CPU 1789773 /* NTSC */
#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 NES_BLACK 0x0F
#define NES_WHITE 0x30
#define NES_RED 0x15
#define NES_GREEN 0x28
#define NES_YELLOW 0x2A
/* Graphics section */
void ppu_off() {
PPU.mask &= 0xE7;
}
void ppu_on() {
waitvsync();
PPU.scroll = 0;
PPU.mask |= 0x1A;
}
int 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) {
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 bg, u8 c1, u8 c2, u8 c3, u8 c4) {
u8 i, pal[16] = {0};
pal[0] = pal[4] = pal[8] = pal[12] = bg;
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) {
unsigned int 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 */
unsigned int get_timer_by_freq(int freq) {
return (unsigned int) ((CPU / 16 / freq) - 1);
}
void play_pulse(u8 idx, unsigned int freq, u8 len) {
unsigned int 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(unsigned int n1f1,
unsigned int n1f2,
unsigned int n2f1,
unsigned int n2f2,
unsigned int n3f1,
unsigned int n3f2) {
unsigned int 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 bg, u8 c1, u8 c2, u8 c3, u8 c4) {
#ifdef _randomize
_randomize();
#endif
init_sound();
bgcolor(COLOR_BLACK);
textcolor(0);
init_palette(bg, c1, c2, c3, c4);
joy_install(joy_static_stddrv);
clrscr();
}
/* wait until Start is pressed */
void wait_for_start() {
u8 joy;
while(1) {
joy = joy_read(JOY_1);
if(JOY_START(joy)) break;
waitvsync();
}
}
/* randomize until Start is pressed */
void wait_for_start_rand() {
u8 joy;
unsigned int entropy = rand();
while(1) {
++entropy;
joy = joy_read(JOY_1);
if(JOY_START(joy)) break;
waitvsync();
}
srand(entropy);
}
#endif
+40 -171
View File
@@ -4,135 +4,20 @@
Created by Luxferre in 2026, released into public domain
*/
#include <nes.h>
#include <joystick.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "eznes.h"
/* Constants section */
#define SCREEN_WIDTH 32
#define SCREEN_HEIGHT 28
#define SCREEN_CENTER_X (SCREEN_WIDTH >> 1)
#define SCREEN_CENTER_Y (SCREEN_HEIGHT >> 1)
#define KEY_UP 1
#define KEY_DN 2
#define KEY_LT 3
#define KEY_RT 4
#define KEY_A 5
#define KEY_B 6
#define KEY_SL 7
#define KEY_ST 8
#define GAME_WHITE 0
#define GAME_RED 1
#define GAME_YELLOW 2
#define GAME_GREEN 3
/* Graphics section */
void ppu_off() {
PPU.mask &= 0xE7;
}
void ppu_on() {
waitvsync();
PPU.scroll = 0;
PPU.mask |= 0x1A;
}
int joy_input_wait() {
unsigned char last_joy = 0, joy, pressed;
/* wait until the previous key is released */
while(joy_read(JOY_1));
/* wait for a new press */
while(1) {
joy = joy_read(JOY_1);
pressed = joy & ~last_joy;
if(pressed & JOY_UP_MASK) return KEY_UP;
if(pressed & JOY_DOWN_MASK) return KEY_DN;
if(pressed & JOY_LEFT_MASK) return KEY_LT;
if(pressed & JOY_RIGHT_MASK) return KEY_RT;
if(pressed & JOY_BTN_1_MASK) return KEY_A;
if(pressed & JOY_BTN_2_MASK) return KEY_B;
if(pressed & JOY_BTN_3_MASK) return KEY_SL;
if(pressed & JOY_BTN_4_MASK) return KEY_ST;
last_joy = joy;
waitvsync();
}
}
/* palette loader */
void init_palette() {
unsigned char i;
const unsigned char my_palette[16] = {
0x0f, 0, 0, 0x30, /* white */
0x0f, 0, 0, 0x15, /* red */
0x0f, 0, 0, 0x28, /* yellow */
0x0f, 0, 0, 0x2A /* green */
};
PPU.vram.address = 0x3F;
PPU.vram.address = 0;
for(i=0; i<16; i++) PPU.vram.data = my_palette[i];
}
/* 4x4 char block colorization method (palette is 0 to 3) */
void colorxy(unsigned char x, unsigned char y, unsigned char palette) {
unsigned int addr;
unsigned char attr_byte;
addr = 0x23C0 + ((y >> 2) << 3) + (x >> 2);
attr_byte = palette | (palette << 2) | (palette << 4) | (palette << 6);
waitvsync();
PPU.vram.address = (unsigned char) (addr >> 8);
PPU.vram.address = (unsigned char) (addr & 255);
PPU.vram.data = attr_byte;
}
/* Sound section */
#define CPU 1789773 /* NTSC */
unsigned int get_timer(int freq) {
return (unsigned int) ((CPU / 16 / freq) - 1);
}
void play_pulse(unsigned char idx, unsigned int freq, unsigned char len) {
unsigned int tm = get_timer(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(unsigned int n1f1,
unsigned int n1f2,
unsigned int n2f1,
unsigned int n2f2,
unsigned int n3f1,
unsigned int n3f2) {
unsigned int 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();
}
void beep_start() {
play_jingle(220, 440, 440, 660, 660, 880);
}
@@ -152,7 +37,7 @@ void beep_run() {
play_jingle(660, 880, 440, 660, 220, 440);
}
void beep_win() {
unsigned short i;
u16 i;
play_jingle(55, 110, 110, 220, 220, 440);
for(i=0;i<4000;++i);
play_jingle(440, 660, 660, 880, 880, 1760);
@@ -160,7 +45,7 @@ void beep_win() {
play_jingle(880, 660, 660, 880, 880, 1760);
}
void beep_lose() {
unsigned short i;
u16 i;
play_jingle(110, 55, 220, 110, 110, 55);
for(i=0;i<4000;++i);
play_jingle(110, 55, 110, 55, 110, 55);
@@ -172,21 +57,12 @@ void beep_err() {
/* Main game logic */
void game_startup() {
unsigned int entropy;
unsigned char joy, i, j;
#ifdef _randomize
_randomize();
#endif
entropy = rand();
init_sound();
bgcolor(COLOR_BLACK);
textcolor(0);
init_palette();
clrscr();
u8 i, j, x = 0;
init_game(NES_BLACK, NES_WHITE, NES_RED, NES_GREEN, NES_YELLOW);
ppu_off();
for(j=0;j<24;j+=4)
for(i=3;i<32;i+=4)
colorxy(i, j, 1 + ((++entropy) % 3));
colorxy(i, j, 1 + ((++x) % 3));
gotoxy(0, 2);
cputs( \
" _______ _______ _______ \r\n" \
@@ -215,22 +91,15 @@ void game_startup() {
cputsxy(SCREEN_CENTER_X - 6, SCREEN_HEIGHT - 4, "Press Start");
cputsxy(1, SCREEN_HEIGHT - 2, "Luxferre");
cputsxy(SCREEN_WIDTH-5, SCREEN_HEIGHT - 2, "2026");
joy_install(joy_static_stddrv);
ppu_on();
while(1) {
++entropy;
joy = joy_read(JOY_1);
if(JOY_START(joy)) break;
waitvsync();
}
srand(entropy);
wait_for_start_rand();
beep_start();
clrscr();
}
/* Win screen display */
void win_screen(int score) {
unsigned char i, j;
void win_screen(s16 score) {
u8 i, j;
clrscr();
ppu_off();
for(j=0;j<12;j+=4)
@@ -253,7 +122,7 @@ void win_screen(int score) {
ppu_on();
waitvsync();
beep_win();
while(joy_input_wait() != KEY_ST);
wait_for_start();
}
/* UI choices */
@@ -286,7 +155,7 @@ static char deck[44], room[] = {0, 0, 0, 0};
static char status_msg[(SCREEN_WIDTH-1)];
/* tracking variables */
static short deck_idx = 0, hp = 20, rooms_cleared = 0,
static s16 deck_idx = 0, hp = 20, rooms_cleared = 0,
weapon = 0, durability = 0, can_run = 1,
engaged = 0, potion_drank = 0, last_potion_val = 0;
@@ -294,18 +163,18 @@ static short deck_idx = 0, hp = 20, rooms_cleared = 0,
/* up 1, left 2, right 3, down 4, a run, start quit */
char get_choice() {
switch(joy_input_wait()) {
case KEY_UP: return CH1;
case KEY_LT: return CH2;
case KEY_RT: return CH3;
case KEY_DN: return CH4;
case KEY_ST: return CH_Q;
case EZJOY_UP: return CH1;
case EZJOY_LT: return CH2;
case EZJOY_RT: return CH3;
case EZJOY_DN: return CH4;
case EZJOY_ST: return CH_Q;
default: return CH_R;
}
}
/* Draw a room character according to the index and value */
void render_room_item(short i, short v) {
unsigned char color,
void render_room_item(s16 i, s16 v) {
u8 color,
topleft_x, topleft_y,
topright_x, bottomleft_y,
text_x, text_y, cheight=7, cwidth=8, left_offset = 9;
@@ -362,7 +231,7 @@ void render_room_item(short i, short v) {
/* Render main game screen */
void render_main_scene() {
unsigned char i, deck_count = 0, statbase = 17,
u8 i, deck_count = 0, statbase = 17,
room_start_x = 1,
room_start_y = 1,
room_end_x = statbase - 8,
@@ -404,8 +273,8 @@ void render_main_scene() {
}
/* Confirmation method */
unsigned char confirm(char *prompt) {
unsigned char res = 2, statbase = SCREEN_WIDTH - 7;
u8 confirm(char *prompt) {
u8 res = 2, statbase = SCREEN_WIDTH - 7;
snprintf(status_msg, sizeof(status_msg), "%s", prompt);
render_main_scene();
gotoxy(statbase, SCREEN_HEIGHT-5);
@@ -414,8 +283,8 @@ unsigned char confirm(char *prompt) {
cprintf("B=NO");
while(res > 1) {
switch(joy_input_wait()) {
case KEY_A: res = 1; break;
case KEY_B: res = 0; break;
case EZJOY_A: res = 1; break;
case EZJOY_B: res = 0; break;
default: res = 2;
}
}
@@ -431,11 +300,11 @@ void print_msg(char *s) {
}
/* Array shuffle method */
void shuffle(char *array, unsigned int n) {
unsigned int i, j, t;
void shuffle(char *array, u16 n) {
u16 i, j, t;
if(n > 1) {
for(i=n-1;i>0;i--) {
j = (unsigned int) (rand()%(i+1));
j = (u16) (rand()%(i+1));
t = array[j];
array[j] = array[i];
array[i] = t;
@@ -445,12 +314,12 @@ void shuffle(char *array, unsigned int n) {
/* Room item handling methods */
int handle_monster(int val) {
int healthlost = val;
s16 handle_monster(s16 val) {
s16 healthlost = val;
char monster_msg[(SCREEN_WIDTH-1)];
beep_enemy();
if(weapon > 0 && val <= durability) {
int weapondiff = val - weapon;
s16 weapondiff = val - weapon;
if(weapondiff < 0) weapondiff = 0;
snprintf(monster_msg, sizeof(status_msg), "%d-level monster: use weapon?", val);
if(confirm(monster_msg)) {
@@ -469,14 +338,14 @@ int handle_monster(int val) {
return val;
}
void handle_weapon(int val) {
void handle_weapon(s16 val) {
weapon = val;
snprintf(status_msg, sizeof(status_msg), "You equip a %d-level weapon.", weapon);
durability = 14; /* reset durability to max limit */
beep_weapon();
}
void handle_potion(unsigned char val) {
void handle_potion(u8 val) {
if(potion_drank) {
print_msg("Already drank a potion here.");
beep_err();
@@ -492,11 +361,11 @@ void handle_potion(unsigned char val) {
}
/* Main gameplay */
int game() {
unsigned int i;
int score = -208; /* score storage */
int item, room_sum; /* true item value */
int last_room = 0, last_room_clr = 0; /* last room markers */
s16 game() {
u16 i;
s16 score = -208; /* score storage */
s16 item, room_sum; /* true item value */
s16 last_room = 0, last_room_clr = 0; /* last room markers */
char ch;
deck_idx = 0; /* reset the deck index */
/* Prefill the deck */
@@ -557,7 +426,7 @@ int game() {
} else if(ch == CH_R) { /* run mechanics */
/* Compact and rearrange the deck */
char temp_deck[sizeof(deck)];
int t_idx = 0;
s16 t_idx = 0;
/* 1. Collect all valid remaining cards from deck */
for(i=0; i<sizeof(deck); i++) {
@@ -630,9 +499,9 @@ int game() {
return score;
}
/* Entry point */
int main() {
int score;
/* Entry pos16 */
s16 main() {
s16 score;
game_startup();
while(1) {
score = game();