Created
July 9, 2020 18:05
-
-
Save jm42/5a02358c187e023a2414d1b0ab29e065 to your computer and use it in GitHub Desktop.
canvas-library
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
class Sprite { | |
constructor(rect) { | |
this._visible = true | |
this.dirty = 1 | |
this.rect = rect | |
} | |
get visible() { | |
return this._visible | |
} | |
set visible(value) { | |
this._visible = value ? true : false | |
if (this.dirty < 2) | |
this.dirty = 1 | |
} | |
render(ctx, rect) {} | |
} | |
class Rect extends Sprite { | |
constructor(rect, color, border) { | |
super(rect) | |
this.color = color | |
this.border = border | |
} | |
render(ctx, rect) { | |
if (this.color) { | |
ctx.fillStyle = this.color | |
ctx.fillRect(this.rect.x, this.rect.y, this.rect.w, this.rect.h) | |
} | |
if (this.color) { | |
ctx.strokeStyle = this.border | |
ctx.strokeRect(this.rect.x, this.rect.y, this.rect.w, this.rect.h) | |
} | |
} | |
} |
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
<title>SAN: Artificial World</title> | |
<style> | |
* { margin: 0; padding: 0; } | |
</style> | |
<script src="src/logging.js" defer></script> | |
<script src="src/math.js" defer></script> | |
<script src="src/display.js" defer></script> | |
<script src="src/play.js" defer></script> | |
<script src="src/world.js" defer></script> | |
<script src="src/main.js" defer></script> | |
</head> | |
<body> | |
<canvas id="san" width="240" height="320"></canvas> | |
</body> | |
</html> |
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
/** Simple in-memory logger that formats the message immediately. */ | |
class Logger { | |
constructor() { | |
this.messages = [] | |
} | |
log(level, message, context) { | |
let json = context ? JSON.stringify(context) : '{}' | |
let type = levelToString(level).toUpperCase() | |
this.messages.push(`[${type}] ${message} ${json}`) | |
} | |
} | |
const LOGGING_LEVELS = ['ERROR', 'WARNING', 'NOTICE', 'DEBUG'] | |
// add constants/helpers to Logger class | |
LOGGING_LEVELS.forEach(function(name, level) { | |
Logger[name.toUpperCase()] = level | |
Logger.prototype[name.toLowerCase()] = function(message, context) { | |
this.log(level, message, context) | |
} | |
}) | |
/** Returns given if correct or lookup in available by priority. */ | |
function levelToString(level) { | |
// match by name | |
if (typeof level === 'string' | |
&& LOGGING_LEVELS.indexOf(level.toUpperCase()) !== -1) | |
return level | |
// match by level | |
if (typeof level === 'number' && level in LOGGING_LEVELS) | |
return LOGGING_LEVELS[level] | |
return 'UNKNOWN' | |
} |
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
(function() { | |
let DEBUG = true | |
let logger = new Logger | |
let config = { | |
transparent: false, | |
} | |
function main() { | |
let director = new Director([ | |
BackgroundColor, | |
TestScene, | |
]) | |
director.push('BackgroundColor', {}) | |
director.push('TestScene', {}) | |
let canvas = document.getElementById('san') | |
let ctx = canvas.getContext('2d', { alpha: config.transparent }) | |
let state = {} | |
requestAnimationFrame(function(timestamp) { | |
if (timestamp) { | |
if (!state.startstamp) | |
state.startstamp = timestamp | |
state.timestamp = timestamp | |
} | |
state = director.update(state) | |
ctx.save() | |
ctx.setTransform(1, 0, 0, 1, 0, 0) | |
ctx.globalAlpha = 1 | |
director.render(ctx, {x:0, y:0, w:canvas.width, h:canvas.height}) | |
ctx.restore() | |
}) | |
} | |
function quit() { | |
if (DEBUG) { | |
let status | |
status = document.createElement('pre') | |
status.id = 'status' | |
logger.messages.forEach(function(msg) { | |
status.innerHTML += msg + "\n" | |
}); | |
document.body.appendChild(status) | |
document.getElementById('san').style.display = 'none' | |
} | |
} | |
if (DEBUG) { | |
window.addEventListener('error', function(event) { | |
let filename = event.filename.split('/').pop() | |
logger.log(Logger.DEBUG, `${event.message} (${filename}:${event.lineno})`) | |
quit() | |
}); | |
} | |
document.addEventListener('readystatechange', function(event) { | |
if (event.target.readyState === 'complete') { | |
main() | |
} | |
}) | |
})() |
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
/** Returns rect relative to parent. */ | |
function fit(rect, parent) { | |
let x = rect.x + parent.x | |
let y = rect.y + parent.y | |
let w = x + rect.w > parent.w ? parent.w - x : rect.w | |
let h = y + rect.h > parent.h ? parent.h - y : rect.h | |
return {x, y, w, h} | |
} |
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
/** Orchestrate stacked scenes to */ | |
class Director { | |
constructor(scenes) { | |
this._scenes = {} // registered constructors | |
this._running = [] // scenes stack, current is index zero | |
this._queue = [] // to be pushed after scene is removed | |
this._resume = [] // to call resume on first update | |
scenes.forEach((scene) => this.register(scene)) | |
} | |
get current() { | |
if (this._running.length > 0) { | |
return this._running[0] | |
} | |
return null | |
} | |
get active() { | |
return this._running.slice() | |
} | |
register(scene) { | |
this._scenes[scene.name] = scene | |
} | |
available(sceneName) { | |
return sceneName in this._scenes | |
} | |
queue(sceneName, state) { | |
this._queue.push([sceneName, state]) | |
} | |
remove(scene, state, pause) { | |
let index = this._running.indexOf(scene) | |
if (index === -1) | |
throw new ReferenceError(`Scene ${scene.name} not running`) | |
if (index === 0 && this._queue.length > 0) { | |
let data = this._queue.shift() | |
this.replace(data[0], data[1]) | |
return | |
} | |
if (pause) | |
scene.pause(state) | |
this._running.splice(index, 1) | |
scene.shutdown(state) | |
if (index === 0 && this._running.length > 0) | |
self.current.resume(state) | |
else if (this._running.length === 0) | |
quit() | |
} | |
pop(state) { | |
let previous = this.current | |
this.remove(previous, state) | |
return previous | |
} | |
push(sceneName, state) { | |
if (!this.available(sceneName)) | |
throw new ReferenceError(`Scene ${sceneName} missing`) | |
let scene = this._scenes[sceneName] | |
let previous = this.current | |
if (previous) | |
previous.pause(state) | |
let instance = new scene(this) | |
this._running.unshift(instance) | |
instance.startup(state) | |
this._resume.push(instance) | |
return instance | |
} | |
replace(sceneName, state) { | |
let previous = this.current | |
let instance = this.push(sceneName, state) | |
if (previous) | |
this.remove(previous, state) | |
return instance | |
} | |
update(state) { | |
if (this.current === null) | |
quit() | |
else if (this.current in this._resume) { | |
let index = this._resume.indexOf(this.current) | |
this._resume.splice(index, 1) | |
this.current.resume(state) | |
} | |
return this.active.reduce((state, scene) => scene.update(state), state) | |
} | |
render(ctx, rect) { | |
this.active.reverse().forEach((scene) => scene.render(ctx, rect)) | |
} | |
} | |
class Scene { | |
constructor(director) { | |
this._director = director | |
this._children = [] // active sprites | |
this._removed = [] // waiting to be clean | |
this.state = {} | |
} | |
get name() { | |
return this.constructor.name | |
} | |
add(sprite) { | |
if (sprite.dirty < 2) | |
sprite.dirty = 1 | |
this._children.push(sprite) | |
} | |
remove(sprite) { | |
let index = this._children.indexOf(sprite) | |
if (index === -1) | |
throw new ReferenceError(`${sprite.constructor.name} not found`) | |
this._children.splice(index, 1) | |
this._removed.push(sprite) | |
} | |
has(sprite) { | |
return this._children.indexOf(sprite) !== -1 | |
} | |
/** Called when added to the state stack. */ | |
startup(state) {} | |
/** Called each time state is updated for first time. */ | |
resume(state) {} | |
/** Called each frame while state is active. Returns modified state. */ | |
update(state) { | |
return state | |
} | |
/** Called when there is a new input event. */ | |
process(event, state) {} | |
/** Called when state is no longer active. */ | |
pause(state) {} | |
/** Called before state is destroyed. */ | |
shutdown(state) {} | |
/** Called each frame with context. Returns list of updated rects. */ | |
render(ctx, rect) { | |
let updated = [] | |
this._children.forEach((sprite) => { | |
if (sprite.dirty > 0) { | |
if (sprite.visible) { | |
let r = fit(sprite.rect, rect) | |
ctx.save() | |
sprite.render(ctx, r) | |
updated.push(r) | |
ctx.restore() | |
} | |
if (sprite.dirty === 1) | |
sprite.dirty = 0 | |
} | |
}) | |
this._removed.forEach((sprite) => updated.push(fit(sprite.rect, rect))) | |
this._removed = [] | |
return updated | |
} | |
} | |
class BackgroundColor extends Scene { | |
startup(state) { | |
if (state.backgroundColor) | |
this.color = state.backgroundColor | |
else | |
this.color = 'white' | |
} | |
render(ctx, rect) { | |
ctx.fillStyle = this.color | |
ctx.fillRect(rect.x, rect.y, rect.w, rect.h) | |
} | |
} | |
class TestScene extends Scene { | |
startup(state) { | |
this.add(new Rect({x:16, y:16, w:16, h:16}, 'blue', 'red')) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment