Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Last active July 20, 2017 18:04
Show Gist options
  • Save Heimdell/fd1c63102b9d4c53a9843f43df10d14c to your computer and use it in GitHub Desktop.
Save Heimdell/fd1c63102b9d4c53a9843f43df10d14c to your computer and use it in GitHub Desktop.
class Utils {
static unsnoc(array) {
return {
init: array.slice(0, -1),
last: array[array.length - 1],
}
}
}
class Event {
static new() {
return new Event()
}
constructor() {
this.subscribers = []
}
subscribe(callback) {
this.subscribers.push(callback)
}
feed(event) {
this.subscribers.forEach(sink => {
sink(event)
})
}
transform(commutator) {
var result = Event.new()
this.subscribe(event => {
setTimeout(() => {
commutator(result.feed.bind(result), event)
}, 0)
})
return result
}
map(mapper) {
return this.transform((feed, event) => {
feed(mapper(event))
})
}
filter(predicate) {
return this.transform((feed, event) => {
if (predicate(event)) {
feed(event)
}
})
}
reduce(acc, add) {
return this.transform((feed, event) => {
feed(acc = add(acc, event))
})
}
delay(ms) {
return this.transform((feed, event) => {
setTimeout(() => feed(event), ms)
})
}
throttle(ms) {
var comesThrough = true;
return this.transform((feed, event) => {
if (comesThrough) {
comesThrough = false
feed(event)
setTimeout(() => {
comesThrough = true
}, ms)
}
})
}
mergeWith(event) {
var result = Event.new()
this .subscribe(event => result.feed(event))
event.subscribe(event => result.feed(event))
return result
}
static merge([event, ...events]) {
return events.reduce((a, b) => a.mergeWith(b), event)
}
static group(group) {
var keyed =
Object.keys(group).map(key =>
group[key].map(x =>
({[key]: x})
)
)
return Event.merge(keyed)
}
behavior() {
return Behavior.of(this)
}
sample(...args) {
const {init: behaviors, last: f} = Utils.unsnoc(args)
return this.map(x => f(x, ...behaviours.map(it => it.now())))
}
compact() {
return this.filter(x => undefined !== x)
}
split(tags) {
var tagged =
tags.map(tag => ({
tag,
event: this.map(event => event[tag]).compact()
}))
return tagged.reduce(
(acc, {tag, event}) =>
Object.assign(acc, {[tag]: event}),
{}
)
}
}
class Behavior {
static of(event) {
return new Behavior(event)
}
constructor(event) {
event.subscribe(value => {
this.value = value
})
}
now() {
return this.value
}
sampleOn(event) {
return event.map(_ => this.now())
}
applyTo(event, f) {
return event.map(x => f(this.now(), x))
}
}
var s = JSON.stringify
var parseLevel = (...lines) => {
var center
outer: for (var y = 0; y < lines.length; y++) {
var line = lines[y]
for (var x = 0; x < line.length; x++) {
if (line[x] === "@") {
center = {x, y}
break outer
}
}
}
var terrain = {doors: {}}
terrain.maxX = 0
terrain.minX = 0
terrain.maxY = 0
terrain.minY = 0
for (var y = 0; y < lines.length; y++) {
var line = lines[y]
for (var x = 0; x < line.length; x++) {
terrain.maxX = Math.max(center.x - x, terrain.maxX)
terrain.minX = Math.min(center.x - x, terrain.minX)
terrain.maxY = Math.max(center.y - y, terrain.maxY)
terrain.minY = Math.min(center.y - y, terrain.minY)
var set = (val) => terrain[s({x: center.x - x, y: center.y - y})] = val
let char = line[x]
switch (char) {
case ".":
case "@":
set({open: true})
break;
case '#':
set({open: false})
break;
case 'X':
set({open: true, onstep: level => level.next()})
break;
default:
if ("abcdef".includes(char)) {
set({
open: true,
keyName: char,
onstep: (level) => level.openDoor(char.toUpperCase())
})
break;
}
if ("ABCDEF".includes(char)) {
terrain.doors[char] = s({x: center.x - x, y: center.y - y})
set({open: false, doorName: char})
break;
}
}
}
}
terrain.openDoor = (name) => {
terrain[terrain.doors[name]].open = ~terrain[terrain.doors[name]].open
return terrain
}
terrain.player = {x: 0, y: 0}
return terrain
}
var move = (terrain, {dx, dy}) => {
let {x, y} = terrain.player
let x1 = x + dx
let y1 = y + dy
let place = terrain[s({x: x1, y: y1})]
if (place && place.open) {
terrain.player = {x: x1, y: y1}
if (terrain[s(terrain.player)].onstep) {
return terrain[s(terrain.player)].onstep(terrain)
}
}
return terrain
}
var show = terrain => {
var acc = []
for (var y = terrain.maxY + 1; y >= terrain.minY - 1; y--) {
for (var x = terrain.maxX + 1; x >= terrain.minX - 1; x--) {
let index = s({x, y})
let place = terrain[index]
var glyph = '#'
if (s(terrain.player) === index) {
glyph = '@'
} else
if (place) {
if (place.open) {
if (place.keyName) {
glyph = place.keyName
} else {
glyph = '.'
}
} else {
if (place.doorName) {
glyph = place.doorName
} else {
glyph = '#'
}
}
}
acc.push(glyph)
}
acc.push('\n')
}
return acc.join("") + "\n" + s(terrain.player)
}
var keyboard = Event.new()
var wasd = keyboard.filter(key => "wasd ".includes(key))
var movements = wasd.map(key => (
{ w: {dx: 0, dy: +1}
, s: {dx: 0, dy: -1}
, a: {dx: +1, dy: 0}
, d: {dx: -1, dy: 0}
, ' ': {dx: 0, dy: 0}
} [key]))
var level = parseLevel(
"#############",
"#.#b......#.#",
"#..#.....#..#",
"#...##A##...#",
"#...#...#...#",
"#[email protected]#",
"#...#a..#...#",
"#...##B##...#",
"#..#.....#..#",
"#.#.....c.#.#",
"#############",
)
var state = movements.reduce(level, move)
var view = state.map(show)
view.subscribe((key) => {
var canvas = document.getElementById('canvas')
canvas.innerText = key
})
document.onkeypress = (event) => {
keyboard.feed(event.key)
}
<body>
<pre id="canvas">
<font caption="inconsolata">
Press 'space' to start.
'WASD' to move.
</font>
</pre>
<script type="text/javascript" src="./frp.js"></script>
<script type="text/javascript" src="./game.js"></script>
<script type="text/javascript" src="./logic.js"></script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment