From 6cc195e44843e3425735e31d602e0410fce5cbc8 Mon Sep 17 00:00:00 2001 From: Maya Karabula-Stysiak Date: Fri, 21 Apr 2023 22:45:15 +0200 Subject: [PATCH] add screen device --- .gitignore | 3 +- devices/screen.ts | 210 ++++++++++++++++++++++++++++++++++++++++++++++ devices/system.ts | 101 ++++++++++++++++++---- index.html | 50 ++++++++++- out.ts | 20 +++++ read-rom.js | 7 ++ sprites.rom | Bin 0 -> 301 bytes sprites.tal | 50 +++++++++++ uxn.ts | 17 ++-- uxncli.ts | 101 ---------------------- uxnemu.ts | 113 +++++++++++++++++++++++++ 11 files changed, 543 insertions(+), 129 deletions(-) create mode 100644 devices/screen.ts create mode 100644 out.ts create mode 100644 read-rom.js create mode 100644 sprites.rom create mode 100644 sprites.tal delete mode 100644 uxncli.ts create mode 100644 uxnemu.ts diff --git a/.gitignore b/.gitignore index b512c09..6fc8fed 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +bundle.js \ No newline at end of file diff --git a/devices/screen.ts b/devices/screen.ts new file mode 100644 index 0000000..09493af --- /dev/null +++ b/devices/screen.ts @@ -0,0 +1,210 @@ +import { PEEK2, POKE2, Uxn } from "../uxn"; + +export const WIDTH = 64 * 8 +export const HEIGHT = 40 * 8 + +let FIXED_SIZE = 0; + +export class Layer { + pixels: number[]; + changed: number = 0; + + constructor (width: number, height: number) { + this.pixels = new Array(width * height).fill(0) + } +}; + +export class UxnScreen { + palette: number[] = new Array(4).fill(0); + pixels: number[] = []; + width: number; + height: number; + fg: Layer; + bg: Layer; + mono: number = 0 + + constructor () { + this.width = WIDTH + this.height = HEIGHT + this.fg = new Layer(WIDTH, HEIGHT); + this.bg = new Layer(WIDTH, HEIGHT); + this.pixels = new Array(WIDTH * HEIGHT).fill(0) + } +}; + +export const uxn_screen = new UxnScreen(); + +const blending: number[][] = [ + [0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0], + [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], + [1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1], + [2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2], +]; + +const palette_mono: number[] = [ + 0x0f000000, 0x0fffffff, +]; + +function screen_write(p: UxnScreen, layer: Layer, x: number, y: number, color: number) +{ + if(x < p.width && y < p.height) { + const i = x + y * p.width; + + if(color != layer.pixels[i]) { + layer.pixels[i] = color; + layer.changed = 1; + } + } +} + +function screen_fill(p: UxnScreen, layer: Layer, x1: number, y1: number, x2: number, y2: number, color: number) +{ + let v: number, h: number; + + for(v = y1; v < y2; v++) + for(h = x1; h < x2; h++) + screen_write(p, layer, h, v, color); + + layer.changed = 1; +} + +function screen_wipe(p: UxnScreen, layer: Layer, x: number, y: number) +{ + screen_fill(p, layer, x, y, x + 8, y + 8, 0); +} + +function screen_blit(p: UxnScreen, layer: Layer, x: number, y: number, sprite: number[], color: number, flipx: number, flipy: number, twobpp: number): void { + let v: number, h: number, opaque = (color % 5) || !color; + + for(v = 0; v < 8; v++) { + let c = sprite[v] | (twobpp ? (sprite[v + 8] << 8) : 0); + + for(h = 7; h >= 0; --h, c >>= 1) { + let ch = (c & 1) | ((c >> 7) & 2); + + if(opaque || ch) { + screen_write(p, layer, x + (flipx ? 7 - h : h), y + (flipy ? 7 - v : v), blending[ch][color]); + } + } + } +} + +export function screen_palette(p: UxnScreen, addr: number[]): void { + for (let i = 0, shift = 4; i < 4; ++i, shift ^= 4) { + const r = (addr[0 + Math.floor(i / 2)] >> shift) & 0x0f; + const g = (addr[2 + Math.floor(i / 2)] >> shift) & 0x0f; + const b = (addr[4 + Math.floor(i / 2)] >> shift) & 0x0f; + + p.palette[i] = 0x000000 | r << 16 | g << 8 | b; + p.palette[i] |= p.palette[i] << 4; + } + + p.fg.changed = p.bg.changed = 1; +} + +function screen_resize(p: UxnScreen, width: number, height: number) +{ + p.bg.pixels = new Array(width * height).fill(0) + p.fg.pixels = new Array(width * height).fill(0) + p.pixels = new Array(width * height).fill(0) + + p.width = width; + p.height = height; + screen_fill(p, p.bg, 0, 0, p.width, p.height, 0); + screen_fill(p, p.fg, 0, 0, p.width, p.height, 0); +} + +function screen_redraw(p: UxnScreen) +{ + let i, size = p.width * p.height, palette = new Array(4).fill(0); + + for(i = 0; i < 16; i++) + palette[i] = p.palette[(i >> 2) ? (i >> 2) : (i & 3)]; + + if(p.mono) { + for(i = 0; i < size; i++) + p.pixels[i] = palette_mono[(p.fg.pixels[i] ? p.fg.pixels[i] : p.bg.pixels[i]) & 0x1]; + } else { + for(i = 0; i < size; i++) + p.pixels[i] = palette[p.fg.pixels[i] << 2 | p.bg.pixels[i]]; + } + + p.fg.changed = p.bg.changed = 0; +} + +function clamp(val: number, min: number, max: number) +{ + return (val >= min) ? (val <= max) ? val : max : min; +} + +export function screen_mono(p: UxnScreen) +{ + p.mono = p.mono ? 0 : 1; + screen_redraw(p); +} + +export function screen_dei(u: Uxn, addr: number) +{ + switch(addr) { + case 0x22: return uxn_screen.width >> 8; + case 0x23: return uxn_screen.width; + case 0x24: return uxn_screen.height >> 8; + case 0x25: return uxn_screen.height; + default: return u.dev[addr]; + } +} + +export function screen_deo(ram: number[], d: number[], port: number) +{ + switch(port) { + case 0x3: + if(!FIXED_SIZE) + screen_resize(uxn_screen, clamp(PEEK2(d.slice(2)), 1, 1024), uxn_screen.height); + break; + case 0x5: + if(!FIXED_SIZE) + screen_resize(uxn_screen, uxn_screen.width, clamp(PEEK2(d.slice(4)), 1, 1024)); + break; + case 0xe: { + let x = PEEK2(d.slice(0x8)), y = PEEK2(d.slice(0xa)); + let layer: Layer = (d[0xf] & 0x40) ? uxn_screen.fg : uxn_screen.bg; + + if(d[0xe] & 0x80) + screen_fill( + uxn_screen, + layer, + (d[0xe] & 0x10) ? 0 : x, (d[0xe] & 0x20) ? 0 : y, (d[0xe] & 0x10) ? x : uxn_screen.width, (d[0xe] & 0x20) ? y : uxn_screen.height, d[0xe] & 0x3) + ; + else { + screen_write(uxn_screen, layer, x, y, d[0xe] & 0x3); + if(d[0x6] & 0x01) POKE2(d, (0x8), x + 1); /* auto x+1 */ + if(d[0x6] & 0x02) POKE2(d, (0xa), y + 1); /* auto y+1 */ + } + break; + } + case 0xf: { + let x = PEEK2(d.slice(0x8)), y = PEEK2(d.slice(0xa)), dx, dy, addr = PEEK2(d.slice(0xc)); + let i, n, twobpp = !!(d[0xf] & 0x80) ? 1 : 0; + let layer: Layer = (d[0xf] & 0x40) ? uxn_screen.fg : uxn_screen.bg; + + n = d[0x6] >> 4; + dx = (d[0x6] & 0x01) << 3; + dy = (d[0x6] & 0x02) << 2; + + if(addr > 0x10000 - ((n + 1) << (3 + twobpp))) + return; + for(i = 0; i <= n; i++) { + if(!(d[0xf] & 0xf)) + screen_wipe(uxn_screen, layer, x + dy * i, y + dx * i); + else { + screen_blit(uxn_screen, layer, x + dy * i, y + dx * i, ram.slice(addr), d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20, twobpp); + addr += (d[0x6] & 0x04) << (1 + twobpp); + } + } + POKE2(d, (0xc), addr); /* auto addr+length */ + POKE2(d, (0x8), x + dx); /* auto x+8 */ + POKE2(d, (0xa), y + dy); /* auto y+8 */ + break; + } + } +} diff --git a/devices/system.ts b/devices/system.ts index b3852fe..d855756 100644 --- a/devices/system.ts +++ b/devices/system.ts @@ -1,10 +1,54 @@ -import { PAGE_PROGRAM , Uxn} from '../uxn' -import { RAM_PAGES } from '../uxncli' -// import { readFileSync } from 'fs'; +import { out, outError } from '../out'; +import { PAGE_PROGRAM, PEEK2, Stack, Uxn, uxn_eval} from '../uxn' +import { RAM_PAGES } from '../uxnemu' -export function system_load(u: Uxn, filename: string): number { - //const f = readFileSync(filename); - const f = [128, 104, 128, 24, 23, 128, 101, 128, 24, 23, 128, 108, 128, 24, 23, 128, 108, 128, 24, 23, 128, 111, 128, 24, 23, 128, 10, 128, 24, 23]; +const errors: string[] = [ + "underflow", + "overflow", + "division by zero" +]; + +function system_print(s: Stack, name: string): void { + out(`<${name}>`); + + for (let i = 0; i < s.ptr; i++) { + out(` ${s.dat[i].toString(16).padStart(2, '0')}`); + } + if (s.ptr === 0) { + out(' empty'); + } + out('\n'); +} + +function system_cmd(ram: number[], addr: number) +{ + if(ram[addr] == 0x01) { + let i, length = PEEK2(ram.slice(addr + 1)); + const a_page = PEEK2(ram.slice(addr + 1 + 2)); + const a_addr = PEEK2(ram.slice(addr + 1 + 4)); + const b_page = PEEK2(ram.slice(addr + 1 + 6)); + const b_addr = PEEK2(ram.slice(addr + 1 + 8)); + const src = (a_page % RAM_PAGES) * 0x10000; + const dst = (b_page % RAM_PAGES) * 0x10000; + + for(i = 0; i < length; i++) + ram[dst + (b_addr + i)] = ram[src + (a_addr + i)]; + } +} + +function system_inspect(u: Uxn) +{ + system_print(u.wst, "wst"); + system_print(u.rst, "rst"); +} + +// const HelloWorldRom = [128, 104, 128, 24, 23, 128, 101, 128, 24, 23, 128, 108, 128, 24, 23, 128, 108, 128, 24, 23, 128, 111, 128, 24, 23, 128, 10, 128, 24, 23]; +const HelloWorldRom = [ + 160,1,61,128,32,55,160,240,127,128,8,55,160,240,224,128,10,55,160,240,192,128,12,55,160,1,0,128,34,55,160,0,176,128,36,55,128,34,54,128,1,63,128,0,49,128,36,54,128,1,63,128,2,49,96,1,119,96,0,87,0,160,0,0,161,128,250,51,128,1,63,160,0,31,60,160,0,64,57,38,128,40,55,128,42,55,160,3,35,96,0,252,128,0,48,160,0,112,57,128,40,55,128,2,48,160,0,72,57,128,42,55,160,3,195,128,200,50,160,0,255,60,128,2,31,128,7,28,128,64,31,56,128,44,55,128,143,128,47,23,96,1,33,96,0,1,0,128,0,48,160,0,96,57,128,40,55,128,2,48,160,0,72,57,128,42,55,160,3,67,128,44,55,128,246,128,38,23,128,5,128,47,23,128,0,48,160,0,112,57,128,40,55,128,2,48,160,0,56,57,128,42,55,160,3,67,128,44,55,128,245,128,38,23,128,5,128,47,23,128,0,128,38,23,160,3,51,128,44,55,128,0,128,0,7,128,15,28,128,48,31,128,0,48,160,0,96,57,56,128,40,55,128,0,7,128,240,28,128,1,31,128,2,48,160,0,56,57,56,128,42,55,6,128,47,23,1,6,32,255,207,2,108,24,15,160,3,51,128,44,55,128,42,55,128,40,55,128,1,128,38,23,79,128,47,151,128,2,128,38,23,4,128,16,24,4,151,128,40,182,160,0,8,57,5,55,128,1,128,38,23,4,128,16,24,4,151,4,128,16,24,4,23,108,128,44,55,160,1,38,23,128,40,54,128,61,51,128,34,54,128,3,63,3,128,8,24,128,18,19,128,36,54,128,3,63,3,128,8,24,128,0,6,128,2,31,15,128,0,128,0,6,128,2,31,207,24,128,1,28,128,47,23,1,138,32,255,239,128,42,182,160,0,8,56,5,55,160,0,0,128,40,55,34,66,1,138,32,255,208,34,108,128,1,128,38,23,128,0,48,160,0,48,56,128,40,55,128,2,48,160,0,72,57,128,42,55,128,34,54,96,0,14,160,3,43,128,44,55,128,1,128,47,23,128,36,54,128,0,128,27,19,160,39,16,96,0,51,160,3,232,96,0,45,160,0,100,96,0,39,160,0,10,96,0,33,3,6,128,0,8,32,0,23,128,255,128,246,19,128,0,7,128,48,31,160,3,67,56,128,44,55,128,1,128,47,23,2,108,187,6,128,219,14,58,57,108,170,85,170,85,170,85,170,85,0,0,0,24,24,0,0,0,15,56,103,95,223,191,191,191,0,7,24,32,35,68,72,72,0,124,130,130,130,130,130,124,0,48,16,16,16,16,16,16,0,124,130,2,124,128,128,254,0,124,130,2,28,2,130,124,0,12,20,36,68,132,254,4,0,254,128,128,124,2,130,124,0,124,130,128,252,130,130,124,0,124,130,2,30,2,2,2,0,124,130,130,124,130,130,124,0,124,130,130,126,2,130,124,0,124,130,2,126,130,130,126,0,252,130,130,252,130,130,252,0,124,130,128,128,128,130,124,0,252,130,130,130,130,130,252,0,124,130,128,240,128,130,124,0,124,130,128,240,128,128,128,0,0,0,24,24,0,0,0,195,129,0,0,0,0,129,195,0,0,24,60,60,24,0,0,0,0,0,0,0,0,0,0,0,24,60,126,126,60,24,0,0,0,0,0,0,0,0,0,60,126,255,231,231,255,126,60,0,0,0,24,24,0,0,0,255,255,231,195,195,231,255,255,0,0,24,60,60,24,0,0,255,231,195,129,129,195,231,255,0,24,60,126,126,60,24,0,195,129,0,0,0,0,129,195,60,126,255,231,231,255,126,60,0,0,0,0,0,0,0,0,255,255,231,195,195,231,255,255,0,0,0,0,0,0,0,0,255,231,195,129,129,195,231,255 +] + +export function system_load(u: Uxn): number { + const f = (window as any).rom || HelloWorldRom; if (!f) { return 0; @@ -16,16 +60,41 @@ export function system_load(u: Uxn, filename: string): number { u.ram[PAGE_PROGRAM + i] = (f.at(i) || 0) } -// let data = new Uint8Array(); -// let l = f.copy(data, PAGE_PROGRAM, 0, 0x10000 - PAGE_PROGRAM); + return 1; +} -// console.log(f) - -// let i = 0; - -// while (l && ++i < RAM_PAGES) { -// l = f.copy(data, 0x10000 * i, 0, 0x10000); -// } +/* IO */ - return 1; +export function system_deo(u: Uxn, d: number[], port: number) +{ + switch(port) { + case 0x3: + system_cmd(u.ram, PEEK2(d.slice(2))); + break; + case 0xe: + system_inspect(u); + break; + } } + +/* Error */ + +export function uxn_halt(u: Uxn, instr: number, err: number, addr: number) +{ + const d = u.dev.slice(0x00); + const handler = PEEK2(d); + + if(handler) { + u.wst.ptr = 4; + u.wst.dat[0] = addr >> 0x8; + u.wst.dat[1] = addr & 0xff; + u.wst.dat[2] = instr; + u.wst.dat[3] = err; + + return uxn_eval(u, handler); + } else { + system_inspect(u); + outError(`${(instr & 0x40) ? "Return-stack" : "Working-stack"} ${errors[err - 1]}, by ${instr.toString(16).padStart(2, '0')} at 0x${addr.toString(16).padStart(4, '0')}.\n`); + } + return 0; +} \ No newline at end of file diff --git a/index.html b/index.html index 01be490..5691722 100644 --- a/index.html +++ b/index.html @@ -5,11 +5,57 @@ requirejs.config({ baseUrl: '', paths: { - 'uxncli': 'bundle' + 'uxnemu': 'bundle' } }); // Load the entry module - requirejs(['uxncli']); + requirejs(['uxnemu']); + + +
+
UXN ts
+ stdout: +

