Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9034b3762 | ||
|
|
12a5d39c54 | ||
|
|
7c2a0b24ed | ||
|
|
af08983f56 | ||
|
|
fc6a230af7 | ||
|
|
7f2cab0b5e | ||
|
|
c6a026846a | ||
|
|
e8156e49e5 | ||
|
|
eb3feaf34c | ||
|
|
f1eecc00f1 | ||
|
|
a4f54caaec |
@@ -6,6 +6,7 @@ This is a collection of my own ports of the [Scoundrel](http://stfj.net/art/2011
|
||||
|
||||
* [ANSI C89 port](c/scoundrel.c) - also compatible with CC65 and Z88DK (use the provided Makefile to build Scoundrel for Commodore 16/64/128/Plus-4/PET, Atari 400/800/XL/XE, ZX Spectrum, MSX and Apple II/IIe; a JRE installation on the host system is required to build Apple disk images)
|
||||
* [NES port](nes/nescoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
||||
* [Game Boy port](gb/gbscoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
||||
|
||||
## Ports NOT included in this repo for various reasons
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ Z80BUILD=zcc
|
||||
BUILDDIR=build
|
||||
|
||||
$(PROJNAME): bdir
|
||||
$(CC) -std=c89 -O2 -o $(BUILDDIR)/scoundrel $(CFILES)
|
||||
$(CC) -std=c89 -O2 -o $(BUILDDIR)/$(PROJNAME) $(CFILES)
|
||||
|
||||
bdir:
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
||||
+4
-10
@@ -163,7 +163,7 @@ void shuffle(char *array, size_t n) {
|
||||
|
||||
/* Room item handling methods */
|
||||
|
||||
void handle_monster(int val) {
|
||||
int handle_monster(int val) {
|
||||
int healthlost = val;
|
||||
printf("You fight a %d-level monster.\n", val);
|
||||
if(weapon > 0 && val <= durability) {
|
||||
@@ -184,6 +184,7 @@ void handle_monster(int val) {
|
||||
hp -= healthlost;
|
||||
if(hp < 0) hp = 0;
|
||||
printf("You lost %d HP. New HP: %d.\n", healthlost, hp);
|
||||
return val;
|
||||
}
|
||||
|
||||
void handle_weapon(int val) {
|
||||
@@ -208,7 +209,7 @@ void handle_potion(int val) {
|
||||
/* Main gameplay */
|
||||
int game() {
|
||||
unsigned int i;
|
||||
int score = 0; /* score storage */
|
||||
int score = -208; /* score storage */
|
||||
int item, room_sum; /* true item value */
|
||||
int last_room = 0, last_room_clr = 0; /* last room markers */
|
||||
char ch;
|
||||
@@ -306,7 +307,7 @@ int game() {
|
||||
/* Note: We do NOT put the item back in deck. It is consumed. */
|
||||
|
||||
if(item < 14) { /* monster */
|
||||
handle_monster(item + 1);
|
||||
score += handle_monster(item + 1);
|
||||
} else if(item < 23) { /* weapon */
|
||||
handle_weapon(item - 12);
|
||||
} else { /* potion */
|
||||
@@ -339,13 +340,6 @@ int game() {
|
||||
if(hp > 0) { /* win */
|
||||
score = hp;
|
||||
if(hp == 20) score += last_potion_val;
|
||||
} else { /* fail */
|
||||
for(i=0;i<sizeof(room);i++)
|
||||
if(room[i] && room[i] < 14)
|
||||
score -= (room[i] + 1);
|
||||
for(i=0;i<sizeof(deck);i++)
|
||||
if(deck[i] && deck[i] < 14)
|
||||
score -= (deck[i] + 1);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
CFILES = gbscoundrel.c
|
||||
OUTFILE = scoundrel.gb
|
||||
|
||||
$(OUTFILE): $(CFILES)
|
||||
zcc +gb -create-app -Os $(CFILES) -o $(OUTFILE)
|
||||
|
||||
clean:
|
||||
rm -f *.gb *.bin *.o
|
||||
@@ -0,0 +1,232 @@
|
||||
#ifndef EZGB_H
|
||||
#define EZGB_H
|
||||
/**
|
||||
EZgb: a set of helpers for easy GB indie development in pure C
|
||||
for Z88DK (in text mode)
|
||||
|
||||
Created by Luxferre in 2026, released into public domain
|
||||
*/
|
||||
|
||||
#include <gb/gb.h>
|
||||
#include <conio.h>
|
||||
#include <curses.h>
|
||||
|
||||
#define u8 unsigned char
|
||||
#define u16 unsigned short
|
||||
#define s8 signed char
|
||||
#define s16 signed short
|
||||
|
||||
/* Generic constants */
|
||||
|
||||
#define SCREEN_WIDTH 20
|
||||
#define SCREEN_HEIGHT 18
|
||||
#define SCREEN_CENTER_X (SCREEN_WIDTH >> 1)
|
||||
#define SCREEN_CENTER_Y (SCREEN_HEIGHT >> 1)
|
||||
#define NL "\n"
|
||||
|
||||
#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
|
||||
|
||||
/* RNG stuff */
|
||||
static u16 _ez_entropy = 0;
|
||||
|
||||
u16 ezrand() {
|
||||
_ez_entropy = 1 + ((_ez_entropy >> 15) | (_ez_entropy << 1));
|
||||
return ((u16) rand()) ^ _ez_entropy;
|
||||
}
|
||||
|
||||
u8 joy_input_wait() {
|
||||
u8 last_joy = 0, joy, pressed;
|
||||
/* wait until the previous key is released */
|
||||
while(joypad());
|
||||
/* wait for a new press */
|
||||
while(1) {
|
||||
++_ez_entropy;
|
||||
joy = joypad();
|
||||
pressed = joy & ~last_joy;
|
||||
if(pressed & J_UP) return EZJOY_UP;
|
||||
if(pressed & J_DOWN) return EZJOY_DN;
|
||||
if(pressed & J_LEFT) return EZJOY_LT;
|
||||
if(pressed & J_RIGHT) return EZJOY_RT;
|
||||
if(pressed & J_A) return EZJOY_A;
|
||||
if(pressed & J_B) return EZJOY_B;
|
||||
if(pressed & J_SELECT) return EZJOY_SL;
|
||||
if(pressed & J_START) return EZJOY_ST;
|
||||
last_joy = joy;
|
||||
wait_vbl_done();
|
||||
}
|
||||
}
|
||||
|
||||
/* wait until Start is pressed */
|
||||
void wait_for_start() {
|
||||
u8 joy;
|
||||
while(1) {
|
||||
++_ez_entropy;
|
||||
joy = joypad();
|
||||
if(joy & J_START) break;
|
||||
wait_vbl_done();
|
||||
}
|
||||
}
|
||||
|
||||
/* randomize until Start is pressed */
|
||||
void wait_for_start_rand() {
|
||||
u8 joy;
|
||||
#ifdef _randomize
|
||||
_randomize();
|
||||
#endif
|
||||
_ez_entropy = rand();
|
||||
while(1) {
|
||||
++_ez_entropy;
|
||||
joy = joypad();
|
||||
if(joy & J_START) break;
|
||||
wait_vbl_done();
|
||||
}
|
||||
srand(_ez_entropy);
|
||||
}
|
||||
|
||||
/* Sound methods */
|
||||
|
||||
void init_sound() {
|
||||
NR52_REG = 0x80;
|
||||
NR50_REG = 0x77;
|
||||
NR51_REG = 0xFF;
|
||||
}
|
||||
|
||||
u16 get_timer_by_freq(u16 freq) {
|
||||
return (u16) (2048 - (131072 / freq));
|
||||
}
|
||||
|
||||
void play_pulse(u8 channel, u16 freq, u16 duration) {
|
||||
u16 tm = get_timer_by_freq(freq);
|
||||
|
||||
if(channel == 0) {
|
||||
NR10_REG = 0x00;
|
||||
NR11_REG = 0x80 | duration;
|
||||
NR12_REG = 0xF3;
|
||||
NR13_REG = tm & 255;
|
||||
NR14_REG = 0xC0 | (tm >> 8);
|
||||
} else {
|
||||
NR21_REG = 0x80 | duration;
|
||||
NR22_REG = 0xF3;
|
||||
NR23_REG = tm & 255;
|
||||
NR24_REG = 0xC0 | (tm >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
void play_jingle(u16 n1f1,
|
||||
u16 n1f2,
|
||||
u16 n2f1,
|
||||
u16 n2f2,
|
||||
u16 n3f1,
|
||||
u16 n3f2) {
|
||||
u16 i, dl = 2600;
|
||||
play_pulse(0, n1f1, 1);
|
||||
play_pulse(1, n1f2, 1);
|
||||
for(i=0;i<dl;++i);
|
||||
play_pulse(0, n2f1, 1);
|
||||
play_pulse(1, n2f2, 1);
|
||||
for(i=0;i<dl;++i);
|
||||
play_pulse(0, n3f1, 1);
|
||||
play_pulse(1, n3f2, 1);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* CC65 compat */
|
||||
|
||||
#define EZ_HLINE (char) 0x0e
|
||||
#define EZ_VLINE (char) 0x0f
|
||||
#define EZ_CORNER_UL (char) 0x1c
|
||||
#define EZ_CORNER_UR (char) 0x1d
|
||||
#define EZ_CORNER_DL (char) 0x1e
|
||||
#define EZ_CORNER_DR (char) 0x1f
|
||||
|
||||
#define cprintf printf
|
||||
|
||||
void convdigits(char *s, u16 len) {
|
||||
u16 i;
|
||||
for(i=0;i<len;i++) {
|
||||
if(s[i] >= 0x30 && s[i] < 0x3A)
|
||||
s[i] -= 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
void cputcxy(u8 x, u8 y, char c) {
|
||||
gotoxy(x, y);
|
||||
cputc(c);
|
||||
}
|
||||
|
||||
void cputsxy(u8 x, u8 y, char *s) {
|
||||
gotoxy(x, y);
|
||||
cprintf("%s", s);
|
||||
}
|
||||
|
||||
void chlinexy(u8 x, u8 y, u8 l) {
|
||||
u8 i;
|
||||
gotoxy(x, y);
|
||||
for(i=0;i<l;++i) cputc(EZ_HLINE);
|
||||
}
|
||||
|
||||
void cvlinexy(u8 x, u8 y, u8 l) {
|
||||
u8 i;
|
||||
for(i=0;i<l;++i) {
|
||||
gotoxy(x, y+i);
|
||||
cputc(EZ_VLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a proper rectangle with corners */
|
||||
void rect(u8 x, u8 y, u8 w, u8 h) {
|
||||
u8 i, bl_y = y + h - 1, tr_x = x + w - 1;
|
||||
gotoxy(x, y);
|
||||
cputc(EZ_CORNER_UL);
|
||||
for(i=0;i<w-2;++i) cputc(EZ_HLINE);
|
||||
cputc(EZ_CORNER_UR);
|
||||
for(i=0;i<h-2;++i) {
|
||||
gotoxy(x, y+i+1); cputc(EZ_VLINE);
|
||||
gotoxy(tr_x, y+i+1); cputc(EZ_VLINE);
|
||||
}
|
||||
gotoxy(x, bl_y);
|
||||
cputc(EZ_CORNER_DL);
|
||||
for(i=0;i<w-2;++i) cputc(EZ_HLINE);
|
||||
cputc(EZ_CORNER_DR);
|
||||
}
|
||||
|
||||
void termreset() {
|
||||
clrscr();
|
||||
move_bkg(0, 0);
|
||||
gotoxy(0, 0);
|
||||
}
|
||||
|
||||
void gamereset() {
|
||||
#asm
|
||||
jp 0
|
||||
#endasm
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,474 @@
|
||||
/**
|
||||
gbcoundrel.c: a Game Boy port of the Scoundrel roguelike card game
|
||||
using the official ruleset only
|
||||
Created by Luxferre in 2026, released into public domain
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "ezgb.h"
|
||||
|
||||
/* Sound section */
|
||||
|
||||
void beep_start() {
|
||||
play_jingle(220, 440, 440, 660, 660, 880);
|
||||
}
|
||||
void beep_interact() {
|
||||
play_jingle(440, 660, 220, 880, 440, 660);
|
||||
}
|
||||
void beep_enemy() {
|
||||
play_jingle(440, 220, 220, 110, 110, 110);
|
||||
}
|
||||
void beep_weapon() {
|
||||
play_jingle(660, 880, 440, 880, 660, 880);
|
||||
}
|
||||
void beep_potion() {
|
||||
play_jingle(440, 660, 660, 990, 660, 880);
|
||||
}
|
||||
void beep_run() {
|
||||
play_jingle(660, 880, 440, 660, 220, 440);
|
||||
}
|
||||
void beep_win() {
|
||||
play_jingle(55, 110, 110, 220, 220, 440);
|
||||
delay_cycles(6000);
|
||||
play_jingle(440, 660, 660, 880, 880, 1760);
|
||||
delay_cycles(6000);
|
||||
play_jingle(880, 660, 660, 880, 880, 1760);
|
||||
}
|
||||
void beep_lose() {
|
||||
play_jingle(110, 55, 220, 110, 110, 110);
|
||||
delay_cycles(6000);
|
||||
play_jingle(110, 110, 110, 110, 110, 110);
|
||||
}
|
||||
void beep_err() {
|
||||
play_jingle(330, 220, 330, 220, 330, 220);
|
||||
}
|
||||
|
||||
/* print helper utils */
|
||||
|
||||
static char numbuf[6];
|
||||
void printnum(s16 num) {
|
||||
memset(numbuf, 0, sizeof(numbuf));
|
||||
snprintf(numbuf, sizeof(numbuf), "%02d", num);
|
||||
convdigits(numbuf, sizeof(numbuf));
|
||||
cprintf("%s", numbuf);
|
||||
}
|
||||
|
||||
/* Main game logic */
|
||||
|
||||
void game_startup() {
|
||||
u8 i, j, x = 0;
|
||||
termreset();
|
||||
DISPLAY_OFF;
|
||||
cputs( " " NL
|
||||
" \x0e\x0e\x0e\x1d \x0e\x0e\x0e\x1d \x0e\x0e\x0e" NL
|
||||
" \x0f \x0f \x0f \x0f" NL
|
||||
" \x0e+\x0e \x0f + \x0f + \x0f" NL
|
||||
" \x0f \x0f \x0f \x0f" NL
|
||||
" \x1e\x0e\x0e\x0e \x0e\x0e\x0e\x1f \x0e\x0e\x0e " NL
|
||||
|
||||
" \x1d \x1c \x1c \x1c \x1c\x0e\x0e\x0e" NL
|
||||
" \x0f \x0f \x0f+ \x0f \x0f \x0f" NL
|
||||
" \x0f + \x0f \x0f + \x0f + \x0f" NL
|
||||
" \x0f \x0f \x0f +\x0f \x0f \x0f" NL
|
||||
" \x0e\x0e\x0e \x1f \x1f \x1e\x0e\x0e\x0e" NL
|
||||
|
||||
" \x1c\x0e\x0e\x0e \x1c\x0e\x0e\x0e\x1d \x1c\x1d" NL
|
||||
" \x0f \x0f \x0f \x0f\x1f" NL
|
||||
" \x0f\x0e+\x0e \x0f\x0e+\x0e \x0f +" NL
|
||||
" \x0f + \x0f \x0f \x1c\x1d" NL
|
||||
" \x0f + \x1e\x0e\x0e\x0e\x1f \x1e\x0e\x0e\x0e\x1f");
|
||||
cputsxy(1, SCREEN_HEIGHT - 1, "Luxferre");
|
||||
cputsxy(SCREEN_WIDTH-5, SCREEN_HEIGHT - 1, "2026");
|
||||
DISPLAY_ON;
|
||||
wait_vbl_done();
|
||||
wait_for_start_rand();
|
||||
beep_start();
|
||||
termreset();
|
||||
}
|
||||
|
||||
/* Win screen display */
|
||||
void win_screen(s16 score) {
|
||||
u8 i, j;
|
||||
termreset();
|
||||
cputs( \
|
||||
" \x1d \x1c \x0e\x0e\x0e \x1c \x1c" NL
|
||||
" \x0f \x0f \x0f \x0f+ \x0f" NL
|
||||
" \x0f + \x0f + \x0f + \x0f" NL
|
||||
" \x0f+ +\x0f \x0f \x0f +\x0f" NL
|
||||
" \x1e\x1f \x1e\x1f \x0e\x0e\x0e \x1f \x1f");
|
||||
cputsxy(2, 6, "Congratulations!");
|
||||
cputsxy(0, 7, " You made it across");
|
||||
cputsxy(0, 8, " the dangerous");
|
||||
cputsxy(0, 9, " dungeon alive!");
|
||||
gotoxy(0, 11);
|
||||
cprintf(" Your score: ");
|
||||
printnum(score);
|
||||
chlinexy(0, 12, SCREEN_WIDTH);
|
||||
cputs(" By: Luxferre, 2026");
|
||||
cputs(NL NL " Orig: Zach Gage," NL " Kurt Bieg, 2011");
|
||||
wait_vbl_done();
|
||||
beep_win();
|
||||
wait_for_start();
|
||||
}
|
||||
|
||||
/* UI choices */
|
||||
#define CH1 0
|
||||
#define CH2 1
|
||||
#define CH3 2
|
||||
#define CH4 3
|
||||
#define CH_R 4
|
||||
#define CH_Q 5
|
||||
#define CH_INVAL 6
|
||||
|
||||
/**
|
||||
Deck/room representation:
|
||||
0 is empty (this value cannot be in the deck, only in the room),
|
||||
1 to 13 is a monster,
|
||||
14 to 22 is a weapon,
|
||||
23 to 31 is a potion
|
||||
|
||||
Monster level: x + 1
|
||||
Weapon level: x - 12
|
||||
Potion level: x - 21
|
||||
*/
|
||||
|
||||
/* Game stats */
|
||||
|
||||
/* main deck and room */
|
||||
static s8 deck[44], room[] = {0, 0, 0, 0};
|
||||
|
||||
/* status message */
|
||||
static char status_msg[(SCREEN_WIDTH)];
|
||||
static char bufmsg[(SCREEN_WIDTH)];
|
||||
|
||||
/* tracking variables */
|
||||
static s16 hp = 20;
|
||||
static u8 deck_idx = 0, rooms_cleared = 0,
|
||||
weapon = 0, durability = 0, can_run = 1,
|
||||
engaged = 0, potion_drank = 0, last_potion_val = 0;
|
||||
|
||||
/* Input method */
|
||||
/* up 1, left 2, right 3, down 4, a run, start quit */
|
||||
u8 get_choice() {
|
||||
switch(joy_input_wait()) {
|
||||
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(u8 i, u8 v) {
|
||||
u8 topleft_x, topleft_y, cheight=4, cwidth=6;
|
||||
if(i == 0) {
|
||||
topleft_x = 7;
|
||||
topleft_y = 1;
|
||||
} else if(i == 1) {
|
||||
topleft_x = 0;
|
||||
topleft_y = 6;
|
||||
} else if(i == 2) {
|
||||
topleft_x = 14;
|
||||
topleft_y = 6;
|
||||
} else if(i == 3) {
|
||||
topleft_x = 7;
|
||||
topleft_y = 11;
|
||||
}
|
||||
if(v == 0) {} /* don't render anything */
|
||||
else {
|
||||
/* draw the rectangle */
|
||||
rect(topleft_x, topleft_y, cwidth, cheight);
|
||||
gotoxy(topleft_x + 1, topleft_y + 1);
|
||||
/* draw text */
|
||||
if(v < 14) {
|
||||
cputs("ENMY");
|
||||
} else if(v < 23) {
|
||||
cputs("WEAP");
|
||||
} else {
|
||||
cputs("POTI");
|
||||
}
|
||||
gotoxy(topleft_x + 2, topleft_y + 2);
|
||||
if(v < 14) printnum(v + 1);
|
||||
else if(v < 23) printnum(v - 12);
|
||||
else printnum(v - 21);
|
||||
}
|
||||
}
|
||||
|
||||
/* Render main game screen */
|
||||
void render_main_scene() {
|
||||
u8 i, deck_count = 0;
|
||||
for(i=0;i<sizeof(deck);i++)
|
||||
if(deck[i]) deck_count++;
|
||||
termreset();
|
||||
/* render the room */
|
||||
for(i=0;i<4;i++) render_room_item(i, room[i]);
|
||||
/* render deck count */
|
||||
rect(7, 6, 6, 4);
|
||||
cputsxy(8, 7, "DECK");
|
||||
gotoxy(9, 8);
|
||||
printnum(deck_count);
|
||||
/* render stats */
|
||||
gotoxy(1, 2);
|
||||
cprintf("HP "); printnum(hp);
|
||||
gotoxy(1, 3);
|
||||
cprintf("RC "); printnum(rooms_cleared);
|
||||
gotoxy(14, 2);
|
||||
cprintf("WP "); printnum(weapon);
|
||||
gotoxy(14, 3);
|
||||
cprintf("DR "); printnum(durability);
|
||||
/* render controls */
|
||||
cputsxy(1, 12, "Item:");
|
||||
cputsxy(1, 13, "D-Pad");
|
||||
if(can_run) {
|
||||
cputsxy(14, 12, "Run:");
|
||||
cputsxy(14, 13, "A/B/SL");
|
||||
} else {
|
||||
cputsxy(14, 12, "Cannot");
|
||||
cputsxy(15, 13, "run!");
|
||||
}
|
||||
/* render status message */
|
||||
chlinexy(0,SCREEN_HEIGHT-2,SCREEN_WIDTH);
|
||||
gotoxy(0, SCREEN_HEIGHT-1);
|
||||
cprintf("%s", status_msg);
|
||||
wait_vbl_done();
|
||||
}
|
||||
|
||||
/* Confirmation method */
|
||||
u8 confirm(char *prompt) {
|
||||
u8 res = 2, statbase = SCREEN_WIDTH - 5;
|
||||
memset(status_msg, 0, sizeof(status_msg));
|
||||
snprintf(status_msg, sizeof(status_msg), "%s", prompt);
|
||||
render_main_scene();
|
||||
gotoxy(statbase, SCREEN_HEIGHT-3);
|
||||
cprintf("A=YES");
|
||||
gotoxy(statbase, SCREEN_HEIGHT-2);
|
||||
cprintf("B=NO");
|
||||
while(res > 1) {
|
||||
switch(joy_input_wait()) {
|
||||
case EZJOY_A: res = 1; break;
|
||||
case EZJOY_B: res = 0; break;
|
||||
default: res = 2;
|
||||
}
|
||||
}
|
||||
beep_interact();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Output method */
|
||||
void print_msg(char *s) {
|
||||
memset(status_msg, 0, sizeof(status_msg));
|
||||
strncpy(status_msg, s, sizeof(status_msg));
|
||||
render_main_scene();
|
||||
}
|
||||
|
||||
/* Room item handling methods */
|
||||
|
||||
s16 handle_monster(s16 val) {
|
||||
s16 healthlost = val;
|
||||
char monster_msg[(SCREEN_WIDTH-1)];
|
||||
beep_enemy();
|
||||
if(weapon > 0 && val <= durability) {
|
||||
s16 weapondiff = val - weapon;
|
||||
if(weapondiff < 0) weapondiff = 0;
|
||||
snprintf(monster_msg, sizeof(status_msg), "(%d) Use weapon?", val);
|
||||
if(confirm(monster_msg)) {
|
||||
healthlost = weapondiff;
|
||||
durability = val - 1;
|
||||
if(durability < 2) {
|
||||
durability = weapon = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
hp -= healthlost;
|
||||
if(hp < 0) hp = 0;
|
||||
snprintf(status_msg, sizeof(status_msg), "Lost %d HP", healthlost);
|
||||
return val;
|
||||
}
|
||||
|
||||
void handle_weapon(u8 val) {
|
||||
weapon = val;
|
||||
snprintf(status_msg, sizeof(status_msg), "Equipped %d-lvl wpn", weapon);
|
||||
durability = 14; /* reset durability to max limit */
|
||||
beep_weapon();
|
||||
}
|
||||
|
||||
void handle_potion(u8 val) {
|
||||
if(potion_drank) {
|
||||
print_msg("Potion broken!");
|
||||
beep_err();
|
||||
} else {
|
||||
potion_drank = 1;
|
||||
hp += val;
|
||||
if(hp > 20) hp = 20;
|
||||
snprintf(status_msg, sizeof(status_msg), "Drank %d-lvl potion", val);
|
||||
if(deck_idx >= sizeof(deck) || deck[deck_idx] == 0)
|
||||
last_potion_val = val;
|
||||
beep_potion();
|
||||
}
|
||||
}
|
||||
|
||||
/* Main gameplay */
|
||||
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 */
|
||||
u8 ch;
|
||||
deck_idx = 0; /* reset the deck index */
|
||||
/* Prefill the deck */
|
||||
for(i=0;i<13;i++) deck[i] = deck[i+13] = i+1;
|
||||
for(i=26;i<44;i++) deck[i] = i - 12;
|
||||
/* Shuffle the deck */
|
||||
shuffle(deck, sizeof(deck));
|
||||
/* prepare all other parameters */
|
||||
hp = 20;
|
||||
can_run = 1;
|
||||
weapon = durability = engaged = rooms_cleared = 0;
|
||||
potion_drank = last_potion_val = 0;
|
||||
for(i=0;i<sizeof(room);i++) room[i] = 0; /* reset the room */
|
||||
print_msg("Good luck!"); /* reset the status message */
|
||||
|
||||
while(hp > 0 && !last_room_clr) {
|
||||
/* fill in the new room unless engaged */
|
||||
if(!engaged) {
|
||||
for(i=0;i<sizeof(room);i++) {
|
||||
/* fill only if empty */
|
||||
if(!room[i]) {
|
||||
/* Check if deck is empty or exhausted */
|
||||
if(deck_idx >= sizeof(deck) || deck[deck_idx] == 0) {
|
||||
last_room = 1;
|
||||
can_run = 0;
|
||||
} else {
|
||||
room[i] = deck[deck_idx];
|
||||
deck[deck_idx] = 0; /* remove from deck */
|
||||
deck_idx++;
|
||||
/* Check immediately if that was the last card */
|
||||
if(deck_idx >= sizeof(deck) || deck[deck_idx] == 0) {
|
||||
last_room = 1;
|
||||
can_run = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* display the stats and room */
|
||||
render_main_scene();
|
||||
ch = get_choice();
|
||||
|
||||
/* Logic validation */
|
||||
if(ch == CH_R) {
|
||||
if(!can_run || engaged) ch = CH_INVAL; /* Cannot run if disabled or engaged */
|
||||
} else if(ch < CH_R && !room[(int)ch]) {
|
||||
ch = CH_INVAL; /* empty item */
|
||||
}
|
||||
|
||||
if(ch == CH_Q) {
|
||||
hp = 0;
|
||||
break;
|
||||
} else if(ch == CH_INVAL) {
|
||||
print_msg("Invalid input!");
|
||||
beep_err();
|
||||
continue;
|
||||
} else if(ch == CH_R) { /* run mechanics */
|
||||
/* Compact and rearrange the deck */
|
||||
char temp_deck[sizeof(deck)];
|
||||
s16 t_idx = 0;
|
||||
|
||||
/* 1. Collect all valid remaining cards from deck */
|
||||
for(i=0; i<sizeof(deck); i++) {
|
||||
if(deck[i] != 0) temp_deck[t_idx++] = deck[i];
|
||||
}
|
||||
|
||||
/* 2. Shuffle room cards and append to the end */
|
||||
shuffle(room, sizeof(room));
|
||||
for(i=0; i<sizeof(room); i++) {
|
||||
if(room[i] != 0) {
|
||||
temp_deck[t_idx++] = room[i];
|
||||
room[i] = 0; /* Clear room */
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. Rebuild deck */
|
||||
memset(deck, 0, sizeof(deck));
|
||||
for(i=0; i<t_idx; i++) deck[i] = temp_deck[i];
|
||||
|
||||
/* 4. Reset state */
|
||||
deck_idx = 0; /* Reset index to start of compacted deck */
|
||||
can_run = 0; /* can't run twice in a row */
|
||||
engaged = 0;
|
||||
last_room = 0; /* Since we added cards, it's not the last room anymore */
|
||||
print_msg("Ran from the room!");
|
||||
beep_run();
|
||||
} else { /* engagement mechanics */
|
||||
engaged++; /* increase the amount of engaged cards */
|
||||
can_run = 0; /* Cannot run once engaged */
|
||||
|
||||
item = room[ch]; /* get the item from the room */
|
||||
room[ch] = 0; /* mark the room slot as empty */
|
||||
/* Note: We do NOT put the item back in deck. It is consumed. */
|
||||
|
||||
if(item < 14) { /* monster */
|
||||
score += handle_monster(item + 1);
|
||||
} else if(item < 23) { /* weapon */
|
||||
handle_weapon(item - 12);
|
||||
} else { /* potion */
|
||||
handle_potion(item - 21);
|
||||
}
|
||||
|
||||
/* condition to move to the next room */
|
||||
room_sum = 0;
|
||||
for(i=0;i<sizeof(room);i++) room_sum += room[i];
|
||||
|
||||
if(hp > 0) {
|
||||
if(room_sum == 0) {
|
||||
if(last_room) last_room_clr = 1;
|
||||
else {
|
||||
engaged = 0;
|
||||
potion_drank = 0;
|
||||
can_run = 1;
|
||||
rooms_cleared++;
|
||||
}
|
||||
} else if(!last_room && engaged == 3) {
|
||||
engaged = 0;
|
||||
potion_drank = 0;
|
||||
can_run = 1;
|
||||
rooms_cleared++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Score the result */
|
||||
if(hp > 0) { /* win */
|
||||
score = hp;
|
||||
if(hp == 20) score += last_potion_val;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
/* Entry point */
|
||||
s16 main() {
|
||||
s16 score;
|
||||
init_sound();
|
||||
while(1) {
|
||||
termreset();
|
||||
game_startup();
|
||||
while(1) {
|
||||
score = game();
|
||||
if(score > 0) {
|
||||
win_screen(score);
|
||||
break;
|
||||
} else {
|
||||
beep_lose();
|
||||
snprintf(bufmsg, sizeof(bufmsg), "(%d) Restart?", score);
|
||||
if(!confirm(bufmsg)) break;
|
||||
}
|
||||
}
|
||||
wait_vbl_done();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
#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 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);
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
+49
-210
@@ -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,17 +37,15 @@ void beep_run() {
|
||||
play_jingle(660, 880, 440, 660, 220, 440);
|
||||
}
|
||||
void beep_win() {
|
||||
unsigned short i;
|
||||
play_jingle(55, 110, 110, 220, 220, 440);
|
||||
for(i=0;i<4000;++i);
|
||||
delay_cycles(4000);
|
||||
play_jingle(440, 660, 660, 880, 880, 1760);
|
||||
for(i=0;i<4000;++i);
|
||||
delay_cycles(4000);
|
||||
play_jingle(880, 660, 660, 880, 880, 1760);
|
||||
}
|
||||
void beep_lose() {
|
||||
unsigned short i;
|
||||
play_jingle(110, 55, 220, 110, 110, 55);
|
||||
for(i=0;i<4000;++i);
|
||||
delay_cycles(4000);
|
||||
play_jingle(110, 55, 110, 55, 110, 55);
|
||||
}
|
||||
void beep_err() {
|
||||
@@ -172,21 +55,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_BLACK, NES_BLACK, 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,27 +89,20 @@ 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)
|
||||
for(i=3;i<32;i+=4)
|
||||
colorxy(i, j, 1 + (rand() % 3));
|
||||
colorxy(i, j, 1 + (ezrand() % 3));
|
||||
cputsxy(0, 3, " ___ ___ ___ ______ \r\n" \
|
||||
" | Y | | _ \\ \r\n" \
|
||||
" |. | |. |. | |\r\n" \
|
||||
@@ -253,7 +120,7 @@ void win_screen(int score) {
|
||||
ppu_on();
|
||||
waitvsync();
|
||||
beep_win();
|
||||
while(joy_input_wait() != KEY_ST);
|
||||
wait_for_start();
|
||||
}
|
||||
|
||||
/* UI choices */
|
||||
@@ -280,35 +147,34 @@ Potion level: x - 21
|
||||
/* Game stats */
|
||||
|
||||
/* main deck and room */
|
||||
static char deck[44], room[] = {0, 0, 0, 0};
|
||||
static s8 deck[44], room[] = {0, 0, 0, 0};
|
||||
|
||||
/* status message */
|
||||
static char status_msg[(SCREEN_WIDTH-1)];
|
||||
|
||||
/* tracking variables */
|
||||
static short deck_idx = 0, hp = 20, rooms_cleared = 0,
|
||||
static s16 hp = 20;
|
||||
static u8 deck_idx = 0, rooms_cleared = 0,
|
||||
weapon = 0, durability = 0, can_run = 1,
|
||||
engaged = 0, potion_drank = 0, last_potion_val = 0;
|
||||
|
||||
/* Input method */
|
||||
/* up 1, left 2, right 3, down 4, a run, start quit */
|
||||
char get_choice() {
|
||||
u8 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,
|
||||
topleft_x, topleft_y,
|
||||
topright_x, bottomleft_y,
|
||||
text_x, text_y, cheight=7, cwidth=8, left_offset = 9;
|
||||
void render_room_item(u8 i, u8 v) {
|
||||
u8 color, topleft_x, topleft_y, text_x, text_y,
|
||||
cheight=7, cwidth=8, left_offset = 9;
|
||||
if(i == 0) {
|
||||
topleft_x = 8;
|
||||
topleft_y = 0;
|
||||
@@ -322,8 +188,6 @@ void render_room_item(short i, short v) {
|
||||
topleft_x = 8;
|
||||
topleft_y = 16;
|
||||
}
|
||||
topright_x = topleft_x + cwidth - 1;
|
||||
bottomleft_y = topleft_y + cheight - 1;
|
||||
text_x = topleft_x + 1;
|
||||
text_y = topleft_y + 2;
|
||||
if(v == 0) {} /* don't render anything */
|
||||
@@ -340,10 +204,7 @@ void render_room_item(short i, short v) {
|
||||
colorxy(topleft_x, topleft_y+4, color);
|
||||
colorxy(topleft_x+4, topleft_y+4, color);
|
||||
/* draw the rectangle */
|
||||
chlinexy(topleft_x + 1, topleft_y, cwidth - 2);
|
||||
chlinexy(topleft_x + 1, bottomleft_y, cwidth - 2);
|
||||
cvlinexy(topleft_x, topleft_y + 1, cheight - 2);
|
||||
cvlinexy(topright_x, topleft_y + 1, cheight - 2);
|
||||
rect(topleft_x, topleft_y, cwidth, cheight);
|
||||
/* draw text */
|
||||
gotoxy(text_x, text_y);
|
||||
if(v < 14) {
|
||||
@@ -362,7 +223,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,
|
||||
@@ -378,13 +239,10 @@ void render_main_scene() {
|
||||
chlinexy(0,SCREEN_HEIGHT-3,SCREEN_WIDTH);
|
||||
/* render the room */
|
||||
for(i=0;i<4;i++) render_room_item(i, room[i]);
|
||||
rect(9, 9, 6, 5);
|
||||
cputsxy(10, 10, "DECK");
|
||||
gotoxy(11, 12);
|
||||
cprintf("%02d", deck_count);
|
||||
cvlinexy(9, 10, 3);
|
||||
cvlinexy(14, 10, 3);
|
||||
chlinexy(10, 9, 4);
|
||||
chlinexy(10, 13, 4);
|
||||
/* render stats */
|
||||
gotoxy(statbase, 1);
|
||||
cprintf("Health %02d", hp);
|
||||
@@ -404,8 +262,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 +272,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;
|
||||
}
|
||||
}
|
||||
@@ -430,27 +288,14 @@ void print_msg(char *s) {
|
||||
render_main_scene();
|
||||
}
|
||||
|
||||
/* Array shuffle method */
|
||||
void shuffle(char *array, unsigned int n) {
|
||||
unsigned int i, j, t;
|
||||
if(n > 1) {
|
||||
for(i=n-1;i>0;i--) {
|
||||
j = (unsigned int) (rand()%(i+1));
|
||||
t = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Room item handling methods */
|
||||
|
||||
void 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)) {
|
||||
@@ -466,16 +311,17 @@ void handle_monster(int val) {
|
||||
hp -= healthlost;
|
||||
if(hp < 0) hp = 0;
|
||||
snprintf(status_msg, sizeof(status_msg), "You lost %d HP. New HP: %d", healthlost, hp);
|
||||
return val;
|
||||
}
|
||||
|
||||
void handle_weapon(int val) {
|
||||
void handle_weapon(u8 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();
|
||||
@@ -491,12 +337,12 @@ void handle_potion(unsigned char val) {
|
||||
}
|
||||
|
||||
/* Main gameplay */
|
||||
int game() {
|
||||
unsigned int i;
|
||||
int score = 0; /* score storage */
|
||||
int item, room_sum; /* true item value */
|
||||
int last_room = 0, last_room_clr = 0; /* last room markers */
|
||||
char ch;
|
||||
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 */
|
||||
u8 ch;
|
||||
deck_idx = 0; /* reset the deck index */
|
||||
/* Prefill the deck */
|
||||
for(i=0;i<13;i++) deck[i] = deck[i+13] = i+1;
|
||||
@@ -556,7 +402,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++) {
|
||||
@@ -587,12 +433,12 @@ int game() {
|
||||
engaged++; /* increase the amount of engaged cards */
|
||||
can_run = 0; /* Cannot run once engaged */
|
||||
|
||||
item = room[(int)ch]; /* get the item from the room */
|
||||
room[(int)ch] = 0; /* mark the room slot as empty */
|
||||
item = room[ch]; /* get the item from the room */
|
||||
room[ch] = 0; /* mark the room slot as empty */
|
||||
/* Note: We do NOT put the item back in deck. It is consumed. */
|
||||
|
||||
if(item < 14) { /* monster */
|
||||
handle_monster(item + 1);
|
||||
score += handle_monster(item + 1);
|
||||
} else if(item < 23) { /* weapon */
|
||||
handle_weapon(item - 12);
|
||||
} else { /* potion */
|
||||
@@ -625,20 +471,13 @@ int game() {
|
||||
if(hp > 0) { /* win */
|
||||
score = hp;
|
||||
if(hp == 20) score += last_potion_val;
|
||||
} else { /* fail */
|
||||
for(i=0;i<sizeof(room);i++)
|
||||
if(room[i] && room[i] < 14)
|
||||
score -= (room[i] + 1);
|
||||
for(i=0;i<sizeof(deck);i++)
|
||||
if(deck[i] && deck[i] < 14)
|
||||
score -= (deck[i] + 1);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
/* Entry point */
|
||||
int main() {
|
||||
int score;
|
||||
s16 main() {
|
||||
s16 score;
|
||||
game_startup();
|
||||
while(1) {
|
||||
score = game();
|
||||
|
||||
Reference in New Issue
Block a user