mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
Merge remote-tracking branch 'origin/master' into fredsa-conflict-fix
# Conflicts: # src/ide/views/asseteditor.ts
This commit is contained in:
commit
be56f7ea68
51 files changed
+2943
-98
No files matched your search
@@ -57,9 +57,17 @@ distro: buildtsc
|
||||
rm -r $(TMP)/doc $(TMP)/scripts $(TMP)/test* $(TMP)/tools $(TMP)/.[a-z]* $(TMP)/ts*.json # $(TMP)/meta
|
||||
rm -f $(TMP)/javatari && mkdir -p $(TMP)/javatari && cp -p javatari.js/release/javatari/* $(TMP)/javatari/
|
||||
|
||||
tsweb: submodules node_modules
|
||||
getip:
|
||||
@if command -v ip > /dev/null; then \
|
||||
ip addr | grep -w inet; \
|
||||
elif command -v ifconfig > /dev/null; then \
|
||||
ifconfig | grep -w inet; \
|
||||
else \
|
||||
ipconfig | grep IPv4; \
|
||||
fi
|
||||
|
||||
tsweb: submodules node_modules getip
|
||||
npm run esbuild-clean
|
||||
(ip addr || ifconfig) | grep inet
|
||||
trap 'kill 0' EXIT; \
|
||||
make buildgrammars; \
|
||||
$(TSC) -w --preserveWatchOutput & \
|
||||
|
||||
@@ -20,6 +20,8 @@ git clone -b master --single-branch git@github.com:sehugg/8bitworkshop.git
|
||||
To build the 8bitworkshop IDE:
|
||||
|
||||
```sh
|
||||
# On Windows, use the `UCRT64` shell from https://msys2.org/
|
||||
# On Linux/macOS, just type:
|
||||
make
|
||||
```
|
||||
|
||||
@@ -51,7 +53,7 @@ Copyright © 2016-2026 [Steven E. Hugg](https://github.com/sehugg).
|
||||
This project, unless specifically noted, is multi-licensed.
|
||||
You may choose to adhere to the terms of either the [GPL-3.0](https://github.com/sehugg/8bitworkshop/blob/master/LICENSE) License for the entire project or respect the individual licenses of its dependencies and included code samples, as applicable.
|
||||
|
||||
This project includes various dependencies, modules, and components that retain their original licenses.
|
||||
This project includes various dependencies, modules, and components that retain their original licenses.
|
||||
For detailed licensing information for each dependency, please refer to the respective files and documentation.
|
||||
|
||||
All included code samples located in the `presets/` directory are licensed under
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ body {
|
||||
<li class="dropdown dropdown-submenu">
|
||||
<a tabindex="-1" href="javascript:void(0)">Sync</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_login">Sign in to Github...</a></li>
|
||||
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_login">Sign in to GitHub...</a></li>
|
||||
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_logout">Log out</a></li>
|
||||
<hr>
|
||||
<li><a class="dropdown-item" href="javascript:void(0)" id="item_github_import">Import Project from GitHub...</a></li>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
* = $7ffe
|
||||
; 2-byte load address for ROM image
|
||||
.word $8000
|
||||
; http://swut.net/c64cart-howto.html
|
||||
; https://codebase64.org/doku.php?id=base:assembling_your_own_cart_rom_image
|
||||
.word CartKReset ; cold start vector
|
||||
.word CartWStart ; warm start vector
|
||||
.byt $c3, $c2, $cd, $38, $30 ; "CBM80"
|
||||
CartKReset
|
||||
STX $D016 ; Turn on VIC for PAL / NTSC check
|
||||
JSR $FDA3 ; IOINIT - Init CIA chips
|
||||
JSR $FD50 ; RANTAM - Clear/test system RAM
|
||||
JSR $FD15 ; RESTOR - Init KERNAL RAM vectors
|
||||
JSR $FF5B ; CINT - Init VIC and screen editor
|
||||
CLI ; Re-enable IRQ interrupts
|
||||
CartBReset
|
||||
; init BASIC?
|
||||
#ifdef CART_INIT_BASIC
|
||||
JSR $E453 ; Init BASIC RAM vectors
|
||||
JSR $E3BF ; Main BASIC RAM Init routine
|
||||
JSR $E422 ; Power-up message / NEW command
|
||||
LDX #$FB
|
||||
TXS ; Reduce stack pointer for BASIC
|
||||
; don't init BASIC, just NOP
|
||||
#else
|
||||
.dsb 12, $ea ; nop
|
||||
#endif
|
||||
CartWStart
|
||||
; should be * = $x025
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
#include "cartheader.xa"
|
||||
|
||||
Temp = $02
|
||||
|
||||
Start
|
||||
sei ; turn off interrupts
|
||||
ldy #0
|
||||
Loop
|
||||
lda Message,y ; load message byte
|
||||
beq EOM ; 0 = end of string
|
||||
clc
|
||||
adc #$c0 ; convert PETSCII to screen code
|
||||
sta $400+41,y ; store to screen
|
||||
iny
|
||||
bne Loop ; next character
|
||||
EOM
|
||||
|
||||
Wait1
|
||||
lda $d011
|
||||
bmi Wait1 ; wait for line < 256
|
||||
Wait2
|
||||
lda $d012 ; get current scanline
|
||||
Wait3
|
||||
cmp $d012
|
||||
beq Wait3 ; wait for scanline to change
|
||||
lsr ; divide by 2
|
||||
lsr ; divide by 2
|
||||
clc
|
||||
adc Temp ; add to frame counter
|
||||
sta $d020 ; set border color
|
||||
lda $d011 ; get status bits
|
||||
bpl Wait2 ; repeat until line >= 256
|
||||
sty $d020 ; reset border color
|
||||
dec Temp ; change frame counter
|
||||
jmp Wait1 ; endless loop
|
||||
|
||||
Message
|
||||
; PETSCII - http://sta.c64.org/cbm64pet.html
|
||||
.asc "HELLO WORLD"
|
||||
.byt 0
|
||||
+1430
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -1,7 +1,7 @@
|
||||
#ifndef __BCD_H_INCLUDE
|
||||
#define __BCD_H_INCLUDE
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/** @file bcd.h
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
#ifndef _CGB_H
|
||||
#define _CGB_H
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/** Macro to create a CGB palette color entry out of 5-bit color components.
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#ifndef __DRAWING_H
|
||||
#define __DRAWING_H
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/** Size of the screen in pixels */
|
||||
@@ -56,7 +56,7 @@
|
||||
#define SIGNED 1
|
||||
#define UNSIGNED 0
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
|
||||
/** Print the string 'str' with no interpretation
|
||||
@see gotogxy()
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#ifndef __EMU_DEBUG_INCLUDE
|
||||
#define __EMU_DEBUG_INCLUDE
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
|
||||
/** Macro to display a message in the emulator debug message window
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef __GBDECOMPRESS_H_INCLUDE
|
||||
#define __GBDECOMPRESS_H_INCLUDE
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/** gb-decompress data from sour into dest
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
#define _ISR_H_INCLUDE_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
|
||||
// #define VECTOR_VBL 0x40 // you can not define raw vector for VBlank interrupt
|
||||
#define VECTOR_STAT 0x48 /**< Address for the STAT interrupt vector */
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
#define _METASPRITES_H_INCLUDE
|
||||
|
||||
#include <gb/hardware.h>
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/** Metasprite sub-item structure
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#ifndef _SGB_H
|
||||
#define _SGB_H
|
||||
|
||||
#include <types.h>
|
||||
#include "types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define SGB_PAL_01 0x00U /**< SGB Command: Set SGB Palettes 0 & 1 */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
.include "gingerbread.sgb"
|
||||
.include "gb/gingerbread.sgb"
|
||||
|
||||
; Set the size of the ROM file here. 0 means 32 kB, 1 means 64 kB, 2 means 128 kB and so on.
|
||||
ROM_SIZE = 1
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
* Paku Paku for Game Boy — port of presets/pacman/pakupaku.c
|
||||
*
|
||||
* One-lane chase inspired by crisp-game-lib "PAKU PAKU":
|
||||
* A / Start = reverse Left / Right = face that way
|
||||
* Collect dots; power pellet makes the ghost edible.
|
||||
* Movement/AI match the Pac-Man hardware port; graphics from pacman.5e/5f
|
||||
* (regenerate with: python3 scripts/gen_gb_pakupaku_gfx.py).
|
||||
*/
|
||||
|
||||
//#link "gb/sfr.sgb"
|
||||
//#link "gb/crt0.sgb"
|
||||
//#resource "gb/global.sgb"
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
|
||||
typedef uint8_t byte;
|
||||
typedef uint16_t word;
|
||||
typedef int8_t sbyte;
|
||||
|
||||
#define FIELD_MAX 152 /* arcade 208 scaled to GB width */
|
||||
#define PLAYER_SPD 2
|
||||
#define NUM_DOTS 18
|
||||
#define HIT_DIST 12
|
||||
|
||||
#define ROW_ABOVE 8
|
||||
#define DOT_TILE_Y 9
|
||||
#define ROW_BELOW 10
|
||||
#define CORRIDOR_Y 68
|
||||
|
||||
/* BG tiles from pacman.5e (after font 0..34) */
|
||||
#define BKG_BASE 40
|
||||
#define T_WALL_ABOVE (BKG_BASE + 0)
|
||||
#define T_WALL_BELOW (BKG_BASE + 1)
|
||||
#define T_DOT (BKG_BASE + 2)
|
||||
#define T_POWER (BKG_BASE + 3)
|
||||
|
||||
/*
|
||||
* Arcade pacman.5f sprites → GB 16×16 (two 8×16 OAM each).
|
||||
* Each frame is 4 tiles at SPR_BASE: L-top, L-bot, R-top, R-bot.
|
||||
* Regenerate: python3 scripts/gen_gb_pakupaku_gfx.py
|
||||
*/
|
||||
#define SPR_BASE 48
|
||||
#define SPR_FRAME(i) ((byte)(SPR_BASE + (i) * 4))
|
||||
#define SP_OPEN SPR_FRAME(0)
|
||||
#define SP_MID SPR_FRAME(1)
|
||||
#define SP_CLOSED SPR_FRAME(2)
|
||||
#define SP_DEATH0 SPR_FRAME(3) /* 11 frames: 3..13 */
|
||||
#define SP_DEATH_N 11
|
||||
#define SP_GHOST0 SPR_FRAME(14)
|
||||
#define SP_GHOST1 SPR_FRAME(15)
|
||||
#define SP_SCARED0 SPR_FRAME(16)
|
||||
#define SP_SCARED1 SPR_FRAME(17)
|
||||
#define SP_EYES SPR_FRAME(18)
|
||||
#define SPR_TILE_COUNT 76
|
||||
|
||||
typedef struct {
|
||||
byte tx;
|
||||
byte active;
|
||||
byte is_power;
|
||||
} Dot;
|
||||
|
||||
word rnd = 0xCACE;
|
||||
word score;
|
||||
byte multiplier;
|
||||
byte player_x;
|
||||
sbyte player_vx;
|
||||
byte enemy_x;
|
||||
sbyte enemy_eye_vx;
|
||||
word power_ticks;
|
||||
word anim_ticks;
|
||||
byte btn_prev;
|
||||
byte game_over;
|
||||
byte waka;
|
||||
Dot dots[NUM_DOTS];
|
||||
|
||||
byte joy_left, joy_right, joy_fire, joy_start;
|
||||
byte sfx_timer;
|
||||
byte sfx_kind; /* 0=off 1/2=waka 3=eat 4=death 6=power */
|
||||
byte death_phase;
|
||||
|
||||
const uint8_t font_1bpp[] = {
|
||||
/* 0 space */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 1 '0' */ 0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00,
|
||||
/* 2 '1' */ 0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00,
|
||||
/* 3 '2' */ 0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00,
|
||||
/* 4 '3' */ 0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00,
|
||||
/* 5 '4' */ 0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00,
|
||||
/* 6 '5' */ 0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00,
|
||||
/* 7 '6' */ 0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00,
|
||||
/* 8 '7' */ 0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00,
|
||||
/* 9 '8' */ 0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00,
|
||||
/*10 '9' */ 0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00,
|
||||
/*11 'A' */ 0x3C,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
|
||||
/*12 'C' */ 0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00,
|
||||
/*13 'E' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00,
|
||||
/*14 'F' */ 0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00,
|
||||
/*15 'G' */ 0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00,
|
||||
/*16 'H' */ 0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00,
|
||||
/*17 'I' */ 0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,
|
||||
/*18 'K' */ 0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00,
|
||||
/*19 'L' */ 0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00,
|
||||
/*20 'M' */ 0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00,
|
||||
/*21 'N' */ 0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00,
|
||||
/*22 'O' */ 0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
|
||||
/*23 'P' */ 0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00,
|
||||
/*24 'R' */ 0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00,
|
||||
/*25 'S' */ 0x3C,0x66,0x60,0x3C,0x06,0x66,0x3C,0x00,
|
||||
/*26 'T' */ 0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00,
|
||||
/*27 'U' */ 0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00,
|
||||
/*28 'V' */ 0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00,
|
||||
/*29 'W' */ 0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00,
|
||||
/*30 'X' */ 0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00,
|
||||
/*31 'D' */ 0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00,
|
||||
/*32 '-' */ 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,
|
||||
/*33 '!' */ 0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00,
|
||||
/*34 '/' */ 0x06,0x06,0x0C,0x18,0x30,0x60,0x60,0x00,
|
||||
};
|
||||
|
||||
const uint8_t bkg_tiles[] = {
|
||||
/*{w:8,h:8,bpp:1,count:4,brev:1,np:2,pofs:1,sl:2}*/
|
||||
0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0xff,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x3c,0x00,0x7e,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x7e,0x00,0x3c,0x00,
|
||||
};
|
||||
|
||||
const uint8_t sprite_tiles[] = {
|
||||
/*{w:8,h:8,bpp:1,count:76,brev:1,np:2,pofs:1,sl:2}*/
|
||||
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7e,0x7e,0x7c,0x7c,0x78,0x78,
|
||||
0x7c,0x7c,0x7e,0x7e,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x78,0x78,
|
||||
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xe0,0xe0,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x07,0x07,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
|
||||
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,
|
||||
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x70,0x70,0x78,0x78,0x7c,0x7c,0x7e,0x7e,
|
||||
0x3f,0x3f,0x3f,0x3f,0x1f,0x1f,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x1c,0x1c,0x3c,0x3c,0x7c,0x7c,0xfc,0xfc,
|
||||
0xf8,0xf8,0xf8,0xf8,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0xf0,0xf0,0xf8,0xf8,0xfe,0xfe,
|
||||
0x7f,0x7f,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x1e,0x1e,0x3e,0x3e,0xfe,0xfe,
|
||||
0xfc,0xfc,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0xfc,0xfc,
|
||||
0xff,0xff,0x7f,0x7f,0x3f,0x3f,0x0e,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x0e,0x7e,0x7e,
|
||||
0xfe,0xfe,0xfc,0xfc,0xf8,0xf8,0xe0,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,
|
||||
0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0x1e,
|
||||
0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x0f,0x0f,0xff,0xff,0xff,0xff,0x7f,0x7f,0x1e,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xe0,0xe0,0xfe,0xfe,0xfe,0xfe,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x03,0x03,0x0f,0x0f,0x7f,0x7f,0xff,0xff,0x7e,0x7e,0x1c,0x1c,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x80,0x80,0xe0,0xe0,0xfc,0xfc,0xfe,0xfe,0xfc,0xfc,0x70,0x70,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0x01,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x7f,0x7f,0x7e,0x7e,0x3c,0x3c,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x80,0x80,0xe0,0xe0,0xf0,0xf0,0xfc,0xfc,0xfc,0xfc,0x78,0x78,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0x01,0x03,0x03,0x07,0x07,0x07,0x07,0x0f,0x0f,0x1f,0x1f,0x0e,0x0e,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x80,0x80,0xc0,0xc0,0xc0,0xc0,0xe0,0xe0,0xf0,0xf0,0xe0,0xe0,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0x01,0x01,0x01,0x03,0x03,0x03,0x03,0x03,0x03,0x07,0x07,0x02,0x02,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0xc0,0x80,0x80,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x12,0x12,
|
||||
0x08,0x08,0x00,0x00,0x30,0x30,0x00,0x00,0x08,0x08,0x10,0x10,0x02,0x02,0x04,0x04,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80,0x80,
|
||||
0x10,0x10,0x20,0x20,0x00,0x00,0x18,0x18,0x00,0x00,0x20,0x20,0x90,0x90,0x40,0x40,
|
||||
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
|
||||
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
|
||||
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
|
||||
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x39,0x3f,0x30,0x3c,0x33,0x7c,0x73,
|
||||
0x7f,0x79,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xe4,0xfc,0xc0,0xf0,0xcc,0xf2,0xce,
|
||||
0xfe,0xe6,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
|
||||
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
|
||||
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x6e,0x6e,0x46,0x46,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
|
||||
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0x76,0x76,0x62,0x62,0x00,0x00,
|
||||
0x00,0x00,0x03,0x03,0x0f,0x0f,0x1f,0x1f,0x3f,0x3f,0x3f,0x3f,0x3f,0x39,0x7f,0x79,
|
||||
0x7f,0x7f,0x7f,0x7f,0x7f,0x66,0x7f,0x59,0x7f,0x7f,0x7b,0x7b,0x31,0x31,0x00,0x00,
|
||||
0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xf8,0xf8,0xfc,0xfc,0xfc,0xfc,0xfc,0x9c,0xfe,0x9e,
|
||||
0xfe,0xfe,0xfe,0xfe,0xfe,0x66,0xfe,0x9a,0xfe,0xfe,0xde,0xde,0x8c,0x8c,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x0f,0x00,0x0c,0x00,0x0c,0x00,
|
||||
0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x3c,0x00,0x30,0x00,0x30,0x00,
|
||||
0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
};
|
||||
|
||||
byte rand8(void) {
|
||||
rnd = rnd * 17 + 53;
|
||||
return (byte)(rnd >> 8);
|
||||
}
|
||||
|
||||
byte abs_diff(byte a, byte b) {
|
||||
return (a > b) ? (byte)(a - b) : (byte)(b - a);
|
||||
}
|
||||
|
||||
byte glyph(char c) {
|
||||
if (c == ' ') return 0;
|
||||
if (c >= '0' && c <= '9') return (byte)(1 + (c - '0'));
|
||||
switch (c) {
|
||||
case 'A': return 11; case 'C': return 12; case 'E': return 13;
|
||||
case 'F': return 14; case 'G': return 15; case 'H': return 16;
|
||||
case 'I': return 17; case 'K': return 18; case 'L': return 19;
|
||||
case 'M': return 20; case 'N': return 21; case 'O': return 22;
|
||||
case 'P': return 23; case 'R': return 24; case 'S': return 25;
|
||||
case 'T': return 26; case 'U': return 27; case 'V': return 28;
|
||||
case 'W': return 29; case 'X': return 30; case 'D': return 31;
|
||||
case '-': return 32; case '!': return 33; case '/': return 34;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void put_str(byte x, byte y, const char* s) {
|
||||
while (*s) set_bkg_tile_xy(x++, y, glyph(*s++));
|
||||
}
|
||||
|
||||
void put_digit(byte x, byte y, byte d) {
|
||||
set_bkg_tile_xy(x, y, (byte)(1 + (d % 10)));
|
||||
}
|
||||
|
||||
void clrscr(void) {
|
||||
byte x, y;
|
||||
for (y = 0; y < 32; y++)
|
||||
for (x = 0; x < 32; x++)
|
||||
set_bkg_tile_xy(x, y, 0);
|
||||
}
|
||||
|
||||
void hide_all_sprites(void) {
|
||||
byte i;
|
||||
for (i = 0; i < 40; i++) move_sprite(i, 0, 0);
|
||||
}
|
||||
|
||||
void read_controls(void) {
|
||||
byte j = joypad();
|
||||
joy_left = (j & J_LEFT) != 0;
|
||||
joy_right = (j & J_RIGHT) != 0;
|
||||
joy_fire = (j & J_A) != 0;
|
||||
joy_start = (j & J_START) != 0;
|
||||
}
|
||||
|
||||
void psg_init(void) {
|
||||
NR52_REG = 0x80;
|
||||
NR50_REG = 0x77;
|
||||
NR51_REG = 0xFF;
|
||||
NR10_REG = 0x00;
|
||||
}
|
||||
|
||||
void sfx_off(void) {
|
||||
sfx_timer = 0;
|
||||
sfx_kind = 0;
|
||||
NR12_REG = 0x00;
|
||||
NR14_REG = 0x80;
|
||||
NR22_REG = 0x00;
|
||||
NR24_REG = 0x80;
|
||||
}
|
||||
|
||||
void beep(word per, byte vol, byte len) {
|
||||
/* ch1 one-shot */
|
||||
NR10_REG = 0x00;
|
||||
NR11_REG = 0x80;
|
||||
NR12_REG = (byte)((vol << 4) | 0x01);
|
||||
NR13_REG = (byte)(per & 0xff);
|
||||
NR14_REG = (byte)(0x80 | ((per >> 8) & 0x07));
|
||||
sfx_timer = len;
|
||||
}
|
||||
|
||||
void play_sfx(byte id) {
|
||||
sfx_kind = id;
|
||||
if (id == 4) death_phase = 0;
|
||||
switch (id) {
|
||||
case 1: beep(0x6C0, 10, 3); break; /* waka hi */
|
||||
case 2: beep(0x650, 9, 3); break; /* waka lo */
|
||||
case 3: beep(0x700, 12, 12); break; /* eat ghost */
|
||||
case 4: beep(0x480, 12, 40); break; /* death */
|
||||
case 6: beep(0x720, 11, 8); break; /* power */
|
||||
default: sfx_off(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void tick_sfx(void) {
|
||||
if (!sfx_timer) return;
|
||||
--sfx_timer;
|
||||
if (!sfx_timer && sfx_kind == 4) {
|
||||
death_phase++;
|
||||
if (death_phase < 4) {
|
||||
beep((word)(0x480 - death_phase * 0x40), 10, 10);
|
||||
sfx_kind = 4;
|
||||
} else {
|
||||
death_phase = 0;
|
||||
sfx_kind = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wait_vblank(void) {
|
||||
wait_vbl_done();
|
||||
tick_sfx();
|
||||
}
|
||||
|
||||
void wait_frames(byte n) {
|
||||
while (n--) wait_vblank();
|
||||
}
|
||||
|
||||
void wait_for_button(void) {
|
||||
read_controls();
|
||||
while (!joy_fire && !joy_start) { wait_vblank(); read_controls(); }
|
||||
while (joy_fire || joy_start) { wait_vblank(); read_controls(); }
|
||||
}
|
||||
|
||||
void draw_hud(void) {
|
||||
put_str(0, 0, "PAKU PAKU");
|
||||
put_str(0, 1, "SCORE");
|
||||
put_digit(6, 1, (byte)((score / 1000) % 10));
|
||||
put_digit(7, 1, (byte)((score / 100) % 10));
|
||||
put_digit(8, 1, (byte)((score / 10) % 10));
|
||||
put_digit(9, 1, (byte)(score % 10));
|
||||
put_str(12, 1, "X");
|
||||
put_digit(13, 1, multiplier > 9 ? 9 : multiplier);
|
||||
put_str(0, 17, "A=TURN");
|
||||
}
|
||||
|
||||
void draw_corridor(void) {
|
||||
byte x;
|
||||
for (x = 0; x < 20; x++) {
|
||||
set_bkg_tile_xy(x, ROW_ABOVE, T_WALL_ABOVE);
|
||||
set_bkg_tile_xy(x, DOT_TILE_Y, 0);
|
||||
set_bkg_tile_xy(x, ROW_BELOW, T_WALL_BELOW);
|
||||
}
|
||||
}
|
||||
|
||||
void add_dots(void) {
|
||||
byte i, pi;
|
||||
pi = (player_x > 80) ? (byte)(1 + (rand8() % 7)) : (byte)(10 + (rand8() % 7));
|
||||
for (i = 0; i < NUM_DOTS; i++) {
|
||||
dots[i].tx = (byte)(i + 1);
|
||||
dots[i].active = 1;
|
||||
dots[i].is_power = (i == pi);
|
||||
}
|
||||
if (multiplier < 99) multiplier++;
|
||||
}
|
||||
|
||||
void draw_dots(void) {
|
||||
byte i;
|
||||
for (i = 0; i < NUM_DOTS; i++) {
|
||||
if (!dots[i].active) {
|
||||
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
|
||||
continue;
|
||||
}
|
||||
if (dots[i].is_power) {
|
||||
if ((anim_ticks >> 3) & 1)
|
||||
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_POWER);
|
||||
else
|
||||
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
|
||||
} else {
|
||||
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, T_DOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collect_dots(void) {
|
||||
byte i, ptx;
|
||||
ptx = (byte)((player_x + 8) >> 3);
|
||||
if (ptx < 1) ptx = 1;
|
||||
if (ptx > NUM_DOTS) ptx = NUM_DOTS;
|
||||
for (i = 0; i < NUM_DOTS; i++) {
|
||||
if (!dots[i].active) continue;
|
||||
if (dots[i].tx != ptx) continue;
|
||||
dots[i].active = 0;
|
||||
set_bkg_tile_xy(dots[i].tx, DOT_TILE_Y, 0);
|
||||
if (dots[i].is_power) {
|
||||
play_sfx(6);
|
||||
if (!enemy_eye_vx) power_ticks = 200;
|
||||
} else {
|
||||
waka ^= 1;
|
||||
play_sfx(waka ? 1 : 2);
|
||||
}
|
||||
score += multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
byte dots_left(void) {
|
||||
byte i, n = 0;
|
||||
for (i = 0; i < NUM_DOTS; i++)
|
||||
if (dots[i].active) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
byte pac_frame(void) {
|
||||
byte ai = (byte)(anim_ticks & 3);
|
||||
if (ai == 0) return SP_OPEN;
|
||||
if (ai == 1) return SP_MID;
|
||||
if (ai == 2) return SP_CLOSED;
|
||||
return SP_MID;
|
||||
}
|
||||
|
||||
void put_actor16(byte i0, byte frame, byte flip, byte prop, byte x, byte y) {
|
||||
/* Two 8×16 sprites = one 16×16 arcade sprite; swap halves when flipped. */
|
||||
byte i1 = (byte)(i0 + 1);
|
||||
byte p = prop;
|
||||
byte oam0, oam1;
|
||||
if (flip) p |= S_FLIPX;
|
||||
set_sprite_tile(i0, frame);
|
||||
set_sprite_tile(i1, (byte)(frame + 2));
|
||||
set_sprite_prop(i0, p);
|
||||
set_sprite_prop(i1, p);
|
||||
if (flip) {
|
||||
oam0 = (byte)(x + 16);
|
||||
oam1 = (byte)(x + 8);
|
||||
} else {
|
||||
oam0 = (byte)(x + 8);
|
||||
oam1 = (byte)(x + 16);
|
||||
}
|
||||
/* Hide if past the right edge (OAM X wraps). */
|
||||
if (x > 152) {
|
||||
move_sprite(i0, 0, 0);
|
||||
move_sprite(i1, 0, 0);
|
||||
return;
|
||||
}
|
||||
move_sprite(i0, oam0, (byte)(y + 16));
|
||||
move_sprite(i1, oam1, (byte)(y + 16));
|
||||
}
|
||||
|
||||
void draw_actors(void) {
|
||||
byte ghost, gprop, pflip, gflip;
|
||||
sbyte evx;
|
||||
|
||||
pflip = (player_vx < 0) ? 1 : 0;
|
||||
put_actor16(0, pac_frame(), pflip, 0, player_x, CORRIDOR_Y);
|
||||
|
||||
if (enemy_eye_vx) {
|
||||
ghost = SP_EYES;
|
||||
gflip = (enemy_eye_vx < 0) ? 1 : 0;
|
||||
gprop = 0;
|
||||
} else if (power_ticks) {
|
||||
ghost = ((anim_ticks >> 1) & 1) ? SP_SCARED1 : SP_SCARED0;
|
||||
gflip = 0;
|
||||
gprop = 0;
|
||||
/* blink near end of power */
|
||||
if (power_ticks < 60 && (anim_ticks & 0x08)) {
|
||||
move_sprite(2, 0, 0);
|
||||
move_sprite(3, 0, 0);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ghost = ((anim_ticks >> 1) & 1) ? SP_GHOST1 : SP_GHOST0;
|
||||
evx = (player_x > enemy_x) ? 1 : -1;
|
||||
gflip = (evx < 0) ? 1 : 0;
|
||||
gprop = 0;
|
||||
}
|
||||
put_actor16(2, ghost, gflip, gprop, enemy_x, CORRIDOR_Y);
|
||||
}
|
||||
|
||||
void title_screen(void) {
|
||||
byte x;
|
||||
clrscr();
|
||||
hide_all_sprites();
|
||||
move_bkg(0, 0);
|
||||
/* corridor stripe behind the demo sprites */
|
||||
for (x = 0; x < 20; x++) {
|
||||
set_bkg_tile_xy(x, 6, T_WALL_ABOVE);
|
||||
set_bkg_tile_xy(x, 7, 0);
|
||||
set_bkg_tile_xy(x, 8, T_WALL_BELOW);
|
||||
}
|
||||
put_str(5, 2, "PAKU PAKU");
|
||||
put_actor16(0, SP_OPEN, 0, 0, 52, 48);
|
||||
put_actor16(2, SP_GHOST0, 0, 0, 92, 48);
|
||||
put_str(4, 11, "A TO TURN");
|
||||
put_str(2, 13, "EAT DOTS EAT GHOST");
|
||||
put_str(4, 16, "PRESS START");
|
||||
wait_for_button();
|
||||
sfx_off();
|
||||
}
|
||||
|
||||
void show_game_over(void) {
|
||||
byte frame_i, t, frame;
|
||||
hide_all_sprites();
|
||||
put_actor16(0, SP_OPEN, 0, 0, player_x, CORRIDOR_Y);
|
||||
play_sfx(4);
|
||||
/* Arcade: 11 frames × 8 vblanks */
|
||||
for (frame_i = 0; frame_i < SP_DEATH_N; frame_i++) {
|
||||
frame = (byte)(SP_DEATH0 + (byte)(frame_i * 4));
|
||||
for (t = 0; t < 8; t++) {
|
||||
put_actor16(0, frame, 0, 0, player_x, CORRIDOR_Y);
|
||||
wait_vblank();
|
||||
read_controls();
|
||||
if (joy_fire || joy_start) goto death_done;
|
||||
}
|
||||
}
|
||||
death_done:
|
||||
hide_all_sprites();
|
||||
put_str(5, 7, "GAME OVER");
|
||||
put_str(4, 10, "PRESS START");
|
||||
wait_for_button();
|
||||
sfx_off();
|
||||
}
|
||||
|
||||
void reset_round(void) {
|
||||
player_x = 48;
|
||||
player_vx = 1;
|
||||
enemy_x = 128;
|
||||
enemy_eye_vx = 0;
|
||||
power_ticks = 0;
|
||||
anim_ticks = 0;
|
||||
btn_prev = 1;
|
||||
game_over = 0;
|
||||
waka = 0;
|
||||
sfx_off();
|
||||
draw_corridor();
|
||||
add_dots();
|
||||
}
|
||||
|
||||
void game_loop(void) {
|
||||
byte btn;
|
||||
sbyte evx;
|
||||
byte espd;
|
||||
|
||||
clrscr();
|
||||
hide_all_sprites();
|
||||
move_bkg(0, 0);
|
||||
draw_corridor();
|
||||
draw_hud();
|
||||
reset_round();
|
||||
|
||||
while (!game_over) {
|
||||
wait_vblank();
|
||||
anim_ticks++;
|
||||
read_controls();
|
||||
|
||||
btn = (joy_fire || joy_start) ? 1 : 0;
|
||||
if (btn && !btn_prev) player_vx = (sbyte)-player_vx;
|
||||
btn_prev = btn;
|
||||
if (joy_left) player_vx = -1;
|
||||
if (joy_right) player_vx = 1;
|
||||
|
||||
/* Same wrap as pacman pakupaku.c */
|
||||
player_x = (byte)(player_x + player_vx * PLAYER_SPD);
|
||||
if (player_x < 4) player_x = FIELD_MAX;
|
||||
else if (player_x > FIELD_MAX) player_x = 4;
|
||||
|
||||
collect_dots();
|
||||
if (!dots_left()) add_dots();
|
||||
|
||||
/* Ghost AI — identical rules to presets/pacman/pakupaku.c */
|
||||
if (enemy_eye_vx) {
|
||||
evx = enemy_eye_vx;
|
||||
espd = 3;
|
||||
} else if (power_ticks) {
|
||||
evx = (player_x > enemy_x) ? (sbyte)-1 : 1;
|
||||
espd = 1;
|
||||
} else {
|
||||
evx = (player_x > enemy_x) ? 1 : -1;
|
||||
espd = 2;
|
||||
}
|
||||
if (evx > 0) {
|
||||
if (enemy_x < FIELD_MAX - 2) enemy_x = (byte)(enemy_x + espd);
|
||||
else if (enemy_eye_vx) enemy_eye_vx = 0;
|
||||
} else {
|
||||
if (enemy_x > 6) enemy_x = (byte)(enemy_x - espd);
|
||||
else if (enemy_eye_vx) enemy_eye_vx = 0;
|
||||
}
|
||||
|
||||
if (!enemy_eye_vx && abs_diff(player_x, enemy_x) < HIT_DIST) {
|
||||
if (power_ticks) {
|
||||
play_sfx(3);
|
||||
score += (word)(10 * (multiplier ? multiplier : 1));
|
||||
enemy_eye_vx = (player_x > 80) ? (sbyte)-1 : 1;
|
||||
power_ticks = 0;
|
||||
if (multiplier < 99) multiplier++;
|
||||
} else {
|
||||
game_over = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (power_ticks) power_ticks--;
|
||||
|
||||
draw_dots();
|
||||
draw_hud();
|
||||
draw_actors();
|
||||
}
|
||||
}
|
||||
|
||||
void setup_graphics(void) {
|
||||
DISPLAY_OFF;
|
||||
set_bkg_1bpp_data(0, 35, font_1bpp);
|
||||
set_bkg_data(BKG_BASE, 4, bkg_tiles);
|
||||
set_sprite_data(SPR_BASE, SPR_TILE_COUNT, sprite_tiles);
|
||||
SPRITES_8x16;
|
||||
BGP_REG = 0xE4;
|
||||
OBP0_REG = 0xE4;
|
||||
OBP1_REG = 0xE4;
|
||||
psg_init();
|
||||
HIDE_WIN;
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
setup_graphics();
|
||||
score = 0;
|
||||
multiplier = 0;
|
||||
while (1) {
|
||||
title_screen();
|
||||
score = 0;
|
||||
multiplier = 0;
|
||||
game_loop();
|
||||
show_game_over();
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ of text
|
||||
If you DO want to add soft line breaks (which translate to `<br>` in HTML) to a paragraph,
|
||||
you can do so by adding 3 space characters to the end of the line (` `).
|
||||
|
||||
You can also force every line break in paragraphs to translate to `<br>` (as Github does) by
|
||||
You can also force every line break in paragraphs to translate to `<br>` (as GitHub does) by
|
||||
enabling the option **`simpleLineBreaks`**.
|
||||
|
||||
## Headings
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,195 @@
|
||||
(story title) Cloak of Darkness
|
||||
(story author) Linus Åkesson
|
||||
(story noun) A port of Roger Firth's reference game
|
||||
|
||||
(story ifid)
|
||||
%% This is the IFID for the Dialog port of Cloak of Darkness:
|
||||
A5AA4F02-8F50-4649-A4BD-B1B5C5408B67
|
||||
|
||||
(story release 3)
|
||||
|
||||
(scoring enabled)
|
||||
(maximum score 2)
|
||||
|
||||
(library links enabled)
|
||||
(default actions enabled)
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
(intro)
|
||||
|
||||
Hurrying through the rainswept November night, you're glad to
|
||||
see the bright lights of the Opera House. It's surprising that
|
||||
there aren't more people about but, hey, what do you expect in
|
||||
a cheap demo game...?
|
||||
|
||||
(banner)
|
||||
(enter #foyer)
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
#foyer
|
||||
|
||||
(room *)
|
||||
(singleton *)
|
||||
(name *) foyer of the Opera House
|
||||
|
||||
(look *)
|
||||
|
||||
(link [me]) {You} are standing in a spacious hall, splendidly
|
||||
decorated in red and gold, with glittering chandeliers
|
||||
overhead. The entrance from the street is to the (#north), and
|
||||
there are doorways (#south) and (#west).
|
||||
|
||||
(prevent [leave * #north])
|
||||
|
||||
You've only just arrived, and besides, the weather outside
|
||||
seems to be getting worse.
|
||||
|
||||
(from * go #west to #cloakroom)
|
||||
(from * go #south to #bar)
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
#cloakroom
|
||||
|
||||
(room *)
|
||||
(singleton *)
|
||||
(name *) cloakroom
|
||||
|
||||
(look *)
|
||||
|
||||
The walls of this small room were clearly once lined with
|
||||
hooks, though now (#hook { only one }) remains. The exit is a
|
||||
door to the (#east).
|
||||
|
||||
(notice #hook)
|
||||
|
||||
(from * go #east to #foyer)
|
||||
|
||||
#hook
|
||||
|
||||
(supporter *)
|
||||
(name *) small brass hook
|
||||
(dict *) peg
|
||||
(* is #in #cloakroom)
|
||||
|
||||
(descr *)
|
||||
|
||||
It's just a small brass hook,
|
||||
(collect $Obj) ($Obj is #on *) (into $List)
|
||||
(if) (nonempty $List) (then)
|
||||
with (a $List) hanging on it.
|
||||
(else)
|
||||
screwed to the wall.
|
||||
(endif)
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
#bar
|
||||
|
||||
(room *)
|
||||
(singleton *)
|
||||
(name *) Foyer Bar
|
||||
(look *)
|
||||
|
||||
The bar, much rougher than you'd have guessed after the
|
||||
opulence of the foyer to the north, is completely empty. There
|
||||
seems to be some sort of (#message {message}) scrawled in the
|
||||
sawdust on the floor.
|
||||
|
||||
(notice #message)
|
||||
|
||||
(from * go #north to #foyer)
|
||||
|
||||
(inherently dark *)
|
||||
(#cloak is in room *)
|
||||
|
||||
(instead of [look #down])
|
||||
(current room *)
|
||||
(try [examine #message])
|
||||
|
||||
#message
|
||||
|
||||
(name *) scrawled message
|
||||
(dict *) floor sawdust
|
||||
(* is #in #bar)
|
||||
|
||||
(descr *)
|
||||
(if) (message has been trampled) (then)
|
||||
The message has been carelessly trampled, making it
|
||||
difficult to read. You can just distinguish the
|
||||
words...
|
||||
(par)
|
||||
(game over { You have lost })
|
||||
(else)
|
||||
(increase score by 1)
|
||||
The message, neatly marked in the sawdust, reads...
|
||||
(par)
|
||||
(game over { You have won })
|
||||
(endif)
|
||||
|
||||
~(warned the player about darkness)
|
||||
|
||||
(prevent $Action)
|
||||
(current room #bar)
|
||||
~(player can see)
|
||||
~(command $Action)
|
||||
($Action = [$Verb | $])
|
||||
~($Verb is one of [go leave enter look])
|
||||
(if) (warned the player about darkness) (then)
|
||||
Blundering around in the dark isn't a good idea!
|
||||
(now) (message has been trampled)
|
||||
(else)
|
||||
In the dark? You could easily disturb something.
|
||||
(now) (warned the player about darkness)
|
||||
(endif)
|
||||
|
||||
(instead of [read *])
|
||||
(try [examine *])
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
#player
|
||||
|
||||
(current player *)
|
||||
(* is #in #foyer)
|
||||
|
||||
(instead of [examine *])
|
||||
(try [inventory])
|
||||
|
||||
#cloak
|
||||
|
||||
(name *) velvet cloak
|
||||
(dict *) dark black satin
|
||||
(wearable *)
|
||||
(* is #wornby #player)
|
||||
|
||||
(descr *)
|
||||
|
||||
A handsome cloak, of velvet trimmed with satin, and slightly
|
||||
splattered with raindrops. Its blackness is so deep that it
|
||||
almost seems to suck light from the room.
|
||||
|
||||
(prevent [drop *])
|
||||
(when dropping outside cloakroom)
|
||||
|
||||
(prevent [put * $ $])
|
||||
(when dropping outside cloakroom)
|
||||
|
||||
(when dropping outside cloakroom)
|
||||
~(current room #cloakroom)
|
||||
This isn't the best place to leave a smart cloak lying around.
|
||||
|
||||
(perform [put * #on #hook])
|
||||
~(hook point awarded)
|
||||
(increase score by 1)
|
||||
(now) (hook point awarded)
|
||||
(fail) %% Fall back on default behaviour now.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
(understand [hang | $Words] as [put $Obj #on $Dest])
|
||||
*(split $Words by [on] into $Left and $Right)
|
||||
*(understand $Left as object $Obj preferably held)
|
||||
*(understand $Right as single object $Dest)
|
||||
@@ -0,0 +1,48 @@
|
||||
%% Hello World, in the Dialog interactive fiction language.
|
||||
%% https://linusakesson.net/dialog/docs/
|
||||
|
||||
%% Story metadata
|
||||
(story title) Hello World
|
||||
(story author) 8bitworkshop
|
||||
(story noun) A one-room Dialog demo
|
||||
|
||||
%% (intro) runs when the game starts.
|
||||
(intro)
|
||||
Welcome to Dialog! Try LOOK, EXAMINE ME, or TURN ON LAMP.
|
||||
(par)
|
||||
(banner)
|
||||
(enter #room)
|
||||
|
||||
%% Define an object: the player character.
|
||||
%% The * token refers to the last object
|
||||
%% labelled on the left margin (the current "topic")
|
||||
#player
|
||||
(current player *)
|
||||
(descr *) You look marvelous.
|
||||
|
||||
%% The only room (for now).
|
||||
#room
|
||||
(room *)
|
||||
(name *) small room
|
||||
(look *)
|
||||
This is a small, featureless room.
|
||||
|
||||
%% An object the player can pick up.
|
||||
#lamp
|
||||
(item *)
|
||||
(name *) brass lamp
|
||||
(dict *) lantern
|
||||
(initial appearance *)
|
||||
A brass lamp sits on the floor.
|
||||
(descr *)
|
||||
(* is on)
|
||||
It is a battered brass lamp, switched on.
|
||||
(descr *)
|
||||
It is a battered brass lamp, currently off.
|
||||
|
||||
(* is #in #room)
|
||||
(switchable *)
|
||||
|
||||
%% A rule: The lamp provides light when it is turned on.
|
||||
(* provides light)
|
||||
(* is on)
|
||||
@@ -441,6 +441,7 @@ export function getToolForFilename_6502(fn: string): string {
|
||||
if (fn.endsWith(".ca65")) return "ca65";
|
||||
if (fn.endsWith(".dasm")) return "dasm";
|
||||
if (fn.endsWith(".acme")) return "acme";
|
||||
if (fn.endsWith(".xa")) return "xa";
|
||||
if (fn.endsWith(".wiz")) return "wiz";
|
||||
if (fn.endsWith(".ecs")) return "ecs";
|
||||
if (fn.endsWith(".cpp")) return "oscar64";
|
||||
@@ -480,7 +481,7 @@ export abstract class Base6502Platform extends BaseDebugPlatform {
|
||||
return disassemble6502(pc, read(pc), read(pc + 1), read(pc + 2));
|
||||
}
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; };
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; };
|
||||
|
||||
getDebugCategories() {
|
||||
return ['CPU', 'ZPRAM', 'Stack'];
|
||||
@@ -984,7 +985,7 @@ export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMac
|
||||
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getToolForFilename(fn) { return getToolForFilename_6502(fn); }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
|
||||
disassemble(pc: number, read: (addr: number) => number): DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc + 1), read(pc + 2));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Shared Game Boy DMG (olive LCD) palette.
|
||||
*
|
||||
* Shade 0 = lightest (LCD pixel off / "white"), shade 3 = darkest (pixel on).
|
||||
* Values are packed for Canvas ImageData as Uint32 little-endian RGBA (= 0xAABBGGRR).
|
||||
*/
|
||||
|
||||
export function rgbToPixel(r: number, g: number, b: number): number {
|
||||
return 0xFF000000 | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff);
|
||||
}
|
||||
|
||||
/** Four DMG shades, lightest → darkest (matches BGP/OBP shade indices). */
|
||||
export const DMG_PALETTE: number[] = [
|
||||
rgbToPixel(0xd0, 0xd8, 0x84), // Lightest (warm mint)
|
||||
rgbToPixel(0x87, 0xa8, 0x4c), // Light mid
|
||||
rgbToPixel(0x52, 0x6b, 0x34), // Dark mid
|
||||
rgbToPixel(0x2d, 0x31, 0x22), // Darkest (deep olive)
|
||||
];
|
||||
|
||||
/** RGB888 triples matching DMG_PALETTE (for boytacean remap / tooling). */
|
||||
export const DMG_PALETTE_RGB: [number, number, number][] = [
|
||||
[0xd0, 0xd8, 0x84],
|
||||
[0x87, 0xa8, 0x4c],
|
||||
[0x52, 0x6b, 0x34],
|
||||
[0x2d, 0x31, 0x22],
|
||||
];
|
||||
|
||||
/** Asset-editor / PREDEF_PALETTES entries (RGB without alpha; callers OR 0xff000000). */
|
||||
export const DMG_PALETTE_RGB24: number[] = [
|
||||
0xd0d884,
|
||||
0x87a84c,
|
||||
0x526b34,
|
||||
0x2d3122,
|
||||
];
|
||||
|
||||
/** Map a greyscale / near-grey RGB888 pixel to a DMG shade (0=light … 3=dark). */
|
||||
export function dmgShadeFromRgb(r: number, g: number, b: number): number {
|
||||
var y = (r + g + b) / 3;
|
||||
if (y >= 192) return 0;
|
||||
if (y >= 128) return 1;
|
||||
if (y >= 64) return 2;
|
||||
return 3;
|
||||
}
|
||||
@@ -624,6 +624,17 @@ export class WASIRunner {
|
||||
const fd = this.fs.getFile(path);
|
||||
console.log("path_filestat_get", dir+"", filename, path, filestat_ptr, '->', fd+"");
|
||||
if (!fd) return WASIErrors.NOENT;
|
||||
this.poke_filestat(filestat_ptr, fd);
|
||||
return WASIErrors.SUCCESS;
|
||||
}
|
||||
fd_filestat_get(fd: number, filestat_ptr: number) {
|
||||
const file = this.fds[fd];
|
||||
debug("fd_filestat_get", fd, filestat_ptr, file + "");
|
||||
if (file == null) return WASIErrors.BADF;
|
||||
this.poke_filestat(filestat_ptr, file);
|
||||
return WASIErrors.SUCCESS;
|
||||
}
|
||||
poke_filestat(filestat_ptr: number, fd: WASIFileDescriptor) {
|
||||
this.poke64(filestat_ptr, fd.fdindex); // dev
|
||||
this.poke64(filestat_ptr + 8, 0); // ino
|
||||
this.poke8(filestat_ptr + 16, fd.type); // filetype
|
||||
@@ -686,6 +697,7 @@ export class WASIRunner {
|
||||
fd_seek: this.fd_seek.bind(this),
|
||||
fd_close: this.fd_close.bind(this),
|
||||
path_filestat_get: this.path_filestat_get.bind(this),
|
||||
fd_filestat_get: this.fd_filestat_get.bind(this),
|
||||
random_get: this.random_get.bind(this),
|
||||
path_readlink: this.path_readlink.bind(this),
|
||||
path_unlink_file: this.path_unlink_file.bind(this),
|
||||
|
||||
+10
-2
@@ -18,7 +18,7 @@ export interface EditorContext {
|
||||
}
|
||||
|
||||
export type SelectablePalette = {
|
||||
node: PixNode
|
||||
node: PixNode | null
|
||||
name: string
|
||||
palette: Uint32Array
|
||||
}
|
||||
@@ -238,7 +238,11 @@ export function validateAssetData(datastr: string, fmt): string | null {
|
||||
maxOffset = Math.max(maxOffset, offset);
|
||||
}
|
||||
}
|
||||
var required = maxOffset + (nplanes > 1 ? (nplanes - 1) * pofs : 0) + 1 + skip;
|
||||
// Planar formats (e.g. NES pofs>=wpimg) store extra planes past each image block.
|
||||
// Interleaved formats (GB/SMS pofs < wpimg) already include plane bytes in maxOffset.
|
||||
var planeExtent = (nplanes > 1) ? (nplanes - 1) * pofs : 0;
|
||||
if (planeExtent < wpimg) planeExtent = 0;
|
||||
var required = maxOffset + planeExtent + 1 + skip;
|
||||
|
||||
if (words.length != required) {
|
||||
return `Expected ${required} value(s), found ${words.length}`;
|
||||
@@ -358,6 +362,10 @@ export function convertPaletteFormat(palbytes: UintArray, palfmt: PixelEditorPal
|
||||
|
||||
// TODO: illegal colors?
|
||||
const PREDEF_PALETTES = {
|
||||
// LCD polarity: index 0 = lightest (pixel off), 3 = darkest (pixel on)
|
||||
'gb': [
|
||||
0xd0d884, 0x87a84c, 0x526b34, 0x2d3122,
|
||||
],
|
||||
'nes': [
|
||||
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
|
||||
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
|
||||
|
||||
+10
-3
@@ -42,7 +42,7 @@ export class NullFilesystem implements ProjectFilesystem {
|
||||
this.sets.push(path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class OverlayFilesystem implements ProjectFilesystem {
|
||||
@@ -222,6 +222,13 @@ export class CodeProject {
|
||||
while (m = re7.exec(text)) {
|
||||
this.pushAllFiles(files, m[1]);
|
||||
}
|
||||
// for dialog -- %% #include "file" (dialog has no include directive,
|
||||
// so the compiler is handed every file listed in a comment instead)
|
||||
let re8 = /^\s*%%\s*#include\s+"(.+?)"/gm;
|
||||
while (m = re8.exec(text)) {
|
||||
this.pushAllFiles(files, m[1]);
|
||||
}
|
||||
// TODO: for xa assembler
|
||||
}
|
||||
return files;
|
||||
}
|
||||
@@ -240,7 +247,7 @@ export class CodeProject {
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
loadFileDependencies(text:string) : Promise<Dependency[]> {
|
||||
let includes = this.parseIncludeDependencies(text);
|
||||
let linkfiles = this.parseLinkDependencies(text);
|
||||
@@ -442,7 +449,7 @@ export class CodeProject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stripLocalPath(path : string) : string {
|
||||
if (this.mainPath) {
|
||||
var folder = getFolderForPath(this.mainPath);
|
||||
|
||||
+32
-32
@@ -200,39 +200,39 @@ export class GithubService {
|
||||
// load README
|
||||
return sess.repo.contents('README.md').read();
|
||||
})
|
||||
.catch( (e) => {
|
||||
console.log(e);
|
||||
console.log('no README.md found')
|
||||
// make user repo exists
|
||||
return sess.repo.fetch().then( (_repo) => {
|
||||
return ''; // empty README
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
console.log('no README.md found')
|
||||
// make user repo exists
|
||||
return sess.repo.fetch().then((_repo) => {
|
||||
return ''; // empty README
|
||||
})
|
||||
})
|
||||
})
|
||||
.then( (readme) => {
|
||||
var m;
|
||||
// check README for main file
|
||||
const re8main = /8bitworkshop.com[^)]+file=([^)&]+)/;
|
||||
m = re8main.exec(readme);
|
||||
if (m && m[1]) {
|
||||
console.log("main path: '" + m[1] + "'");
|
||||
sess.mainPath = m[1];
|
||||
}
|
||||
// check README for proper platform
|
||||
// unless we use githubURL=
|
||||
// TODO: cannot handle multiple URLs in README
|
||||
const re8plat = /8bitworkshop.com[^)]+platform=([A-Za-z0-9._\-]+)/;
|
||||
m = re8plat.exec(readme);
|
||||
if (m) {
|
||||
console.log("platform id: '" + m[1] + "'");
|
||||
if (sess.platform_id && !sess.platform_id.startsWith(m[1]))
|
||||
throw Error("Platform mismatch: Repository is " + m[1] + ", you have " + sess.platform_id + " selected.");
|
||||
sess.platform_id = m[1];
|
||||
}
|
||||
// bind to repository
|
||||
this.bind(sess, true);
|
||||
// get head commit
|
||||
return sess;
|
||||
});
|
||||
.then((readme) => {
|
||||
var m;
|
||||
// check README for main file
|
||||
const re8main = /8bitworkshop.com[^)]+file=([^)&]+)/;
|
||||
m = re8main.exec(readme);
|
||||
if (m && m[1]) {
|
||||
console.log("main path: '" + m[1] + "'");
|
||||
sess.mainPath = m[1];
|
||||
}
|
||||
// check README for proper platform
|
||||
// unless we use githubURL=
|
||||
// TODO: cannot handle multiple URLs in README
|
||||
const re8plat = /8bitworkshop.com[^)]+platform=([A-Za-z0-9._\-]+)/;
|
||||
m = re8plat.exec(readme);
|
||||
if (m) {
|
||||
console.log("platform id: '" + m[1] + "'");
|
||||
if (sess.platform_id && !sess.platform_id.startsWith(m[1]))
|
||||
throw Error("Platform mismatch: Repository is '" + m[1] + "', you have '" + sess.platform_id + "' selected.");
|
||||
sess.platform_id = m[1];
|
||||
}
|
||||
// bind to repository
|
||||
this.bind(sess, true);
|
||||
// get head commit
|
||||
return sess;
|
||||
});
|
||||
}
|
||||
|
||||
pull(ghurl:string, deststore?) : Promise<GHSession> {
|
||||
|
||||
+9
-5
@@ -43,7 +43,7 @@ export async function importProjectFromGithub(githuburl: string, replaceURL: boo
|
||||
var sess: GHSession;
|
||||
var urlparse = parseGithubURL(githuburl);
|
||||
if (!urlparse) {
|
||||
alertError('Could not parse Github URL.');
|
||||
alertError('Could not parse GitHub URL.');
|
||||
return;
|
||||
}
|
||||
// redirect to repo if exists
|
||||
@@ -70,14 +70,14 @@ export async function importProjectFromGithub(githuburl: string, replaceURL: boo
|
||||
}).catch((e) => {
|
||||
setWaitDialog(false);
|
||||
console.log(e);
|
||||
alertError("Could not import " + githuburl + "." + e);
|
||||
alertError("Could not import " + githuburl + ". " + e);
|
||||
});
|
||||
}
|
||||
|
||||
export async function _loginToGithub(e) {
|
||||
var gh = await getGithubService();
|
||||
gh.login().then(() => {
|
||||
alertInfo("You are signed in to Github.");
|
||||
alertInfo("You are signed in to GitHub.");
|
||||
}).catch((e) => {
|
||||
alertError("Could not sign in." + e);
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export async function _loginToGithub(e) {
|
||||
export async function _logoutOfGithub(e) {
|
||||
var gh = await getGithubService();
|
||||
gh.logout().then(() => {
|
||||
alertInfo("You are logged out of Github.");
|
||||
alertInfo("You are logged out of GitHub.");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,6 +94,10 @@ export function _importProjectFromGithub(e) {
|
||||
var modal = $("#importGithubModal");
|
||||
var btn = $("#importGithubButton");
|
||||
modal.modal('show');
|
||||
modal.off('shown.bs.modal').on('shown.bs.modal', () => $("#importGithubURL").trigger('focus'));
|
||||
$("#importGithubURL").off('keydown').on('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); btn.trigger('click'); }
|
||||
});
|
||||
btn.off('click').on('click', () => {
|
||||
var githuburl = $("#importGithubURL").val() + "";
|
||||
modal.modal('hide');
|
||||
@@ -103,7 +107,7 @@ export function _importProjectFromGithub(e) {
|
||||
|
||||
export function _publishProjectToGithub(e) {
|
||||
if (repo_id) {
|
||||
if (!confirm("This project (" + getCurrentProject().mainPath + ") is already bound to a Github repository. Do you want to re-publish to a new repository? (You can instead choose 'Push Changes' to update files in the existing repository.)"))
|
||||
if (!confirm("This project (" + getCurrentProject().mainPath + ") is already bound to a GitHub repository. Do you want to re-publish to a new repository? (You can instead choose 'Push Changes' to update files in the existing repository.)"))
|
||||
return;
|
||||
}
|
||||
var modal = $("#publishGithubModal");
|
||||
|
||||
+9
-1
@@ -113,6 +113,7 @@ export function getWorkerParams() {
|
||||
const TOOL_TO_SOURCE_STYLE = {
|
||||
'dasm': '6502',
|
||||
'acme': '6502',
|
||||
'xa': '6502',
|
||||
'cc65': 'text/x-csrc',
|
||||
'ca65': '6502',
|
||||
'nesasm': '6502',
|
||||
@@ -131,6 +132,7 @@ const TOOL_TO_SOURCE_STYLE = {
|
||||
'yasm': 'gas',
|
||||
'smlrc': 'text/x-csrc',
|
||||
'inform6': 'inform6',
|
||||
'dialog': 'dialog',
|
||||
'fastbasic': 'fastbasic',
|
||||
'basic': 'basic',
|
||||
'silice': 'verilog',
|
||||
@@ -160,6 +162,8 @@ const TOOL_TO_HELPURL = {
|
||||
'cmoc': "http://perso.b2b2c.ca/~sarrazip/dev/cmoc.html",
|
||||
'remote:llvm-mos': 'https://llvm-mos.org/wiki/Welcome',
|
||||
'acme': 'https://raw.githubusercontent.com/sehugg/acme/main/docs/QuickRef.txt',
|
||||
'xa': 'https://www.floodgap.com/retrotech/xa/',
|
||||
'dialog': 'https://linusakesson.net/dialog/docs/',
|
||||
}
|
||||
|
||||
function newWorker(): Worker {
|
||||
@@ -489,7 +493,7 @@ function reloadProject(id: string) {
|
||||
}
|
||||
|
||||
async function getSkeletonFile(fileid: string): Promise<string> {
|
||||
var ext = platform.getToolForFilename(fileid);
|
||||
var ext = platform.getToolForFilename(fileid).replace(/^remote:/, "");
|
||||
try {
|
||||
return await $.get("presets/" + getBasePlatform(platform_id) + "/skeleton." + ext, 'text');
|
||||
} catch (e) {
|
||||
@@ -1414,6 +1418,10 @@ function _addIncludeFile() {
|
||||
addFileToProject("Include", ".ecs", (s) => { return 'import "' + s + '"' });
|
||||
else if (tool == 'acme')
|
||||
addFileToProject("Include", ".acme", (s) => { return '!src "' + s + '"' });
|
||||
else if (tool == 'xa')
|
||||
addFileToProject("Include", ".xa", (s) => { return '#include "' + s + '"' });
|
||||
else if (tool == 'dialog')
|
||||
addFileToProject("Include", ".dg", (s) => { return '%% #include "' + s + '"' });
|
||||
else
|
||||
alertError("Can't add include file to this project type (" + tool + ")");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
import { DMG_PALETTE } from "../../common/gbpalette";
|
||||
import { hex, rgb2bgr, safeident } from "../../common/util";
|
||||
import { FileData } from "../../common/workertypes";
|
||||
import * as pixed from "../pixeleditor";
|
||||
@@ -80,6 +81,18 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
node = node.right;
|
||||
}
|
||||
});
|
||||
// Game Boy: default to LCD green scale (0=light … 3=dark) when no project palette
|
||||
if (result.length == 0 && platform_id && platform_id.startsWith('gb')) {
|
||||
if (matchlen == 4) {
|
||||
result.push({ node: null, name: "DMG Green", palette: new Uint32Array(DMG_PALETTE) });
|
||||
} else if (matchlen == 2) {
|
||||
result.push({
|
||||
node: null,
|
||||
name: "DMG Green",
|
||||
palette: new Uint32Array([DMG_PALETTE[0], DMG_PALETTE[3]]),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SourceFile, SourceLocation, WorkerError } from "../../common/workertype
|
||||
import { asm6502 } from "../../parser/lang-6502";
|
||||
import { basic } from "../../parser/lang-basic";
|
||||
import { batariBasic } from "../../parser/lang-bataribasic";
|
||||
import { dialog } from "../../parser/lang-dialog";
|
||||
import { fastBasic } from "../../parser/lang-fastbasic";
|
||||
import { inform6 } from "../../parser/lang-inform6";
|
||||
import { verilog } from "../../parser/lang-verilog";
|
||||
@@ -74,9 +75,10 @@ const MODEDEFS = {
|
||||
gas: { isAsm: true },
|
||||
vasm: { isAsm: true },
|
||||
inform6: { theme: cobalt },
|
||||
dialog: { theme: cobalt, lineWrap: true },
|
||||
markdown: { lineWrap: true },
|
||||
fastbasic: { noGutters: true },
|
||||
basic: { noLineNumbers: true, noGutters: true },
|
||||
basic: { noGutters: true },
|
||||
ecs: { theme: mbo }, // TODO: is actually mixed-mode, as is verilog
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ export class SourceEditor implements ProjectView {
|
||||
var isAsm = isAsmOverride || modedef.isAsm;
|
||||
var lineWrap = !!modedef.lineWrap;
|
||||
var theme = modedef.theme || MODEDEFS.default.theme;
|
||||
var lineNums = !isAsm && !modedef.noLineNumbers && !isMobileDevice;
|
||||
var lineNums = modedef.useLineNumbers && !isMobileDevice;
|
||||
if (qs['embed']) {
|
||||
lineNums = false; // no line numbers while embedded
|
||||
isAsm = false; // no opcode bytes either
|
||||
@@ -150,6 +152,9 @@ export class SourceEditor implements ProjectView {
|
||||
case 'fastbasic':
|
||||
parser = fastBasic();
|
||||
break;
|
||||
case 'dialog':
|
||||
parser = dialog();
|
||||
break;
|
||||
case 'inform6':
|
||||
parser = inform6();
|
||||
break;
|
||||
@@ -546,8 +551,8 @@ export class SourceEditor implements ProjectView {
|
||||
effects: [
|
||||
currentPcMarker.set.of(line.line),
|
||||
currentPc.effect.of(line.line),
|
||||
// Optional: follow the execution point
|
||||
EditorView.scrollIntoView(this.editor.state.doc.line(line.line).from, { y: "center" }),
|
||||
// Follow the execution point when stepping/hitting breakpoints.
|
||||
...(moveCursor ? [EditorView.scrollIntoView(this.editor.state.doc.line(line.line).from, { y: "center" })] : []),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
+1
-8
@@ -2,16 +2,9 @@
|
||||
import { SM83, SM83State } from "../common/cpu/SM83";
|
||||
import { BasicScanlineMachine, Bus } from "../common/devices";
|
||||
import { newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
|
||||
import { DMG_PALETTE } from "../common/gbpalette";
|
||||
import { hex } from "../common/util";
|
||||
|
||||
// Game Boy DMG palette: 4 shades of green (darkest to lightest)
|
||||
const DMG_PALETTE: number[] = [
|
||||
0xFFd0d884, // Lightest (Warm Mint)
|
||||
0xFF87a84c, // Light Mid
|
||||
0xFF526b34, // Dark Mid
|
||||
0xFF2d3122, // Darkest (Deep Olive)
|
||||
];
|
||||
|
||||
// Convert GBC 15-bit RGB (xBBBBBGGGGGRRRRR) to 32-bit ARGB
|
||||
function cgbColorToARGB(lo: number, hi: number): number {
|
||||
var r5 = lo & 0x1F;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// CodeMirror 6 language support for Dialog
|
||||
// https://linusakesson.net/dialog/docs/
|
||||
|
||||
import { StreamLanguage, StreamParser } from "@codemirror/language";
|
||||
import { LanguageSupport } from "@codemirror/language";
|
||||
|
||||
interface DialogState {
|
||||
// parens are only nested inside a rule head/query; column 0 starts a rule
|
||||
depth: number;
|
||||
}
|
||||
|
||||
// TODO: Migrate to CodeMirror 6 Lezer parser.
|
||||
function createDialogParser(): StreamParser<DialogState> {
|
||||
// control keywords appear as the first word inside a parenthesis
|
||||
const keywords = new Set([
|
||||
"if", "then", "elseif", "else", "endif", "or", "select", "stopping",
|
||||
"at random", "purely at random", "then purely at random", "cycling",
|
||||
"collect", "into", "collect words", "determine object", "from words",
|
||||
"matching all of", "stoppable", "stop", "just", "exhaust", "repeat forever",
|
||||
"now", "global variable", "generate", "interface", "fail",
|
||||
]);
|
||||
|
||||
function token(stream: any, state: DialogState): string | null {
|
||||
if (stream.eatSpace()) return null;
|
||||
const ch = stream.peek();
|
||||
// %% comment to end of line
|
||||
if (stream.match("%%")) {
|
||||
stream.skipToEnd();
|
||||
return "comment";
|
||||
}
|
||||
// \( and \) are escaped literals inside text
|
||||
if (stream.match(/\\./)) {
|
||||
return "escape";
|
||||
}
|
||||
if (ch == "(" || ch == ")") {
|
||||
stream.next();
|
||||
state.depth += ch == "(" ? 1 : -1;
|
||||
if (state.depth < 0) state.depth = 0;
|
||||
return "bracket";
|
||||
}
|
||||
if (ch == "[" || ch == "]" || ch == "{" || ch == "}") {
|
||||
stream.next();
|
||||
return "bracket";
|
||||
}
|
||||
// $Variable, or $ for an anonymous variable
|
||||
if (stream.match(/\$[a-zA-Z0-9_]*/)) {
|
||||
return "variableName";
|
||||
}
|
||||
// #object name
|
||||
if (stream.match(/#[a-zA-Z0-9_+-]+/)) {
|
||||
return "atom";
|
||||
}
|
||||
// @style class
|
||||
if (stream.match(/@[a-zA-Z0-9_-]+/)) {
|
||||
return "className";
|
||||
}
|
||||
if (stream.match(/-?\d+\b/)) {
|
||||
return "number";
|
||||
}
|
||||
// ~ negation, * multi-query, | list tail
|
||||
if (stream.match(/[~*|]/)) {
|
||||
return "operator";
|
||||
}
|
||||
// words: keywords only count at the start of a predicate
|
||||
if (stream.match(/[^\s()\[\]{}$#@~*|\\%]+/)) {
|
||||
if (state.depth > 0 && keywords.has(stream.current().toLowerCase())) {
|
||||
return "keyword";
|
||||
}
|
||||
// text outside of parentheses is printed verbatim by the story
|
||||
return state.depth > 0 ? null : "string";
|
||||
}
|
||||
stream.next();
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
startState(): DialogState {
|
||||
return { depth: 0 };
|
||||
},
|
||||
token,
|
||||
languageData: {
|
||||
commentTokens: { line: "%%" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Language support for Dialog
|
||||
*/
|
||||
export function dialog(): LanguageSupport {
|
||||
return new LanguageSupport(StreamLanguage.define(createDialogParser()));
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class Apple2MAMEPlatform extends BaseMAME6502Platform implements Platform {
|
||||
}
|
||||
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
|
||||
getPresets() { return APPLE2_PRESETS; }
|
||||
|
||||
@@ -83,7 +83,7 @@ abstract class Atari8MAMEPlatform extends BaseMAME6502Platform {
|
||||
getPresets() { return Atari8_PRESETS; }
|
||||
getToolForFilename = getToolForFilename_Atari8;
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getDefaultExtensions() { return [".c", ".bas", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".bas", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
showHelp = atari8_showHelp;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ abstract class C64MAMEPlatform extends BaseMAME6502Platform {
|
||||
getPresets() { return C64_PRESETS; }
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
loadROM(title, data) {
|
||||
if (!this.started) {
|
||||
this.startModule(this.mainElement, {
|
||||
|
||||
@@ -7,6 +7,8 @@ const GB_PRESETS: Preset[] = [
|
||||
{ id: 'hello.c', name: 'Hello World (C)' },
|
||||
{ id: 'testdrawing.c', name: 'Drawing Routines (C)' },
|
||||
{ id: 'testphys.c', name: 'Sprite Test (C)' },
|
||||
{ id: 'chase.c', name: "Shiru's Chase Game" },
|
||||
{ id: 'pakupaku.c', name: 'Paku Paku' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)' },
|
||||
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
|
||||
];
|
||||
|
||||
+1
-1
@@ -561,7 +561,7 @@ class NESMAMEPlatform extends BaseMAME6502Platform implements Platform {
|
||||
getPresets() { return JSNES_PRESETS; }
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class PCEnginePlatform implements Platform {
|
||||
return getToolForFilename_6502(fn);
|
||||
}
|
||||
getDefaultExtensions() {
|
||||
return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"];
|
||||
return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"];
|
||||
}
|
||||
readAddress(addr: number): number {
|
||||
return this.pce.Get(addr);
|
||||
|
||||
+3
-3
@@ -303,7 +303,7 @@ class VCSPlatform extends BasePlatform {
|
||||
return "\n" + dumpRAM(ram, 0x80, 0x80);
|
||||
}
|
||||
getToolForFilename = getToolForFilename_vcs;
|
||||
getDefaultExtensions() { return [".dasm", ".a", ".bb", ".acme", ".ca65", ".cc2600", ".ecs", ".wiz", ".c"]; }
|
||||
getDefaultExtensions() { return [".dasm", ".a", ".bb", ".acme", ".xa", ".ca65", ".cc2600", ".ecs", ".wiz", ".c"]; }
|
||||
getROMExtension() { return ".a26"; }
|
||||
|
||||
getDebugCategories() {
|
||||
@@ -480,7 +480,7 @@ class VCSMAMEPlatform extends BaseMAME6502Platform implements Platform {
|
||||
getPresets = function() { return VCS_PRESETS; }
|
||||
|
||||
getToolForFilename = getToolForFilename_vcs;
|
||||
getDefaultExtensions() { return [".c", ".bb", ".acme", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".bb", ".acme", ".xa", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
|
||||
getROMExtension() { return ".a26"; }
|
||||
|
||||
getOriginPC = function() {
|
||||
@@ -538,7 +538,7 @@ class VCSStellaPlatform implements Platform {
|
||||
return this.running;
|
||||
}
|
||||
getToolForFilename = getToolForFilename_vcs;
|
||||
getDefaultExtensions() { return [".c", ".bb", ".acme", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".bb", ".acme", ".xa", ".ca65", ".dasm", ".cc2600", ".ecs", ".wiz"]; }
|
||||
getROMExtension() { return ".a26"; }
|
||||
getPresets() { return VCS_PRESETS }
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ abstract class VIC20MAMEPlatform extends BaseMAME6502Platform {
|
||||
getPresets() { return VIC20_PRESETS; }
|
||||
getToolForFilename = getToolForFilename_6502;
|
||||
getOpcodeMetadata = getOpcodeMetadata_6502;
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
getDefaultExtensions() { return [".c", ".cpp", ".acme", ".xa", ".ca65", ".dasm", ".ecs", ".wiz"]; }
|
||||
loadROM(title, data) {
|
||||
if (!this.started) {
|
||||
this.startModule(this.mainElement, {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { InputResponse } from "../common/basic/runtime";
|
||||
import { loadScript } from "../common/util";
|
||||
|
||||
const ZMACHINE_PRESETS = [
|
||||
{ id: 'hello.inf', name: 'Hello World' },
|
||||
{ id: 'hello.inf', name: 'Hello World', category: 'Inform 6' },
|
||||
{ id: 'house01.inf', name: 'House Tutorial #1' },
|
||||
{ id: 'house02.inf', name: 'House Tutorial #2' },
|
||||
{ id: 'house03.inf', name: 'House Tutorial #3' },
|
||||
@@ -25,6 +25,8 @@ const ZMACHINE_PRESETS = [
|
||||
{ id: 'museum.inf', name: 'Museum of Inform' },
|
||||
{ id: 'advent.inf', name: 'Colossal Cave Adventure' },
|
||||
{ id: 'ztrek.inf', name: 'Super Z Trek' },
|
||||
{ id: 'hello.dg', name: 'Hello Dialog', category: 'Dialog' },
|
||||
{ id: 'cottage.dg', name: 'Cloak of Darkness' },
|
||||
];
|
||||
|
||||
declare var ZVM;
|
||||
@@ -704,10 +706,10 @@ class ZmachinePlatform implements Platform {
|
||||
}
|
||||
|
||||
getToolForFilename(s: string): string {
|
||||
return "inform6";
|
||||
return s.endsWith(".dg") ? "dialog" : "inform6";
|
||||
}
|
||||
getDefaultExtensions() {
|
||||
return [".inf"];
|
||||
return [".inf", ".dg"];
|
||||
}
|
||||
showHelp() {
|
||||
return "https://www.inform-fiction.org/manual/html/contents.html";
|
||||
|
||||
+10
-1
@@ -49,6 +49,14 @@ export interface BuildStep extends WorkerBuildStep {
|
||||
|
||||
///
|
||||
|
||||
export function fixLineEndings(data: any): any {
|
||||
if (typeof data === 'string') {
|
||||
// C build tools require LF line endings.
|
||||
return data.replace(/\r\n/g, '\n');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export class FileWorkingStore implements WorkingStore {
|
||||
workfs: { [path: string]: FileEntry } = {};
|
||||
workerseq: number = 0;
|
||||
@@ -89,7 +97,7 @@ export class FileWorkingStore implements WorkingStore {
|
||||
let data = this.getFileData(path);
|
||||
if (data != null && typeof data !== 'string')
|
||||
throw new Error(`${path}: expected string`)
|
||||
return data as string; // TODO
|
||||
return fixLineEndings(data) as string; // TODO
|
||||
}
|
||||
getFileEntry(path: string): FileEntry {
|
||||
return this.workfs[path];
|
||||
@@ -256,6 +264,7 @@ export function populateEntry(fs, path: string, entry: FileEntry, options: Build
|
||||
if (options && options.processFn) {
|
||||
data = options.processFn(path, data);
|
||||
}
|
||||
data = fixLineEndings(data);
|
||||
// create subfolders
|
||||
var toks = path.split('/');
|
||||
if (toks.length > 1) {
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
import { WorkerError } from "../../common/workertypes";
|
||||
import { WASIFilesystem, WASIRunner } from "../../common/wasi/wasishim";
|
||||
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, store, putWorkFile } from "../builder";
|
||||
import { loadWASIFilesystemZip } from "../wasiutils";
|
||||
import { loadWASMBinary } from "../wasmutils";
|
||||
|
||||
// the Dialog standard library ships in the preload filesystem, but a project
|
||||
// can override it by including its own copy
|
||||
const STDLIB = "stdlib.dg";
|
||||
|
||||
let dialog_fs: WASIFilesystem | null = null;
|
||||
let wasiModule: WebAssembly.Module | null = null;
|
||||
|
||||
// Error: story.dg, line 6: Unexpected token at beginning of line.
|
||||
// Error: No library (such as stdlib.dg) was specified on the commandline.
|
||||
const re_error = /^Error:\s+(?:(\S+?), line (\d+):\s+)?(.+)/;
|
||||
|
||||
export async function compileDialog(step: BuildStep): Promise<BuildStepResult> {
|
||||
const errors: WorkerError[] = [];
|
||||
gatherFiles(step, { mainFilePath: "main.dg" });
|
||||
const destpath = step.prefix + ".z8";
|
||||
if (staleFiles(step, [destpath])) {
|
||||
if (!dialog_fs) {
|
||||
dialog_fs = await loadWASIFilesystemZip("dialog-fs.zip");
|
||||
}
|
||||
if (!wasiModule) {
|
||||
wasiModule = new WebAssembly.Module(loadWASMBinary("dialogc"));
|
||||
}
|
||||
const wasi = new WASIRunner();
|
||||
wasi.initSync(wasiModule);
|
||||
wasi.fs.setParent(dialog_fs);
|
||||
for (let file of step.files) {
|
||||
wasi.fs.putFile("./" + file, store.getFileData(file));
|
||||
}
|
||||
wasi.addPreopenDirectory(".");
|
||||
// sources are matched in command-line order, so the story comes first
|
||||
// and the library last -- a project's own stdlib.dg shadows the
|
||||
// preloaded one, since putFile() writes to the child filesystem
|
||||
const sources = step.files.filter(fn => fn.endsWith(".dg") && fn != STDLIB);
|
||||
sources.push(STDLIB);
|
||||
wasi.setArgs(["dialogc", "-t", "z8", "-o", destpath, ...sources]);
|
||||
try {
|
||||
wasi.run();
|
||||
} catch (e) {
|
||||
errors.push({ line: 0, msg: e + "" });
|
||||
}
|
||||
const stderr = wasi.fds[2].getBytesAsString();
|
||||
for (let line of stderr.split("\n")) {
|
||||
const matches = re_error.exec(line);
|
||||
if (matches) {
|
||||
errors.push({
|
||||
path: matches[1] || step.path,
|
||||
line: parseInt(matches[2]) || 0,
|
||||
msg: matches[3]
|
||||
});
|
||||
} else if (line) {
|
||||
console.log(line); // warnings, progress messages
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
return { errors };
|
||||
}
|
||||
const outfile = wasi.fs.getFile("./" + destpath);
|
||||
if (!outfile) {
|
||||
return { errors: [{ line: 0, msg: "dialogc did not produce " + destpath }] };
|
||||
}
|
||||
const output = outfile.getBytes();
|
||||
putWorkFile(destpath, output);
|
||||
return { output, errors };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { WASIRunner } from "../../common/wasi/wasishim";
|
||||
import { CodeListing, CodeListingMap } from "../../common/workertypes";
|
||||
import { BuildStep, BuildStepResult, gatherFiles, putWorkFile, staleFiles, store } from "../builder";
|
||||
import { re_crlf } from "../listingutils";
|
||||
import { loadWASMBinary } from "../wasmutils";
|
||||
|
||||
// xa listing lines look like this:
|
||||
// 5 A:c000 a9 05 start lda #$05 ; comment
|
||||
// (line number, segment:address, then fixed-width byte / label / source columns)
|
||||
const re_listing = /^\s*(\d+) (\w):([0-9a-f]+) (.*)$/i;
|
||||
const LABEL_COL = 25; // width of the byte column
|
||||
const SOURCE_COL = 35; // width of the byte + label columns
|
||||
|
||||
// xa writes errors to stderr, e.g.:
|
||||
// main.xa:line 7: c00c:Overflow error
|
||||
const re_error = /^(\S.*?):line (\d+): (?:[0-9a-f]+:)?\s*(.+)$/;
|
||||
|
||||
// the label file (-l) has one comma-separated entry per line:
|
||||
// start, 0xc000, 0, 0x0000
|
||||
const re_label = /^(\S+),\s*0x([0-9a-f]+)/i;
|
||||
|
||||
function parseXALabels(text: string) {
|
||||
const symbolmap = {};
|
||||
for (const line of text.split(re_crlf)) {
|
||||
const m = re_label.exec(line);
|
||||
if (m) {
|
||||
symbolmap[m[1]] = parseInt(m[2], 16);
|
||||
}
|
||||
}
|
||||
return symbolmap;
|
||||
}
|
||||
|
||||
function parseXAListing(lsttext: string, listings: CodeListingMap) {
|
||||
// xa prints the file name on its own line whenever it enters or resumes a file
|
||||
let path: string | null = null;
|
||||
let listing: CodeListing | null = null;
|
||||
let lastline = 0;
|
||||
const lastlines: { [path: string]: number } = {};
|
||||
for (const line of lsttext.split(re_crlf)) {
|
||||
const m = re_listing.exec(line);
|
||||
if (!m) {
|
||||
// a file name switches us to another listing
|
||||
const newpath = line.trim();
|
||||
if (listings[newpath]) {
|
||||
if (path) lastlines[path] = lastline;
|
||||
path = newpath;
|
||||
listing = listings[path];
|
||||
lastline = lastlines[path] || 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const linenum = parseInt(m[1]);
|
||||
const offset = parseInt(m[3], 16);
|
||||
const rest = m[4];
|
||||
const insns = rest.substring(0, LABEL_COL).replace('...', '').trim();
|
||||
const source = rest.substring(SOURCE_COL).trim();
|
||||
// when xa resumes a file after an include it repeats its line counter
|
||||
// for one line, so only accept line numbers that move forward
|
||||
if (insns && listing && linenum > lastline) {
|
||||
listing.lines.push({
|
||||
line: linenum,
|
||||
offset: offset,
|
||||
insns: insns,
|
||||
iscode: !source.startsWith('.'),
|
||||
});
|
||||
}
|
||||
if (linenum > lastline) lastline = linenum;
|
||||
}
|
||||
}
|
||||
|
||||
// the origin is the address of the first byte written to the output file
|
||||
function getListingOrigin(listings: CodeListingMap): number | undefined {
|
||||
let origin: number | undefined;
|
||||
for (const key in listings) {
|
||||
for (const line of listings[key].lines) {
|
||||
if (origin === undefined || line.offset < origin) origin = line.offset;
|
||||
}
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
let wasiModule: WebAssembly.Module | null = null;
|
||||
|
||||
export function assembleXA(step: BuildStep): BuildStepResult {
|
||||
const errors = [];
|
||||
gatherFiles(step, { mainFilePath: "main.xa" });
|
||||
const binpath = step.prefix + ".bin";
|
||||
const lstpath = step.prefix + ".lst";
|
||||
const sympath = step.prefix + ".lbl";
|
||||
if (staleFiles(step, [binpath])) {
|
||||
if (!wasiModule) {
|
||||
wasiModule = new WebAssembly.Module(loadWASMBinary("xa"));
|
||||
}
|
||||
const wasi = new WASIRunner();
|
||||
wasi.initSync(wasiModule);
|
||||
for (const file of step.files) {
|
||||
wasi.fs.putFile("./" + file, store.getFileData(file));
|
||||
}
|
||||
wasi.addPreopenDirectory(".");
|
||||
const args = ['xa', '-E', '-o', binpath, '-l', sympath, '-P', lstpath];
|
||||
if (step.params?.xaargs) {
|
||||
args.push.apply(args, step.params.xaargs);
|
||||
}
|
||||
args.push("-D__8BITWORKSHOP__=1");
|
||||
if (step.mainfile) {
|
||||
args.push("-D__MAIN__=1");
|
||||
}
|
||||
args.push(step.path);
|
||||
wasi.setArgs(args);
|
||||
try {
|
||||
wasi.run();
|
||||
} catch (e) {
|
||||
errors.push({ line: 0, msg: e + "" });
|
||||
}
|
||||
const stderr = wasi.fds[2].getBytesAsString();
|
||||
for (const line of stderr.split(re_crlf)) {
|
||||
const m = re_error.exec(line);
|
||||
if (m) {
|
||||
errors.push({ path: m[1], line: parseInt(m[2]), msg: m[3] });
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
return { errors };
|
||||
}
|
||||
const lstout = wasi.fs.getFile("./" + lstpath)?.getBytesAsString() || "";
|
||||
const symout = wasi.fs.getFile("./" + sympath)?.getBytesAsString() || "";
|
||||
const output = wasi.fs.getFile("./" + binpath)?.getBytes();
|
||||
if (!output) {
|
||||
return { errors: [{ line: 0, msg: "xa did not produce an output file" }] };
|
||||
}
|
||||
const listings: CodeListingMap = {};
|
||||
for (const path of step.files) {
|
||||
listings[path] = { lines: [] };
|
||||
}
|
||||
parseXAListing(lstout, listings);
|
||||
putWorkFile(binpath, output);
|
||||
putWorkFile(lstpath, lstout);
|
||||
putWorkFile(sympath, symout);
|
||||
return {
|
||||
output,
|
||||
errors,
|
||||
listings,
|
||||
symbolmap: parseXALabels(symout),
|
||||
origin: getListingOrigin(listings),
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -16,6 +16,8 @@ import * as cc7800 from './tools/cc7800'
|
||||
import * as cc2600 from './tools/cc2600'
|
||||
import * as bataribasic from './tools/bataribasic'
|
||||
import * as oscar64 from './tools/oscar64'
|
||||
import * as xa from './tools/xa'
|
||||
import * as dialog from './tools/dialog'
|
||||
|
||||
import { PLATFORM_PARAMS } from "./platforms";
|
||||
|
||||
@@ -60,6 +62,8 @@ export const TOOLS = {
|
||||
'armtcc': arm.compileARMTCC,
|
||||
'armtcclink': arm.linkARMTCC,
|
||||
'oscar64': oscar64.compileOscar64,
|
||||
'xa': xa.assembleXA,
|
||||
'dialog': dialog.compileDialog,
|
||||
}
|
||||
|
||||
export const TOOL_PRELOADFS = {
|
||||
|
||||
+12
-12
@@ -34,7 +34,7 @@ const t0 = new Date().getTime();
|
||||
|
||||
describe('Github', function() {
|
||||
|
||||
it('Should import from Github (check README)', function(done) {
|
||||
it('Should import from GitHub (check README)', function(done) {
|
||||
var store = createNewPersistentStore('vcs', function(store) {
|
||||
var gh = newGH(store, 'vcs');
|
||||
gh.importAndPull('https://github.com/pzpinfo/test123123/').then( (sess) => {
|
||||
@@ -52,7 +52,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (default branch)', function(done) {
|
||||
it('Should import from GitHub (default branch)', function(done) {
|
||||
var store = createNewPersistentStore('nes', function(store) {
|
||||
var gh = newGH(store, 'nes');
|
||||
gh.importAndPull('https://github.com/sehugg/mdf2020-nes').then( (sess) => {
|
||||
@@ -62,7 +62,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (explicit branch)', function(done) {
|
||||
it('Should import from GitHub (explicit branch)', function(done) {
|
||||
var store = createNewPersistentStore('nes', function(store) {
|
||||
var gh = newGH(store, 'nes');
|
||||
gh.importAndPull('https://github.com/sehugg/mdf2020-nes/tree/main').then( (sess) => {
|
||||
@@ -72,7 +72,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (binary files)', function(done) {
|
||||
it('Should import from GitHub (binary files)', function(done) {
|
||||
var store = createNewPersistentStore('vcs', function(store) {
|
||||
var gh = newGH(store, 'vcs');
|
||||
gh.importAndPull('https://github.com/pzpinfo/testrepo3').then( (sess) => {
|
||||
@@ -88,7 +88,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (wrong platform)', function(done) {
|
||||
it('Should import from GitHub (wrong platform)', function(done) {
|
||||
var store = createNewPersistentStore('_FOO', function(store) {
|
||||
var gh = newGH(store, '_FOO');
|
||||
gh.importAndPull('https://github.com/pzpinfo/testrepo1557326056720').catch( (e) => {
|
||||
@@ -98,7 +98,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (invalid URL)', function(done) {
|
||||
it('Should import from GitHub (invalid URL)', function(done) {
|
||||
var store = createNewPersistentStore('_FOO', function(store) {
|
||||
var gh = newGH(store, '_FOO');
|
||||
gh.importAndPull('https://github.com/pzpinfo/NOEXISTSREPO').catch( (e) => {
|
||||
@@ -109,7 +109,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should import from Github (subdirectory tree)', function(done) {
|
||||
it('Should import from GitHub (subdirectory tree)', function(done) {
|
||||
var store = createNewPersistentStore('nes', function(store) {
|
||||
var gh = newGH(store, 'nes');
|
||||
gh.importAndPull('https://github.com/brovador/NESnake/tree/master/src').then( (sess) => {
|
||||
@@ -120,7 +120,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should publish (fail) on Github', function(done) {
|
||||
it('Should publish (fail) on GitHub', function(done) {
|
||||
var store = createNewPersistentStore(test_platform_id, function(store) {
|
||||
var gh = newGH(store);
|
||||
// should fail
|
||||
@@ -130,7 +130,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should publish new repository on Github', function(done) {
|
||||
it('Should publish new repository on GitHub', function(done) {
|
||||
var store = createNewPersistentStore(test_platform_id, function(store) {
|
||||
var gh = newGH(store);
|
||||
var reponame = 'testrepo'+t0;
|
||||
@@ -144,7 +144,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should commit/push to Github', function(done) {
|
||||
it('Should commit/push to GitHub', function(done) {
|
||||
var store = createNewPersistentStore(test_platform_id, function(store) {
|
||||
var gh = newGH(store);
|
||||
var binfile = new Uint8Array(256);
|
||||
@@ -164,7 +164,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should commit/push to Github (subdirectory tree)', function(done) {
|
||||
it('Should commit/push to GitHub (subdirectory tree)', function(done) {
|
||||
var store = createNewPersistentStore(test_platform_id, function(store) {
|
||||
var gh = newGH(store);
|
||||
var files = [
|
||||
@@ -182,7 +182,7 @@ describe('Github', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should bind paths to Github', function(done) {
|
||||
it('Should bind paths to GitHub', function(done) {
|
||||
var store = createNewPersistentStore(test_platform_id, function(store) {
|
||||
var gh = newGH(store);
|
||||
var sess = {repopath:'foo/bar', url:'_', platform_id:'vcs',mainPath:'test.c'};
|
||||
|
||||
@@ -82,6 +82,45 @@ describe('test WASI cc7800', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('test WASI dialogc', function () {
|
||||
async function loadDialogc() {
|
||||
let shim = await loadWASM('dialogc');
|
||||
const zipdata = fs.readFileSync(`./src/worker/fs/dialog-fs.zip`);
|
||||
shim.fs.setParent(await unzipWASIFilesystem(zipdata as any, "./"));
|
||||
shim.addPreopenDirectory(".");
|
||||
return shim;
|
||||
}
|
||||
it('dialogc compile', async function () {
|
||||
let shim = await loadDialogc();
|
||||
const src = fs.readFileSync('./presets/zmachine/hello.dg', 'utf8');
|
||||
shim.fs.putFile("./hello.dg", src);
|
||||
shim.setArgs(["dialogc", "-t", "z8", "-o", "hello.z8", "hello.dg", "stdlib.dg"]);
|
||||
let errno = shim.run();
|
||||
assert.strictEqual(shim.fds[2].getBytesAsString(), "");
|
||||
assert.strictEqual(errno, 0);
|
||||
const zfile = shim.fs.getFile("./hello.z8").getBytes();
|
||||
assert.ok(zfile.length > 1000);
|
||||
assert.strictEqual(zfile[0], 8); // z-machine version 8
|
||||
});
|
||||
it('dialogc syntax error', async function () {
|
||||
let shim = await loadDialogc();
|
||||
shim.fs.putFile("./bad.dg", "(room #room)\n(current player #player\n");
|
||||
shim.setArgs(["dialogc", "-t", "z8", "-o", "bad.z8", "bad.dg", "stdlib.dg"]);
|
||||
let errno = shim.run();
|
||||
assert.strictEqual(errno, 1);
|
||||
const stderr = shim.fds[2].getBytesAsString();
|
||||
assert.ok(stderr.indexOf('Error: bad.dg, line ') >= 0, stderr);
|
||||
});
|
||||
it('dialogc missing source file', async function () {
|
||||
let shim = await loadDialogc();
|
||||
shim.setArgs(["dialogc", "-t", "z8", "-o", "x.z8", "nosuch.dg", "stdlib.dg"]);
|
||||
let errno = shim.run();
|
||||
assert.strictEqual(errno, 1);
|
||||
const stderr = shim.fds[2].getBytesAsString();
|
||||
assert.ok(stderr.indexOf('Error: Failed to open "nosuch.dg"') >= 0, stderr);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
describe('test WASI oscar64', function () {
|
||||
it('oscar64 compile', async function () {
|
||||
|
||||
@@ -22,7 +22,7 @@ try {
|
||||
console.log('warning: need ./github.json with {token:"..."}')
|
||||
}
|
||||
|
||||
exports['test import Github'] = async function (browser) {
|
||||
exports['test import GitHub'] = async function (browser) {
|
||||
|
||||
await browser.url(`${IDEURL}?${QS_GITHUBURL}`)
|
||||
.waitForElementNotPresent('#step-0')
|
||||
|
||||
Reference in new issue
Block a user