Offloaded some NES routines to eznes.h
This commit is contained in:
+179
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user