+      stderr:
+      

+    
+ + + + + + \ No newline at end of file diff --git a/out.ts b/out.ts new file mode 100644 index 0000000..6bedece --- /dev/null +++ b/out.ts @@ -0,0 +1,20 @@ +let output = '' +let error = '' + +export const out = (c: string) => { + output += c; + + const element = document.getElementById('stdout'); + if (element) { + element.textContent = output; + } +} + +export const outError = (c: string) => { + error += c; + + const element = document.getElementById('stderr'); + if (element) { + element.textContent = error; + } +} \ No newline at end of file diff --git a/read-rom.js b/read-rom.js new file mode 100644 index 0000000..63a95f2 --- /dev/null +++ b/read-rom.js @@ -0,0 +1,7 @@ +const { readFileSync, writeFileSync } = require("fs") + +const file = (process.argv[2]) + +const rom = [...readFileSync(file)] + +writeFileSync('output', rom.join(',')) \ No newline at end of file diff --git a/sprites.rom b/sprites.rom new file mode 100644 index 0000000000000000000000000000000000000000..0440573df4ab287d4bf3b6743509459a82cbaeef GIT binary patch literal 301 zcmZ3W^Rj`%d;#Nu1}^gjI!_yT%oi|lG-!ZHE%OCTvJE=s4Gj(Y;td*R3mAATfKm;O zsN5!0ZZn9h1yKw%0c=`B3#yt{RBjt8w;h`a9jIzLQMp~H+-__p^q{KgMdkLPa{GZ? T2FCj8;$-Lg{|q%Cz`y_iuZ>!x literal 0 HcmV?d00001 diff --git a/sprites.tal b/sprites.tal new file mode 100644 index 0000000..2b90b47 --- /dev/null +++ b/sprites.tal @@ -0,0 +1,50 @@ +( hello-2bpp-sprite.tal ) + +( devices ) +|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ] +|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ] + +( macros ) +%INIT-X { #0008 .Screen/x DEO2 } ( -- ) +%INIT-Y { #0008 .Screen/y DEO2 } ( -- ) +%cADD-X { .Screen/x DEI2 #000c ADD2 .Screen/x DEO2 } ( -- ) +%cADD-Y { .Screen/y DEI2 #000c ADD2 .Screen/y DEO2 } ( -- ) + +( main program ) +|0100 + ( set system colors ) + #2ce9 .System/r DEO2 + #01c0 .System/g DEO2 + #2ce5 .System/b DEO2 + + ( set initial x,y coordinates ) + INIT-X INIT-Y + ( set sprite address ) + ;new-square .Screen/addr DEO2 + + #80 .Screen/sprite DEO cADD-X + #81 .Screen/sprite DEO cADD-X + #82 .Screen/sprite DEO cADD-X + #83 .Screen/sprite DEO cADD-Y + + INIT-X + #84 .Screen/sprite DEO cADD-X + #85 .Screen/sprite DEO cADD-X + #86 .Screen/sprite DEO cADD-X + #87 .Screen/sprite DEO cADD-Y + + INIT-X + #88 .Screen/sprite DEO cADD-X + #89 .Screen/sprite DEO cADD-X + #8a .Screen/sprite DEO cADD-X + #8b .Screen/sprite DEO cADD-Y + + INIT-X + #8c .Screen/sprite DEO cADD-X + #8d .Screen/sprite DEO cADD-X + #8e .Screen/sprite DEO cADD-X + #8f .Screen/sprite DEO + +BRK + +@new-square 017f 7b73 6343 7fff 007c 7c7c 7c7c 0000 \ No newline at end of file diff --git a/uxn.ts b/uxn.ts index d554e4b..a4a4dc2 100644 --- a/uxn.ts +++ b/uxn.ts @@ -1,10 +1,11 @@ -import { deo_mask, dei_mask, uxn_deo, uxn_dei } from './uxncli' +import { uxn_halt } from './devices/system'; +import { deo_mask, dei_mask, uxn_deo, uxn_dei } from './uxnemu' export const PAGE_PROGRAM = 0x0100 -export function POKE2(d: number[], v: number) { - d[0] = (v >> 8); - d[1] = (v); +export function POKE2(d: number[], addr: number, v: number) { + d[addr] = (v >> 8); + d[addr + 1] = (v); } export function PEEK2(d: number[]): number { @@ -39,8 +40,6 @@ export class Uxn { } }; -declare function uxn_halt(u: Uxn, ins: number, err: number, addr: number): number; - export function uxn_eval(u: Uxn, pc: number): number { let ins: number, m2: number, opc: number, k: number; let t: number, n: number, l: number, tmp: number; @@ -157,15 +156,15 @@ export function uxn_eval(u: Uxn, pc: number): number { case 0x10: /* LDZ */ t = T(); SET(1, 0); PUT(0, u.ram[t]); break; case 0x30: t = T(); SET(1, 1); PUT2(0, PEEK2(u.ram.slice(t))); break; case 0x11: /* STZ */ t = T(); n = N(); SET(2,-2); u.ram[t] = (n); break; - case 0x31: t = T(); n = H2(); SET(3,-3); POKE2(u.ram.slice(t), n); break; + case 0x31: t = T(); n = H2(); SET(3,-3); POKE2(u.ram, t, n); break; case 0x12: /* LDR */ t = T(); SET(1, 0); PUT(0, u.ram[pc + t]); break; case 0x32: t = T(); SET(1, 1); PUT2(0, PEEK2(u.ram.slice(pc + t))); break; case 0x13: /* STR */ t = T(); n = N(); SET(2,-2); u.ram[pc + t] = (n); break; - case 0x33: t = T(); n = H2(); SET(3,-3); POKE2(u.ram.slice(pc + t), n); break; + case 0x33: t = T(); n = H2(); SET(3,-3); POKE2(u.ram, (pc + t), n); break; case 0x14: /* LDA */ t = T2(); SET(2,-1); PUT(0, u.ram[t]); break; case 0x34: t = T2(); SET(2, 0); PUT2(0, PEEK2(u.ram.slice(t))); break; case 0x15: /* STA */ t = T2(); n = L(); SET(3,-3); u.ram[t] = (n); break; - case 0x35: t = T2(); n = N2(); SET(4,-4); POKE2(u.ram.slice(t), n); break; + case 0x35: t = T2(); n = N2(); SET(4,-4); POKE2(u.ram, (t), n); break; case 0x16: /* DEI */ t = T(); SET(1, 0); DEI(0, t); break; case 0x36: t = T(); SET(1, 1); DEI(1, t); DEI(0, t + 1); break; case 0x17: /* DEO */ t = T(); n = N(); SET(2,-2); DEO(t, n); break; diff --git a/uxncli.ts b/uxncli.ts deleted file mode 100644 index f4cb4ed..0000000 --- a/uxncli.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { system_load } from "./devices/system"; -import { Uxn, PEEK2, uxn_eval, uxn_boot, PAGE_PROGRAM } from './uxn' - -export const RAM_PAGES = 0x10 - -export const deo_mask: number[] = ([0x6a08, 0x0300, 0xc028, 0x8000, 0x8000, 0x8000, 0x8000, 0x0000, 0x0000, 0x0000, 0xa260, 0xa260, 0x0000, 0x0000, 0x0000, 0x0000]) -export const dei_mask: number[] = ([0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x07ff, 0x0000, 0x0000, 0x0000]) - -function emu_error(msg: string, err: string): number { - console.log(`Error ${msg}: ${err}`); - return 1; -} - -// function console_input(u: Uxn, c: number): number { -// const d = u.dev[0x10]; -// d[0x02] = new Uint8(c); -// return uxn_eval(u, PEEK2(u.dev.slice(0x10))); -// } - -function console_deo(d: number[], port: number): void { - switch (port) { - case 0x8: - console.log(String.fromCharCode(d[port])); - return; - case 0x9: - console.error('error !!', d[port]); - return; - } -} - -export function uxn_dei(u: Uxn, addr: number): number { - switch (addr & 0xf0) { - //case 0xc0: - // return datetime_dei(u, addr); - default: - return u.dev[addr]; - } -} - -export function uxn_deo(u: Uxn, addr: number): void { - const p = addr & 0x0f; - const d = addr & 0xf0; - - switch (d) { - case 0x00: - // system_deo(u, u.dev[d], p); - break; - case 0x10: - console_deo(u.dev.slice(d), p); - break; - case 0xa0: - // file_deo(0, u.ram, u.dev[d], p); - break; - case 0xb0: - // file_deo(1, u.ram, u.dev[d], p); - break; - } -} - -function main(argc: number, argv: string[]): number { - const u = new Uxn(uxn_dei, uxn_deo); - - let i: number; - - if (argc < 2) { - return emu_error("Usage", "uxncli game.rom args"); - } - - if (!uxn_boot(u, (new Array(0x10000 * RAM_PAGES).fill(0)))) { - return emu_error("Boot", "Failed"); - } - - if (!system_load(u, argv[2])) { - return emu_error("Load", "Failed"); - } - - if (!uxn_eval(u, (PAGE_PROGRAM))) { - return u.dev[0x0f] & 0x7f; - } - -// for (i = 2; i < argc; i++) { -// const p = argv[i]; -// for (let j = 0; j < p.length; j++) { -// console_input(u, p[j]); -// } -// console_input(u, '\n'); -// } - -// while (!u.dev[0x0f]) { -// const c = fgetc(stdin); -// if (c !== EOF) { -// console_input(u, c); -// } -// } - -return 0 - // return u.dev[0x0f].val & 0x7f; -} - -// main(process.argv.length, process.argv) -main(3,[]) diff --git a/uxnemu.ts b/uxnemu.ts new file mode 100644 index 0000000..2c96c52 --- /dev/null +++ b/uxnemu.ts @@ -0,0 +1,113 @@ +import { HEIGHT, screen_dei, screen_deo, screen_palette, uxn_screen, WIDTH } from "./devices/screen"; +import { system_deo, system_load } from "./devices/system"; +import { out, outError } from "./out"; +import { Uxn, PEEK2, uxn_eval, uxn_boot, PAGE_PROGRAM } from './uxn' + +export const RAM_PAGES = 0x10 +export const PAD = 4 +export const TIMEOUT_MS = 334 +export const BENCH = 0 + +export let zoom = 1; +export let stdin_event: number, audio0_event: number; +export let exec_deadline: number, deadline_interval: number, ms_interval: number; + +export const deo_mask: number[] = [0xff08, 0x0300, 0xc028, 0x8000, 0x8000, 0x8000, 0x8000, 0x0000, 0x0000, 0x0000, 0xa260, 0xa260, 0x0000, 0x0000, 0x0000, 0x0000] +export const dei_mask: number[] = [0x0000, 0x0000, 0x003c, 0x0014, 0x0014, 0x0014, 0x0014, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x07ff, 0x0000, 0x0000, 0x0000] + +function emu_error(msg: string, err: string): number { + outError(`Error ${msg}: ${err}`); + return 1; +} + +function console_deo(d: number[], port: number): void { + switch (port) { + case 0x8: + out(String.fromCharCode(d[port])); + return; + case 0x9: + outError(String.fromCharCode(d[port])); + return; + } +} + +export function uxn_dei(u: Uxn, addr: number): number { + const p = addr & 0x0f, d = addr & 0xf0; + + switch(d) { + case 0x20: return screen_dei(u, addr); + // case 0x30: return audio_dei(0, u.dev.slice(d), p); + // case 0x40: return audio_dei(1, u.dev.slice(d), p); + // case 0x50: return audio_dei(2, u.dev.slice(d), p); + // case 0x60: return audio_dei(3, u.dev.slice(d), p); + // case 0xc0: return datetime_dei(u, addr); + } + return u.dev[addr]; +} + +export function uxn_deo(u: Uxn, addr: number): void { + const p = addr & 0x0f, d = addr & 0xf0; + + switch(d) { + case 0x00: + system_deo(u, u.dev.slice(d), p); + + if(p > 0x7 && p < 0xe) + screen_palette(uxn_screen, u.dev.slice(0x8)); + break; + case 0x10: console_deo(u.dev.slice(d), p); break; + case 0x20: screen_deo(u.ram, u.dev.slice(d), p); break; + // case 0x30: audio_deo(0, u.dev.slice(d), p, u); break; + // case 0x40: audio_deo(1, u.dev.slice(d), p, u); break; + // case 0x50: audio_deo(2, u.dev.slice(d), p, u); break; + // case 0x60: audio_deo(3, u.dev.slice(d), p, u); break; + // case 0xa0: file_deo(0, u.ram, u.dev.slice(d), p); break; + // case 0xb0: file_deo(1, u.ram, u.dev.slice(d), p); break; + } +} + +function draw () { + const canvas = document.getElementById('canvas') as HTMLCanvasElement + + if (canvas) { + const ctx = canvas.getContext('2d') + + if (ctx) { + for (let x = 0; x < WIDTH; x++) { + for (let y = 0; y < HEIGHT; y++) { + if (uxn_screen.bg.pixels[x + y * WIDTH]) { + ctx.fillStyle = `#${uxn_screen.palette[uxn_screen.bg.pixels[x + y * WIDTH]].toString(16)}`; + ctx.fillRect(x, y, 1, 1); + } + + if (uxn_screen.fg.pixels[x + y * WIDTH]) { + ctx.fillStyle = `#${uxn_screen.palette[uxn_screen.fg.pixels[x + y * WIDTH]].toString(16)}`; + ctx.fillRect(x, y, 1, 1); + } + } + } + } + } +} + +function main(): number { + const u = new Uxn(uxn_dei, uxn_deo); + + if (!uxn_boot(u, (new Array(0x10000 * RAM_PAGES).fill(0)))) { + return emu_error("Boot", "Failed"); + } + + if (!system_load(u)) { + return emu_error("Load", "Failed"); + } + + if (!uxn_eval(u, (PAGE_PROGRAM))) { + return u.dev[0x0f] & 0x7f; + } + + draw() + + return 0 +} + +main()