started on JSNES support
This commit is contained in:
parent
0e807690e8
commit
aa1d303000
|
@ -215,9 +215,9 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
|
|||
|
||||
<script src="javatari.js/release/javatari/javatari.js"></script>
|
||||
<script src="src/cpu/z80fast.js"></script>
|
||||
<script src="jsnes/jsnes.min.js"></script>
|
||||
<!--<script src="src/cpu/6809.js"></script>-->
|
||||
<!--
|
||||
<script src="jsnes/build/jsnes.min.js"></script>
|
||||
<script src="jsnes/lib/dynamicaudio-min.js" type="text/javascript" charset="utf-8"></script>
|
||||
-->
|
||||
<!--
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,54 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <joystick.h>
|
||||
|
||||
const char Text [] = "Hello world!";
|
||||
|
||||
int main (void)
|
||||
{
|
||||
unsigned char width, height;
|
||||
|
||||
/* Set screen colors */
|
||||
(void) bgcolor (COLOR_BLUE);
|
||||
|
||||
/* Clear the screen, put cursor in upper left corner */
|
||||
clrscr ();
|
||||
|
||||
/* Ask for the screen size */
|
||||
screensize (&width, &height);
|
||||
|
||||
/* Draw a border around the screen */
|
||||
|
||||
/* Top line */
|
||||
cputc (CH_ULCORNER);
|
||||
chline (width - 2);
|
||||
cputc (CH_URCORNER);
|
||||
|
||||
/* Vertical line, left side */
|
||||
cvlinexy (0, 1, height - 2);
|
||||
|
||||
/* Bottom line */
|
||||
cputc (CH_LLCORNER);
|
||||
chline (width - 2);
|
||||
cputc (CH_LRCORNER);
|
||||
|
||||
/* Vertical line, right side */
|
||||
cvlinexy (width - 1, 1, height - 2);
|
||||
|
||||
/* Write the greeting in the mid of the screen */
|
||||
gotoxy ((width - strlen (Text)) / 2, height / 2);
|
||||
cprintf ("%s", Text);
|
||||
|
||||
/* Wait for the user to press a button */
|
||||
joy_install (joy_static_stddrv);
|
||||
while (!joy_read (JOY_1)) ;
|
||||
joy_uninstall ();
|
||||
|
||||
/* Clear the screen again */
|
||||
clrscr ();
|
||||
|
||||
/* Done */
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
;;;;; CONSTANTS
|
||||
|
||||
PPU_CTRL equ $2000
|
||||
PPU_MASK equ $2001
|
||||
PPU_STATUS equ $2002
|
||||
PPU_SCROLL equ $2005
|
||||
PPU_ADDR equ $2006
|
||||
PPU_DATA equ $2007
|
||||
DMC_FREQ equ $4010
|
||||
|
||||
;;;;; CARTRIDGE FILE HEADER
|
||||
|
||||
processor 6502
|
||||
seg Header
|
||||
org $7FF0
|
||||
|
||||
NES_MAPPER equ 0 ;mapper number
|
||||
NES_PRG_BANKS equ 2 ;number of 16K PRG banks, change to 2 for NROM256
|
||||
NES_CHR_BANKS equ 1 ;number of 8K CHR banks (0 = RAM)
|
||||
NES_MIRRORING equ 1 ;0 horizontal, 1 vertical, 8 four screen
|
||||
|
||||
.byte $4e,$45,$53,$1a ; header
|
||||
.byte NES_PRG_BANKS
|
||||
.byte NES_CHR_BANKS
|
||||
.byte NES_MIRRORING|(NES_MAPPER<<4)
|
||||
.byte NES_MAPPER&$f0
|
||||
.byte 0,0,0,0,0,0,0,0 ; reserved, set to zero
|
||||
|
||||
;;;;; CODE
|
||||
|
||||
seg Code
|
||||
org $8000
|
||||
start:
|
||||
_exit:
|
||||
sei ;disable IRQs
|
||||
cld ;decimal mode not supported
|
||||
ldx #$ff
|
||||
txs ;set up stack pointer
|
||||
inx ;increment X to 0
|
||||
stx PPU_MASK ;disable rendering
|
||||
stx DMC_FREQ ;disable DMC interrupts
|
||||
stx PPU_CTRL ;disable NMI interrupts
|
||||
jsr WaitSyncSafe ;wait for VSYNC
|
||||
; clear RAM -- not a subroutine because we clear the stack too
|
||||
lda #0
|
||||
tax
|
||||
.clearRAM
|
||||
sta $0,x
|
||||
sta $100,x
|
||||
; skip $200-$2FF, used for OAM display list
|
||||
sta $300,x
|
||||
sta $400,x
|
||||
sta $500,x
|
||||
sta $600,x
|
||||
sta $700,x
|
||||
inx
|
||||
bne .clearRAM
|
||||
; wait for PPU warmup
|
||||
jsr WaitSync
|
||||
; set palette background
|
||||
ldy #$0
|
||||
lda #$3f
|
||||
sta PPU_ADDR
|
||||
sty PPU_ADDR
|
||||
lda #$1c
|
||||
sta PPU_DATA
|
||||
; enable PPU rendering
|
||||
lda #0
|
||||
sta PPU_ADDR
|
||||
sta PPU_ADDR ;PPU addr = 0
|
||||
sta PPU_SCROLL
|
||||
sta PPU_SCROLL ;scroll = 0
|
||||
lda #$90
|
||||
sta PPU_CTRL ;enable NMI
|
||||
lda #$1e
|
||||
sta PPU_MASK ;enable rendering
|
||||
.endless
|
||||
jmp .endless ;endless loop
|
||||
|
||||
;;;;; SUBROUTINES
|
||||
|
||||
; wait for VSYNC to start
|
||||
WaitSyncSafe: subroutine
|
||||
bit PPU_STATUS
|
||||
WaitSync:
|
||||
bit PPU_STATUS
|
||||
bpl WaitSync
|
||||
rts
|
||||
|
||||
;;;;; INTERRUPT HANDLERS
|
||||
|
||||
nmi:
|
||||
irq:
|
||||
rti
|
||||
|
||||
;;;;; CPU VECTORS
|
||||
|
||||
org $fffa
|
||||
.word nmi ;$fffa vblank nmi
|
||||
.word start ;$fffc reset
|
||||
.word irq ;$fffe irq / brk
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
|
||||
;;;;; CONSTANTS
|
||||
|
||||
PPU_CTRL equ $2000
|
||||
PPU_MASK equ $2001
|
||||
PPU_STATUS equ $2002
|
||||
OAM_ADDR equ $2003
|
||||
OAM_DATA equ $2004
|
||||
PPU_SCROLL equ $2005
|
||||
PPU_ADDR equ $2006
|
||||
PPU_DATA equ $2007
|
||||
PPU_OAM_DMA equ $4014
|
||||
DMC_FREQ equ $4010
|
||||
|
||||
;;;;; ZERO-PAGE VARIABLES
|
||||
|
||||
seg.u ZPVars
|
||||
org $0
|
||||
|
||||
ScrollPos byte ; used during NMI
|
||||
|
||||
;;;;; CARTRIDGE FILE HEADER
|
||||
|
||||
processor 6502
|
||||
seg Header
|
||||
org $7FF0
|
||||
|
||||
NES_MAPPER equ 0 ;mapper number
|
||||
NES_PRG_BANKS equ 2 ;number of 16K PRG banks, change to 2 for NROM256
|
||||
NES_CHR_BANKS equ 1 ;number of 8K CHR banks (0 = RAM)
|
||||
NES_MIRRORING equ 1 ;0 horizontal, 1 vertical, 8 four screen
|
||||
|
||||
.byte $4e,$45,$53,$1a ; header
|
||||
.byte NES_PRG_BANKS
|
||||
.byte NES_CHR_BANKS
|
||||
.byte NES_MIRRORING|(NES_MAPPER<<4)
|
||||
.byte NES_MAPPER&$f0
|
||||
.byte 0,0,0,0,0,0,0,0 ; reserved, set to zero
|
||||
|
||||
;;;;; CODE
|
||||
|
||||
seg Code
|
||||
org $8000
|
||||
start:
|
||||
_exit:
|
||||
sei ;disable IRQs
|
||||
cld ;decimal mode not supported
|
||||
ldx #$ff
|
||||
txs ;set up stack pointer
|
||||
inx ;increment X to 0
|
||||
stx PPU_MASK ;disable rendering
|
||||
stx DMC_FREQ ;disable DMC interrupts
|
||||
stx PPU_CTRL ;disable NMI interrupts
|
||||
jsr WaitSyncSafe ;wait for VSYNC
|
||||
; clear RAM -- not a subroutine because we clear the stack too
|
||||
lda #0
|
||||
tax
|
||||
.clearRAM
|
||||
sta $0,x
|
||||
sta $100,x
|
||||
; skip $200-$2FF, used for OAM display list
|
||||
sta $300,x
|
||||
sta $400,x
|
||||
sta $500,x
|
||||
sta $600,x
|
||||
sta $700,x
|
||||
inx
|
||||
bne .clearRAM
|
||||
; end of clear RAM routine
|
||||
jsr SetPalette ;set colors
|
||||
jsr FillVRAM ;set PPU RAM
|
||||
jsr WaitSync ;wait for VSYNC (and PPU warmup)
|
||||
lda #0
|
||||
sta PPU_ADDR
|
||||
sta PPU_ADDR ;PPU addr = 0
|
||||
sta PPU_SCROLL
|
||||
sta PPU_SCROLL ;scroll = 0
|
||||
lda #$90
|
||||
sta PPU_CTRL ;enable NMI
|
||||
lda #$1e
|
||||
sta PPU_MASK ;enable rendering
|
||||
.endless
|
||||
jmp .endless ;endless loop
|
||||
|
||||
;;;;; SUBROUTINES
|
||||
|
||||
; set palette colors
|
||||
SetPalette: subroutine
|
||||
ldy #$0
|
||||
lda #$3f
|
||||
sta PPU_ADDR
|
||||
sty PPU_ADDR
|
||||
ldx #4
|
||||
.loop:
|
||||
lda Palette,y
|
||||
sta PPU_DATA
|
||||
iny
|
||||
dex
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
; fill video RAM
|
||||
FillVRAM: subroutine
|
||||
txa
|
||||
ldy #$20
|
||||
sty PPU_ADDR
|
||||
sta PPU_ADDR
|
||||
ldy #$10
|
||||
.loop:
|
||||
sta PPU_DATA
|
||||
adc #1
|
||||
inx
|
||||
bne .loop
|
||||
dey
|
||||
bne .loop
|
||||
rts
|
||||
|
||||
; wait for VSYNC to start
|
||||
WaitSyncSafe: subroutine
|
||||
bit PPU_STATUS
|
||||
WaitSync:
|
||||
bit PPU_STATUS
|
||||
bpl WaitSync
|
||||
rts
|
||||
|
||||
;;;;; INTERRUPT HANDLERS
|
||||
|
||||
nmi:
|
||||
irq:
|
||||
; save registers
|
||||
pha ; save A
|
||||
; update scroll position
|
||||
inc ScrollPos
|
||||
lda ScrollPos
|
||||
sta PPU_SCROLL
|
||||
sta PPU_SCROLL
|
||||
; reload registers
|
||||
pla ; reload A
|
||||
rti
|
||||
|
||||
;;;;; CONSTANT DATA
|
||||
|
||||
Palette:
|
||||
hex 1f001020 ; black, gray, lt gray, white
|
||||
TextString:
|
||||
byte "HELLO WORLD!"
|
||||
byte 0
|
||||
|
||||
;;;;; CPU VECTORS
|
||||
|
||||
org $fffa
|
||||
.word nmi ;$fffa vblank nmi
|
||||
.word start ;$fffc reset
|
||||
.word irq ;$fffe irq / brk
|
||||
|
||||
;;;;; TILE SETS
|
||||
|
||||
org $10000
|
||||
REPEAT 64
|
||||
hex 003c6666766e663c007e181818381818
|
||||
hex 007e60300c06663c003c66061c06663c
|
||||
hex 0006067f661e0e06003c6606067c607e
|
||||
hex 003c66667c60663c00181818180c667e
|
||||
hex 003c66663c66663c003c66063e66663c
|
||||
hex 01010101010101010000000000000000
|
||||
hex ff000000000000000000000000000000
|
||||
hex 01020408102040800000000000000000
|
||||
REPEND
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
#include <nes.h>
|
||||
|
||||
const unsigned char TEXT[]="Hello PPU!!!!";
|
||||
|
||||
const unsigned char PALETTE[]={0x1, 0x00, 0x10, 0x20}; // blue, gray, lt gray, white
|
||||
|
||||
void main (void) {
|
||||
unsigned char index; // used in 'for' loops
|
||||
|
||||
// if we've just powered on,
|
||||
// wait for PPU to warm-up
|
||||
waitvsync();
|
||||
waitvsync();
|
||||
|
||||
// turn off screen
|
||||
PPU.control = 0x0; // NMI off
|
||||
PPU.mask = 0x0; // screen off
|
||||
|
||||
// load the palette
|
||||
// set PPU address to 0x3f00
|
||||
PPU.vram.address = 0x3f;
|
||||
PPU.vram.address = 0x00;
|
||||
for (index = 0; index < sizeof(PALETTE); index++) {
|
||||
PPU.vram.data = PALETTE[index];
|
||||
}
|
||||
|
||||
// load the text into VRAM
|
||||
// set PPU address to 0x21c9
|
||||
PPU.vram.address = 0x21;
|
||||
PPU.vram.address = 0xc9;
|
||||
for (index = 0; index < sizeof(TEXT); index++) {
|
||||
PPU.vram.data = TEXT[index];
|
||||
}
|
||||
|
||||
// reset the scroll position to 0
|
||||
PPU.scroll = 0;
|
||||
PPU.scroll = 0;
|
||||
// reset the PPU address to 0x2000 (frame start)
|
||||
PPU.vram.address = 0x20;
|
||||
PPU.vram.address = 0x00;
|
||||
|
||||
// turn on the screen
|
||||
PPU.mask = 0x1e;
|
||||
|
||||
// infinite loop
|
||||
while (1);
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <nes.h>
|
||||
#include <joystick.h>
|
||||
|
||||
#define COLS 32
|
||||
#define ROWS 28
|
||||
|
||||
typedef unsigned char byte;
|
||||
typedef signed char sbyte;
|
||||
typedef unsigned short word;
|
||||
|
||||
// read a character from VRAM.
|
||||
// this is tricky because we have to wait
|
||||
// for VSYNC to start, then set the VRAM
|
||||
// address to read, then set the VRAM address
|
||||
// back to the start of the frame.
|
||||
byte getchar(byte x, byte y) {
|
||||
// compute VRAM read address
|
||||
word addr = 0x2020+x+y*32;
|
||||
byte rd;
|
||||
// wait for VBLANK to start
|
||||
waitvsync();
|
||||
// set VRAM read address in PPU
|
||||
PPU.vram.address = addr>>8;
|
||||
PPU.vram.address = addr&0xff;
|
||||
// read the char from PPUDATA
|
||||
rd = PPU.vram.data; // discard
|
||||
rd = PPU.vram.data; // keep this one
|
||||
// reset the VRAM address to start of frame
|
||||
PPU.vram.address = 0x00;
|
||||
PPU.vram.address = 0x00;
|
||||
// return result
|
||||
return rd;
|
||||
}
|
||||
|
||||
void delay(byte count) {
|
||||
while (count--) waitvsync();
|
||||
}
|
||||
|
||||
////////// GAME DATA
|
||||
|
||||
typedef struct {
|
||||
byte x;
|
||||
byte y;
|
||||
byte dir;
|
||||
word score;
|
||||
char head_attr;
|
||||
char tail_attr;
|
||||
int collided:1;
|
||||
int human:1;
|
||||
} Player;
|
||||
|
||||
Player players[2];
|
||||
|
||||
byte credits = 0;
|
||||
byte frames_per_move;
|
||||
|
||||
#define START_SPEED 12
|
||||
#define MAX_SPEED 5
|
||||
#define MAX_SCORE 7
|
||||
|
||||
///////////
|
||||
|
||||
const char BOX_CHARS[8] = { 17, 8, 20, 18, 11, 11, 14, 14 };
|
||||
|
||||
void draw_box(byte x, byte y, byte x2, byte y2, const char* chars) {
|
||||
byte x1 = x;
|
||||
cputcxy(x, y, chars[2]);
|
||||
cputcxy(x2, y, chars[3]);
|
||||
cputcxy(x, y2, chars[0]);
|
||||
cputcxy(x2, y2, chars[1]);
|
||||
while (++x < x2) {
|
||||
cputcxy(x, y, chars[5]);
|
||||
cputcxy(x, y2, chars[4]);
|
||||
}
|
||||
while (++y < y2) {
|
||||
cputcxy(x1, y, chars[6]);
|
||||
cputcxy(x2, y, chars[7]);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_playfield() {
|
||||
draw_box(0,1,COLS-1,ROWS-1,BOX_CHARS);
|
||||
cputsxy(0,0,"Plyr1:");
|
||||
cputsxy(20,0,"Plyr2:");
|
||||
cputcxy(7,0,players[0].score+'0');
|
||||
cputcxy(27,0,players[1].score+'0');
|
||||
}
|
||||
|
||||
typedef enum { D_RIGHT, D_DOWN, D_LEFT, D_UP } dir_t;
|
||||
const char DIR_X[4] = { 1, 0, -1, 0 };
|
||||
const char DIR_Y[4] = { 0, 1, 0, -1 };
|
||||
|
||||
void init_game() {
|
||||
memset(players, 0, sizeof(players));
|
||||
players[0].head_attr = '1';
|
||||
players[1].head_attr = '2';
|
||||
players[0].tail_attr = 1;
|
||||
players[1].tail_attr = 9;
|
||||
frames_per_move = START_SPEED;
|
||||
}
|
||||
|
||||
void reset_players() {
|
||||
players[0].x = players[0].y = 5;
|
||||
players[0].dir = D_RIGHT;
|
||||
players[1].x = COLS-6;
|
||||
players[1].y = ROWS-6;
|
||||
players[1].dir = D_LEFT;
|
||||
players[0].collided = players[1].collided = 0;
|
||||
}
|
||||
|
||||
void draw_player(Player* p) {
|
||||
cputcxy(p->x, p->y, p->head_attr);
|
||||
}
|
||||
|
||||
void move_player(Player* p) {
|
||||
cputcxy(p->x, p->y, p->tail_attr);
|
||||
p->x += DIR_X[p->dir];
|
||||
p->y += DIR_Y[p->dir];
|
||||
if (getchar(p->x, p->y) != ' ')
|
||||
p->collided = 1;
|
||||
draw_player(p);
|
||||
}
|
||||
|
||||
void human_control(Player* p) {
|
||||
byte dir = 0xff;
|
||||
byte joy;
|
||||
if (!p->human) return;
|
||||
joy = joy_read (JOY_1);
|
||||
if (joy & JOY_LEFT_MASK) dir = D_LEFT;
|
||||
if (joy & JOY_RIGHT_MASK) dir = D_RIGHT;
|
||||
if (joy & JOY_UP_MASK) dir = D_UP;
|
||||
if (joy & JOY_DOWN_MASK) dir = D_DOWN;
|
||||
// don't let the player reverse
|
||||
if (dir < 0x80 && dir != (p->dir ^ 2)) {
|
||||
p->dir = dir;
|
||||
}
|
||||
}
|
||||
|
||||
byte ai_try_dir(Player* p, dir_t dir, byte shift) {
|
||||
byte x,y;
|
||||
dir &= 3;
|
||||
x = p->x + (DIR_X[dir] << shift);
|
||||
y = p->y + (DIR_Y[dir] << shift);
|
||||
if (x < COLS && y < ROWS && getchar(x, y) == ' ') {
|
||||
p->dir = dir;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ai_control(Player* p) {
|
||||
dir_t dir;
|
||||
if (p->human) return;
|
||||
dir = p->dir;
|
||||
if (!ai_try_dir(p, dir, 0)) {
|
||||
ai_try_dir(p, dir+1, 0);
|
||||
ai_try_dir(p, dir-1, 0);
|
||||
} else {
|
||||
ai_try_dir(p, dir-1, 0) && ai_try_dir(p, dir-1, 1+(rand() & 3));
|
||||
ai_try_dir(p, dir+1, 0) && ai_try_dir(p, dir+1, 1+(rand() & 3));
|
||||
ai_try_dir(p, dir, rand() & 3);
|
||||
}
|
||||
}
|
||||
|
||||
byte gameover;
|
||||
|
||||
void flash_colliders() {
|
||||
byte i;
|
||||
// flash players that collided
|
||||
for (i=0; i<56; i++) {
|
||||
//cv_set_frequency(CV_SOUNDCHANNEL_0, 1000+i*8);
|
||||
//cv_set_attenuation(CV_SOUNDCHANNEL_0, i/2);
|
||||
if (players[0].collided) players[0].head_attr ^= 0x80;
|
||||
if (players[1].collided) players[1].head_attr ^= 0x80;
|
||||
delay(2);
|
||||
draw_player(&players[0]);
|
||||
draw_player(&players[1]);
|
||||
}
|
||||
//cv_set_attenuation(CV_SOUNDCHANNEL_0, 28);
|
||||
}
|
||||
|
||||
void make_move() {
|
||||
byte i;
|
||||
for (i=0; i<frames_per_move; i++) {
|
||||
human_control(&players[0]);
|
||||
delay(1);
|
||||
}
|
||||
ai_control(&players[0]);
|
||||
ai_control(&players[1]);
|
||||
// if players collide, 2nd player gets the point
|
||||
move_player(&players[1]);
|
||||
move_player(&players[0]);
|
||||
}
|
||||
|
||||
void declare_winner(byte winner) {
|
||||
byte i;
|
||||
clrscr();
|
||||
for (i=0; i<ROWS/2-3; i++) {
|
||||
draw_box(i,i,COLS-1-i,ROWS-1-i,BOX_CHARS);
|
||||
delay(1);
|
||||
}
|
||||
cputsxy(12,10,"WINNER:");
|
||||
cputsxy(12,13,"PLAYER ");
|
||||
cputcxy(12+7, 13, '1'+winner);
|
||||
delay(75);
|
||||
gameover = 1;
|
||||
}
|
||||
|
||||
#define AE(a,b,c,d) (((a)<<0)|((b)<<2)|((c)<<4)|((d)<<6))
|
||||
|
||||
// this is attribute table data,
|
||||
// each 2 bits defines a color palette
|
||||
// for a 16x16 box
|
||||
const unsigned char Attrib_Table[0x40]={
|
||||
AE(3,3,1,1),AE(3,3,1,1),AE(3,3,1,1),AE(3,3,1,1), AE(2,2,1,1),AE(2,2,1,1),AE(2,2,1,1),AE(2,2,1,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,0,1,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0), AE(0,0,0,0),AE(0,0,0,0),AE(0,0,0,0),AE(0,1,0,1),
|
||||
AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1), AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1),AE(1,1,1,1),
|
||||
};
|
||||
|
||||
// put 8x8 grid of palette entries into the PPU
|
||||
void setup_attrib_table() {
|
||||
byte index;
|
||||
waitvsync(); // wait for VBLANK
|
||||
PPU.vram.address = 0x23;
|
||||
PPU.vram.address = 0xc0;
|
||||
for( index = 0; index < 0x40; ++index ){
|
||||
PPU.vram.data = Attrib_Table[index];
|
||||
}
|
||||
}
|
||||
|
||||
void play_round() {
|
||||
reset_players();
|
||||
clrscr();
|
||||
setup_attrib_table();
|
||||
draw_playfield();
|
||||
while (1) {
|
||||
make_move();
|
||||
if (players[0].collided || players[1].collided) break;
|
||||
}
|
||||
flash_colliders();
|
||||
// add scores to players that didn't collide
|
||||
if (players[0].collided) players[1].score++;
|
||||
if (players[1].collided) players[0].score++;
|
||||
// increase speed
|
||||
if (frames_per_move > MAX_SPEED) frames_per_move--;
|
||||
// game over?
|
||||
if (players[0].score != players[1].score) {
|
||||
if (players[0].score >= MAX_SCORE)
|
||||
declare_winner(0);
|
||||
else if (players[1].score >= MAX_SCORE)
|
||||
declare_winner(1);
|
||||
}
|
||||
}
|
||||
|
||||
void play_game() {
|
||||
gameover = 0;
|
||||
init_game();
|
||||
players[0].human = 1;
|
||||
while (!gameover) {
|
||||
play_round();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
joy_install (joy_static_stddrv);
|
||||
play_game();
|
||||
}
|
|
@ -109,32 +109,14 @@ abstract class BaseDebugPlatform {
|
|||
}
|
||||
}
|
||||
|
||||
////// 6502
|
||||
abstract class BaseFrameBasedPlatform extends BaseDebugPlatform {
|
||||
debugPCDelta = -1;
|
||||
|
||||
function getToolForFilename_6502(fn:string) : string {
|
||||
if (fn.endsWith(".pla")) return "plasm";
|
||||
if (fn.endsWith(".c")) return "cc65";
|
||||
if (fn.endsWith(".s")) return "ca65";
|
||||
if (fn.endsWith(".acme")) return "acme";
|
||||
return "dasm"; // .a
|
||||
}
|
||||
|
||||
export abstract class Base6502Platform extends BaseDebugPlatform {
|
||||
|
||||
newCPU(membus : MemoryBus) {
|
||||
var cpu = new jt.M6502();
|
||||
cpu.connectBus(membus);
|
||||
return cpu;
|
||||
evalDebugCondition() {
|
||||
if (this.debugCondition && !this.debugBreakState) {
|
||||
this.debugCondition();
|
||||
}
|
||||
}
|
||||
|
||||
getOpcodeMetadata(opcode, offset) {
|
||||
return Javatari.getOpcodeMetadata(opcode, offset); // TODO
|
||||
}
|
||||
|
||||
getOriginPC() : number {
|
||||
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
||||
}
|
||||
|
||||
restartDebugState() {
|
||||
if (this.debugCondition && !this.debugBreakState) {
|
||||
this.debugSavedState = this.saveState();
|
||||
|
@ -145,24 +127,38 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
breakpointHit(targetClock : number) {
|
||||
this.debugTargetClock = targetClock;
|
||||
this.debugBreakState = this.saveState();
|
||||
this.debugBreakState.c.PC = (this.debugBreakState.c.PC-1) & 0xffff;
|
||||
this.debugBreakState.c.PC = (this.debugBreakState.c.PC + this.debugPCDelta) & 0xffff;
|
||||
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
|
||||
this.pause();
|
||||
if (this.onBreakpointHit) {
|
||||
this.onBreakpointHit(this.debugBreakState);
|
||||
}
|
||||
}
|
||||
// TODO: lower bound of clock value
|
||||
runEval(evalfunc : DebugEvalCondition) {
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ >= this.debugTargetClock) {
|
||||
var cpuState = this.getCPUState();
|
||||
cpuState.PC = (cpuState.PC + this.debugPCDelta) & 0xffff;
|
||||
if (evalfunc(cpuState)) {
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
step() {
|
||||
var previousPC = -1;
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ > this.debugTargetClock) {
|
||||
//console.log(this.debugClock, this.debugTargetClock, this.getCPUState().PC, this.getCPUState());
|
||||
if (this.debugClock++ >= this.debugTargetClock) {
|
||||
var thisState = this.getCPUState();
|
||||
if (previousPC < 0) {
|
||||
previousPC = thisState.PC;
|
||||
} else {
|
||||
// doesn't work w/ endless loops
|
||||
if (thisState.PC != previousPC && thisState.T == 0) {
|
||||
//console.log(previousPC.toString(16), thisPC.toString(16));
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
}
|
||||
|
@ -188,20 +184,34 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
runEval(evalfunc : DebugEvalCondition) {
|
||||
this.setDebugCondition( () => {
|
||||
if (this.debugClock++ > this.debugTargetClock) {
|
||||
var cpuState = this.getCPUState();
|
||||
cpuState.PC = (cpuState.PC-1)&0xffff;
|
||||
if (evalfunc(cpuState)) {
|
||||
this.breakpointHit(this.debugClock-1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
////// 6502
|
||||
|
||||
function getToolForFilename_6502(fn:string) : string {
|
||||
if (fn.endsWith(".pla")) return "plasm";
|
||||
if (fn.endsWith(".c")) return "cc65";
|
||||
if (fn.endsWith(".s")) return "ca65";
|
||||
if (fn.endsWith(".acme")) return "acme";
|
||||
return "dasm"; // .a
|
||||
}
|
||||
|
||||
export abstract class Base6502Platform extends BaseFrameBasedPlatform {
|
||||
|
||||
newCPU(membus : MemoryBus) {
|
||||
var cpu = new jt.M6502();
|
||||
cpu.connectBus(membus);
|
||||
return cpu;
|
||||
}
|
||||
|
||||
getOpcodeMetadata(opcode, offset) {
|
||||
return Javatari.getOpcodeMetadata(opcode, offset); // TODO
|
||||
}
|
||||
|
||||
getOriginPC() : number {
|
||||
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
||||
}
|
||||
|
||||
runUntilReturn() {
|
||||
var depth = 1;
|
||||
this.runEval( (c:CpuState) => {
|
||||
|
@ -214,6 +224,7 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
|
@ -336,7 +347,6 @@ export abstract class BaseZ80Platform extends BaseDebugPlatform {
|
|||
breakpointHit(targetClock : number) {
|
||||
this.debugTargetClock = targetClock;
|
||||
this.debugBreakState = this.saveState();
|
||||
//this.debugBreakState.c.PC = (this.debugBreakState.c.PC-1) & 0xffff;
|
||||
console.log("Breakpoint at clk", this.debugBreakState.c.T, "PC", this.debugBreakState.c.PC.toString(16));
|
||||
this.pause();
|
||||
if (this.onBreakpointHit) {
|
||||
|
|
|
@ -278,6 +278,7 @@ var Apple2Platform = function(mainElement) {
|
|||
for (var i=0; i<2000000; i++) {
|
||||
cpu.clockPulse();
|
||||
if (this.getCPUState().PC == 0xc602) {
|
||||
cpu.clockPulse();
|
||||
cpu.clockPulse();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -20,33 +20,152 @@ new jt.M6502(); // to get Javatari.getOpcodeMetadata
|
|||
|
||||
/// JSNES
|
||||
|
||||
var JSNES_KEYCODE_MAP = makeKeycodeMap([
|
||||
[Keys.VK_Z, 0, 0],
|
||||
[Keys.VK_X, 0, 1],
|
||||
[Keys.VK_2, 0, 2],
|
||||
[Keys.VK_1, 0, 3],
|
||||
[Keys.VK_UP, 0, 4],
|
||||
[Keys.VK_DOWN, 0, 5],
|
||||
[Keys.VK_LEFT, 0, 6],
|
||||
[Keys.VK_RIGHT, 0, 7],
|
||||
[Keys.VK_Q, 1, 0],
|
||||
[Keys.VK_E, 1, 1],
|
||||
[Keys.VK_4, 1, 2],
|
||||
[Keys.VK_3, 1, 3],
|
||||
[Keys.VK_W, 1, 4],
|
||||
[Keys.VK_S, 1, 5],
|
||||
[Keys.VK_A, 1, 6],
|
||||
[Keys.VK_D, 1, 7],
|
||||
]);
|
||||
|
||||
var JSNESPlatform = function(mainElement) {
|
||||
var self = this;
|
||||
this.__proto__ = new Base6502Platform();
|
||||
this.debugPCDelta = 1;
|
||||
|
||||
var nes;
|
||||
var rom;
|
||||
var video, audio, timer;
|
||||
var audioFrequency = 44100;
|
||||
|
||||
this.getPresets = function() { return NES_PRESETS; }
|
||||
this.getPresets = function() { return NES_CONIO_PRESETS; }
|
||||
|
||||
this.start = function() {
|
||||
nes = new JSNES({
|
||||
'ui': $('#emulator').JSNESUI({})
|
||||
video = new RasterVideo(mainElement,256,224);
|
||||
audio = new SampleAudio(audioFrequency);
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
nes = new jsnes.NES({
|
||||
onFrame: function(frameBuffer) {
|
||||
for (var i=0; i<frameBuffer.length; i++)
|
||||
idata[i] = frameBuffer[i] | 0xff000000;
|
||||
video.updateFrame();
|
||||
self.restartDebugState();
|
||||
},
|
||||
onAudioSample: function(left, right) {
|
||||
audio.feedSample(left+right, 1);
|
||||
},
|
||||
onStatusUpdate: function(s) {
|
||||
console.log(s);
|
||||
},
|
||||
//onBatteryRamWrite
|
||||
});
|
||||
nes.stop = function() {
|
||||
self.pause();// TODO
|
||||
console.log(nes.cpu.toJSON()); // TODO
|
||||
throw ("CPU STOPPED");
|
||||
};
|
||||
// insert debug hook
|
||||
nes.cpu._emulate = nes.cpu.emulate;
|
||||
nes.cpu.emulate = function() {
|
||||
var cycles = nes.cpu._emulate();
|
||||
//if (self.debugCondition && !self.debugBreakState && self.debugClock < 100) console.log(self.debugClock, nes.cpu.REG_PC);
|
||||
self.evalDebugCondition();
|
||||
return cycles;
|
||||
}
|
||||
timer = new AnimationTimer(60, function() {
|
||||
nes.frame();
|
||||
});
|
||||
// set keyboard map
|
||||
setKeyboardFromMap(video, [], JSNES_KEYCODE_MAP, function(o,key,code,flags) {
|
||||
if (flags & 1)
|
||||
nes.buttonDown(o.index+1, o.mask); // controller, button
|
||||
else
|
||||
nes.buttonUp(o.index+1, o.mask); // controller, button
|
||||
});
|
||||
}
|
||||
|
||||
this.loadROM = function(title, data) {
|
||||
nes.loadRom(data);
|
||||
var romstr = String.fromCharCode.apply(null, data);
|
||||
nes.loadROM(romstr);
|
||||
}
|
||||
|
||||
this.getOpcodeMetadata = Javatari.getOpcodeMetadata;
|
||||
this.getToolForFilename = getToolForFilename_6502;
|
||||
this.getDefaultExtension = function() { return ".c"; };
|
||||
|
||||
this.isRunning = function() { return nes.isRunning; }
|
||||
this.pause = function() { if (rom) nes.stop(); }
|
||||
this.resume = function() { if (rom) nes.start(); }
|
||||
|
||||
this.clearDebug = function() { // TODO
|
||||
|
||||
this.reset = function() {
|
||||
//nes.cpu.reset(); // doesn't work right, crashes
|
||||
nes.cpu.requestIrq(nes.cpu.IRQ_RESET);
|
||||
}
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
audio.stop();
|
||||
}
|
||||
this.resume = function() {
|
||||
timer.start();
|
||||
audio.start();
|
||||
}
|
||||
|
||||
this.getCPUState = function() {
|
||||
var c = nes.cpu.toJSON();
|
||||
this.copy6502REGvars(c);
|
||||
return c;
|
||||
}
|
||||
this.saveState = function() {
|
||||
//var s = $.extend(true, {}, nes);
|
||||
var s = nes.toJSON();
|
||||
s.c = s.cpu;
|
||||
this.copy6502REGvars(s.c);
|
||||
s.cpu.mem = s.cpu.mem.slice(0);
|
||||
s.ppu.vramMem = s.ppu.vramMem.slice(0);
|
||||
s.ppu.spriteMem = s.ppu.spriteMem.slice(0);
|
||||
return s;
|
||||
}
|
||||
this.loadState = function(state) {
|
||||
nes.fromJSON(state);
|
||||
//nes.cpu.fromJSON(state.cpu);
|
||||
//nes.mmap.fromJSON(state.mmap);
|
||||
//nes.ppu.fromJSON(state.ppu);
|
||||
nes.cpu.mem = state.cpu.mem.slice(0);
|
||||
nes.ppu.vramMem = state.ppu.vramMem.slice(0);
|
||||
nes.ppu.spriteMem = state.ppu.spriteMem.slice(0);
|
||||
//$.extend(nes, state);
|
||||
}
|
||||
this.readAddress = function(addr) {
|
||||
return nes.cpu.mem[addr] & 0xff;
|
||||
}
|
||||
this.copy6502REGvars = function(c) {
|
||||
c.T = 0;
|
||||
c.PC = c.REG_PC;
|
||||
c.A = c.REG_ACC;
|
||||
c.X = c.REG_X;
|
||||
c.Y = c.REG_Y;
|
||||
c.SP = c.REG_SP;
|
||||
c.Z = c.F_ZERO;
|
||||
c.N = c.F_SIGN;
|
||||
c.V = c.F_OVERFLOW;
|
||||
c.D = c.F_DECIMAL;
|
||||
c.C = c.F_CARRY;
|
||||
c.R = 1;
|
||||
c.o = this.readAddress(c.PC+1);
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// MAME support
|
||||
|
@ -102,7 +221,7 @@ var NESLibPlatform = function(mainElement) {
|
|||
|
||||
///
|
||||
|
||||
//PLATFORMS['nes'] = JSNESPlatform;
|
||||
PLATFORMS['nes'] = JSNESPlatform;
|
||||
PLATFORMS['nes-lib'] = NESLibPlatform;
|
||||
PLATFORMS['nes-conio'] = NESConIOPlatform;
|
||||
|
||||
|
|
|
@ -102,6 +102,11 @@ var PLATFORM_PARAMS = {
|
|||
//'main.rel'
|
||||
],
|
||||
},
|
||||
'nes': { //TODO
|
||||
define: '__NES__',
|
||||
cfgfile: 'neslib.cfg',
|
||||
libargs: ['neslib.lib', 'nes.lib'],
|
||||
},
|
||||
'nes-conio': {
|
||||
cfgfile: 'nes.cfg',
|
||||
define: '__NES__',
|
||||
|
|
Loading…
Reference in New Issue