Created
December 21, 2022 10:10
-
-
Save shanecelis/cdd9097b6120f32e7b3da0a325b024f9 to your computer and use it in GitHub Desktop.
A Befunge + Sokoban mashup for sprig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@title: Befungoban | |
@author: Shane Celis @shanecelis | |
https://esolangs.org/wiki/Befunge | |
http://qiao.github.io/javascript-playground/visual-befunge93-interpreter/ | |
*/ | |
const player = "p"; | |
const selection = "b"; | |
const blockSE = "e"; | |
const blockW = "w"; | |
const blockN = "n"; | |
const blockNW = "N"; | |
class Stack extends Array { | |
// Give us a default value of 0 when we pop with nothing in the stack. | |
pop() { | |
if (this.length == 0) | |
return 0; | |
else | |
return super.pop(); | |
} | |
} | |
/* Keep a set of sprites together as though they're one sprite. */ | |
class CompositeSprite { | |
constructor(sprites) { | |
this.sprites = sprites; | |
} | |
get x() { | |
return this.sprites[0].x; | |
} | |
get y() { | |
return this.sprites[0].y; | |
} | |
set x(newX) { | |
let dx = newX - this.sprites[0].x; | |
for (let i = 0; i < this.sprites.length; i++) { | |
this.sprites[i].x += dx; | |
} | |
} | |
set y(newY) { | |
let dy = newY - this.sprites[0].y; | |
for (let i = 0; i < this.sprites.length; i++) { | |
this.sprites[i].y += dy; | |
} | |
} | |
} | |
class Befunge { | |
constructor(width, height, char = ' ') { | |
this.width = width; | |
this.height = height; | |
// Cells are a 2d array of characters. | |
this.cells = Array(width).fill(null).map(i => Array(height).fill(char)); | |
this.pointer = [0, 0]; | |
this.inertia = [1, 0]; | |
this.stack = new Stack(); // Just integers, please. | |
this.output = ""; | |
this.error = null; | |
this.stringMode = false; | |
} | |
isDigit(c) { | |
return c >= '0' && c <= '9'; | |
} | |
/** Accept a character. */ | |
eval(instruction) { | |
let s = this.stack; | |
if (this.stringMode) { | |
if (instruction == '"') | |
this.stringMode = false; | |
else | |
s.push(instruction.codePointAt()); | |
return true; | |
} | |
if (this.isDigit(instruction)) { | |
s.push(instruction - 0); | |
return true; | |
} | |
switch(instruction) { | |
case ' ': | |
break; | |
case '+': | |
s.push(s.pop() + s.pop()); | |
break; | |
case '-': | |
s.push(s.pop() - s.pop()); | |
break; | |
case '*': | |
s.push(s.pop() * s.pop()); | |
break; | |
case '/': | |
s.push(s.pop() / s.pop()); | |
break; | |
case '%': | |
s.push(s.pop() % s.pop()); | |
break; | |
case '!': | |
s.push(s.pop() == 0 ? 1 : 0); | |
break; | |
case '`': | |
s.push(s.pop() > s.pop() ? 1 : 0); | |
break; | |
case '>': | |
this.inertia = [1, 0]; | |
break; | |
case '<': | |
this.inertia = [-1, 0]; | |
break; | |
case '^': | |
this.inertia = [0, -1]; | |
break; | |
case 'v': | |
this.inertia = [0, 1]; | |
break; | |
case '?': | |
let index = Math.floor(Math.random() * 4); | |
return this.eval(['>', '<', '^', 'v'][index]); | |
break; | |
case '_': | |
if (s.pop() == 0) | |
return this.eval('>'); | |
else | |
return this.eval('<'); | |
break; | |
case '|': | |
if (s.pop() == 0) | |
return this.eval('v'); | |
else | |
return this.eval('^'); | |
break; | |
case '"': | |
this.stringMode = true; | |
break; | |
case ':': | |
let item = s.pop(); | |
s.push(item); | |
s.push(item); | |
break; | |
case '\\': | |
let a = s.pop(); | |
let b = s.pop(); | |
s.push(a); | |
s.push(b); | |
break; | |
case '$': | |
s.pop(); | |
break; | |
case '.': | |
// Output as integer. | |
this.output += s.pop(); | |
break; | |
case ',': | |
// Output as character. | |
this.output += String.fromCodePoint(s.pop()); | |
break; | |
case '#': | |
let pc = this.pointer; | |
pc[0] = (pc[0] + this.inertia[0]) % this.width; | |
pc[1] = (pc[1] + this.inertia[1]) % this.height; | |
break; | |
case 'g': | |
{ | |
let y = s.pop(); | |
let x = s.pop(); | |
if (x < 0 || y < 0 || x >= this.width || y >= this.height) | |
s.push(0); | |
else | |
s.push(this.cells[x][y].codePointAt()); | |
break; | |
} | |
case 'p': | |
let y = s.pop(); | |
let x = s.pop(); | |
let v = s.pop(); | |
this.cells[x][y] = String.fromCodePoint(v)[0]; | |
break; | |
case '&': | |
// Get integer from user and push it. | |
break; | |
case '~': | |
// Get character from user and push it. | |
break; | |
case '@': | |
// End program. | |
this.inertia[0] = 0; | |
this.inertia[1] = 0; | |
break; | |
default: | |
// This code is probably invalid. Ignoring. | |
this.error = "No instruction '" + instruction +"'"; | |
return false; | |
} | |
return true; | |
} | |
step(count) { | |
let pc = this.pointer; | |
for (let i = 0; i < count; i++) { | |
let instruction = this.cells[pc[0]][pc[1]]; | |
if (this.eval(instruction)) { | |
pc[0] = (pc[0] + this.inertia[0]) % this.width; | |
pc[1] = (pc[1] + this.inertia[1]) % this.height; | |
} | |
} | |
} | |
read(input) { | |
for (let i = 0, j = 0, k = 0; k < input.length; k++) { | |
let c = input.charAt(k); | |
if (c == '\n') { | |
j++; | |
i = 0; | |
continue; | |
} | |
if (i >= this.width) { | |
j++; | |
i = 0; | |
} | |
this.cells[i][j] = c; | |
i++; | |
} | |
} | |
draw(x = 0, y = 0) { | |
for (let i = 0; i < this.width; i++) { | |
for (let j = 0; j < this.height; j++) { | |
addText(this.cells[i][j], { x: i + x, y: j + y}); | |
} | |
} | |
} | |
} | |
const TEXT_WIDTH = 20; | |
const TEXT_HEIGHT = 16; | |
function addCenterText(text, y, color = 0) { | |
addText(text, { x: (TEXT_WIDTH - text.length) / 2, y : y, color : color }); | |
} | |
var scenes = {}; | |
var currentScene = null; | |
var block = new CompositeSprite([]); | |
const defaultIntervalFrequency = 100; | |
function currentTick() { | |
currentScene.tick(); | |
} | |
class Scene { | |
constructor(name) { | |
scenes[name] = this; | |
this.intervalFrequency = null; | |
} | |
get tickFrequency() { | |
return this.intervalFrequency; | |
} | |
set tickFrequency(newF) { | |
if (this.intervalFrequency != newF) { | |
if (this.intervalFrequency != null) | |
clearInterval(currentTick); | |
this.intervalFrequency = newF; | |
if (this.intervalFrequency != null) { | |
if (this.intervalFrequency < 10) | |
this.intervalFrequency = 10; | |
setInterval(currentTick, this.intervalFrequency); | |
} | |
} | |
} | |
onInput(char) { | |
} | |
draw() { | |
} | |
tick() { | |
} | |
enter() { | |
this.draw(); | |
} | |
exit() { | |
clearText(); | |
this.tickFrequency = null; | |
} | |
gotoScene(scene) { | |
currentScene.exit(); | |
if (typeof scene == "string") | |
currentScene = scenes[scene]; | |
else | |
currentScene = scene; | |
currentScene.enter(); | |
} | |
} | |
class TitleScene extends Scene { | |
onInput(c) { | |
this.gotoScene("level0"); | |
switch (c) { | |
case 'w': | |
block.y -= 1; | |
break; | |
case 'a': | |
block.x -= 1; | |
break; | |
case 's': | |
block.y += 1; | |
break; | |
case 'd': | |
block.x += 1; | |
break; | |
} | |
} | |
draw() { | |
let y = TEXT_HEIGHT / 2; | |
addCenterText("Befungoban", y - 2); | |
addCenterText("By Shane Celis", y); | |
addCenterText(" @shanecelis", y + 2); | |
} | |
} | |
class BefungeScene extends Scene { | |
constructor(name) { | |
super(name); | |
this.play = true; | |
this.befunge = new Befunge(15, 13, ' '); | |
} | |
tick() { | |
if (this.play) { | |
this.befunge.step(1); | |
this.draw(); | |
} | |
} | |
enter() { | |
super.enter(); | |
this.tickFrequency = defaultIntervalFrequency; | |
} | |
draw() { | |
clearText(); | |
let x = 1; | |
let y = 1; | |
this.befunge.draw(x, y); | |
// Draw the stack. | |
for (let i = 0; i < 5; i++) { | |
addText("stack"[4 - i], { x: this.befunge.width + x, y: this.befunge.height + y - 1 - i }); | |
} | |
for (let i = 0; i < this.befunge.stack.length; i++) { | |
addText(("" + this.befunge.stack[i]).padStart(2, ' '), { x: this.befunge.width + x + 1, y: this.befunge.height + y - 1 - i }); | |
} | |
// Draw the output. | |
if (this.befunge.error == null) { | |
addText("output", { x: x, y: this.befunge.height + y }); | |
addText(this.befunge.output, { x: x, y: this.befunge.height + y + 1 }); | |
} else { | |
this.play = false; | |
addText("error ", { x: x, y: this.befunge.height + y }); | |
addText(this.befunge.error, { x: x, y: this.befunge.height + y + 1 }); | |
} | |
block.x = this.befunge.pointer[0] + x; | |
block.y = this.befunge.pointer[1] + y; | |
} | |
onInput(c) { | |
switch (c) { | |
case 'w': | |
this.tickFrequency = null; | |
break; | |
case 'a': | |
this.tickFrequency += 10; | |
break; | |
case 's': | |
this.tickFrequency = null; | |
break; | |
case 'd': | |
this.tickFrequency -= 10; | |
break; | |
} | |
} | |
} | |
let titleScene = new TitleScene("titleScene"); | |
// let level0 = new Level0(); | |
let level0 = new BefungeScene("level0"); | |
level0.befunge.read(`"!dlroW"v | |
v < | |
>",olleH">:v | |
|,< | |
@`); | |
currentScene = titleScene; | |
setLegend( | |
[ player, bitmap` | |
................ | |
................ | |
.......000...... | |
.......0.0...... | |
......0..0...... | |
......0...0.0... | |
....0003.30.0... | |
....0.0...000... | |
....0.05550..... | |
......0...0..... | |
.....0....0..... | |
.....0...0...... | |
......000....... | |
......0.0....... | |
.....00.00...... | |
................`], | |
[ selection, bitmap` | |
FFFFFFFFFFFFFFFF | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
F99999999999999F | |
FFFFFFFFFFFFFFFF`], | |
[ blockSE, bitmap` | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999 | |
9999999999999999`], | |
[ blockN, bitmap` | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
9999999999999999 | |
9999999999999999`], | |
[ blockW, bitmap` | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99 | |
..............99`], | |
[ blockNW, bitmap` | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
................ | |
..............99 | |
..............99`] | |
); | |
setSolids([]); | |
let level = 0; | |
// 20 x 16 | |
const levels = [ | |
map` | |
.p.................. | |
.................... | |
.................... | |
.................... | |
.................... | |
.................... | |
.................... | |
...........Nn....... | |
...........we....... | |
.................... | |
.................... | |
.................... | |
.................... | |
.................... | |
.................... | |
....................`, | |
]; | |
setMap(levels[level]); | |
block.sprites = [getFirst(blockSE), getFirst(blockN), getFirst(blockW), getFirst(blockNW)]; | |
clearText(); | |
currentScene.enter(); | |
setPushables({ | |
[player]: [] | |
}); | |
onInput("w", () => { | |
currentScene.onInput('w'); | |
}); | |
onInput("a", () => { | |
currentScene.onInput('a'); | |
}); | |
onInput("s", () => { | |
currentScene.onInput('s'); | |
}); | |
onInput("d", () => { | |
currentScene.onInput('d'); | |
}); | |
afterInput(() => { | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment