forked from cory/tildefriends
937 lines
24 KiB
JavaScript
937 lines
24 KiB
JavaScript
|
imports.terminal.print({iframe: `
|
||
|
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>become a game developer in 60 seconds</title>
|
||
|
<script language="javascript">
|
||
|
"use strict";
|
||
|
|
||
|
function ImageEditor() {
|
||
|
this.kWidth = 256;
|
||
|
this.kHeight = 256;
|
||
|
this.kTop = 30;
|
||
|
this.kLeft = 640 / 2 - this.kWidth / 2;
|
||
|
this.kSpriteSize = 8;
|
||
|
this.image = new Array(this.kSpriteSize * this.kSpriteSize);
|
||
|
for (var i = 0; i < this.kSpriteSize * this.kSpriteSize; i++) {
|
||
|
this.image[i] = 0;
|
||
|
}
|
||
|
this.pixelsFilled = 0;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
ImageEditor.prototype.update = function() {
|
||
|
if (this.title) {
|
||
|
var fontSize = 16;
|
||
|
gContext.font = 'bold ' + fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText(this.title, 640 / 2 - gContext.measureText(this.title).width / 2, fontSize);
|
||
|
}
|
||
|
for (var i = 0; i < this.kSpriteSize; i++) {
|
||
|
for (var j = 0; j < this.kSpriteSize; j++) {
|
||
|
var p = j * this.kSpriteSize + i;
|
||
|
if (this.pixelsFilled > p) {
|
||
|
gContext.fillStyle = this.image[p] ? '#fff' : '#000';
|
||
|
gContext.fillRect(
|
||
|
this.kLeft + (i * this.kWidth / this.kSpriteSize),
|
||
|
this.kTop + (j * this.kHeight / this.kSpriteSize),
|
||
|
this.kWidth / this.kSpriteSize,
|
||
|
this.kHeight / this.kSpriteSize);
|
||
|
} else if (this.pixelsFilled == p) {
|
||
|
gContext.fillStyle = pulseColor();
|
||
|
gContext.fillRect(
|
||
|
this.kLeft + (i * this.kWidth / this.kSpriteSize),
|
||
|
this.kTop + (j * this.kHeight / this.kSpriteSize),
|
||
|
this.kWidth / this.kSpriteSize,
|
||
|
this.kHeight / this.kSpriteSize);
|
||
|
} else {
|
||
|
gContext.strokeStyle = '#fff';
|
||
|
gContext.strokeRect(
|
||
|
this.kLeft + (i * this.kWidth / this.kSpriteSize),
|
||
|
this.kTop + (j * this.kHeight / this.kSpriteSize),
|
||
|
this.kWidth / this.kSpriteSize,
|
||
|
this.kHeight / this.kSpriteSize);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.suggested) {
|
||
|
var fontSize = 16;
|
||
|
gContext.font = fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText('suggested: ' + this.suggested.substring(this.pixelsFilled, this.pixelsFilled + 8), 10, this.kTop + this.kHeight + 2 * fontSize);
|
||
|
}
|
||
|
|
||
|
if (gKeyPressed['0']) {
|
||
|
this.image[this.pixelsFilled++] = 0;
|
||
|
} else if (gKeyPressed['1']) {
|
||
|
this.image[this.pixelsFilled++] = 1;
|
||
|
}
|
||
|
|
||
|
if (this.pixelsFilled == this.kSpriteSize * this.kSpriteSize && this.completed) {
|
||
|
var image = [];
|
||
|
for (var i = 0; i < this.kSpriteSize; i++) {
|
||
|
var line = '';
|
||
|
for (var j = 0; j < this.kSpriteSize; j++) {
|
||
|
line += this.image[i * this.kSpriteSize + j] ? '1' : '0';
|
||
|
}
|
||
|
image.push(line);
|
||
|
}
|
||
|
this.completed(makeImageData(image, 16));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
function SoundEditor() {
|
||
|
this.start = true;
|
||
|
this.data = [null, null, null];
|
||
|
this.downTime = null;
|
||
|
this.part = 0;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
SoundEditor.prototype.update = function() {
|
||
|
if (this.start && (gKeyDown['0'] || gKeyDown['1'])) {
|
||
|
return;
|
||
|
}
|
||
|
this.start = false;
|
||
|
var min = [20, 20, 0.001];
|
||
|
var max = [20000, 20000, 1.0];
|
||
|
|
||
|
var fontSize = 16;
|
||
|
gContext.font = fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText("start frequency", 1.5 * 640 / 7 - gContext.measureText('start frequency').width / 2, 2 * fontSize);
|
||
|
gContext.fillText("end frequency", 3.5 * 640 / 7 - gContext.measureText('end frequency').width / 2, 2 * fontSize);
|
||
|
gContext.fillText("duration", 5.5 * 640 / 7 - gContext.measureText('duration').width / 2, 2 * fontSize);
|
||
|
|
||
|
for (var i = 0; i < this.data.length; i++) {
|
||
|
var kHeight = 240;
|
||
|
gContext.strokeStyle = '#fff';
|
||
|
gContext.strokeRect((2 * i + 1) * 640 / 7, 3 * fontSize, 640 / 7, kHeight);
|
||
|
if (this.data[i] != null) {
|
||
|
var v = (this.data[i] - min[i]) / (max[i] - min[i]);
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillRect((2 * i + 1) * 640 / 7, 3 * fontSize + kHeight * (1 - v), 640 / 7, kHeight * v);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.part < this.data.length) {
|
||
|
if (gKeyPressed['0']) {
|
||
|
this.data[this.part] = Math.random() * (max[this.part] - min[this.part]) + min[this.part];
|
||
|
this.part++;
|
||
|
} else {
|
||
|
var now = Date.now();
|
||
|
if (!this.downTime && gKeyDown['1']) {
|
||
|
this.downTime = now;
|
||
|
}
|
||
|
if (this.downTime) {
|
||
|
var v = Math.min((now - this.downTime) / 1000.0, 1.0);
|
||
|
this.data[this.part] = v * (max[this.part] - min[this.part]) + min[this.part];
|
||
|
if (!gKeyDown['1'] || (now - this.downTime) / 1000.0 > 1.0) {
|
||
|
this.part++;
|
||
|
this.downTime = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var names = ['start frequency', 'end frequency', 'duration'];
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.font = 'bold ' + fontSize + 'px courier new';
|
||
|
gContext.fillText("set " + names[this.part], 640 / 2 - gContext.measureText('set ' + names[this.part]).width / 2, 320);
|
||
|
gContext.font = fontSize + 'px courier new';
|
||
|
gContext.fillText("1 hold to set", 640 / 2 - gContext.measureText('1 hold to set').width / 2, 320 + fontSize);
|
||
|
gContext.fillText("0 random", 640 / 2 - gContext.measureText('0 random').width / 2, 320 + fontSize * 2);
|
||
|
} else {
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText("1 hear it", 640 / 2 - gContext.measureText('1 hear it').width / 2, 320 + fontSize);
|
||
|
gContext.fillText("0 done", 640 / 2 - gContext.measureText('0 done').width / 2, 320 + fontSize * 2);
|
||
|
if (gKeyPressed['1']) {
|
||
|
tone(this.data[0], this.data[1], this.data[2]);
|
||
|
}
|
||
|
if (gKeyPressed['0'] && this.completed) {
|
||
|
this.completed(this.data);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
function Game(options) {
|
||
|
this.options = {};
|
||
|
for (var k in options) {
|
||
|
this.options[k] = options[k];
|
||
|
}
|
||
|
this.velocity = [0, 0];
|
||
|
this.position = [0, 0];
|
||
|
this.gone = {};
|
||
|
this.camera = 10;
|
||
|
this.projectile = null;
|
||
|
this.gameOver = false;
|
||
|
this.start = true;
|
||
|
this.countdown = 3;
|
||
|
|
||
|
this.points = 0;
|
||
|
this.kills = 0;
|
||
|
|
||
|
document.location.hash = store(gGame);
|
||
|
document.getElementById("share").href = document.location.href;
|
||
|
|
||
|
if (this.options.button0 == undefined) {
|
||
|
this.options.button0 = 2;
|
||
|
}
|
||
|
|
||
|
if (this.options.button1 == undefined) {
|
||
|
this.options.button1 = 0;
|
||
|
}
|
||
|
|
||
|
if (!this.options.score) {
|
||
|
this.options.score = 1;
|
||
|
}
|
||
|
|
||
|
if (!this.options.player) {
|
||
|
this.options.player = makeImageData([
|
||
|
'11111111',
|
||
|
'10000001',
|
||
|
'10100101',
|
||
|
'10000001',
|
||
|
'10100101',
|
||
|
'10111101',
|
||
|
'10000001',
|
||
|
'11111111',
|
||
|
], 16);
|
||
|
}
|
||
|
|
||
|
if (!this.options.enemy) {
|
||
|
this.options.enemy = makeImageData([
|
||
|
'11111111',
|
||
|
'10000001',
|
||
|
'10100101',
|
||
|
'10000001',
|
||
|
'10111101',
|
||
|
'10100101',
|
||
|
'10000001',
|
||
|
'11111111',
|
||
|
], 16);
|
||
|
}
|
||
|
|
||
|
if (!this.options.point) {
|
||
|
this.options.point = makeImageData([
|
||
|
'00000000',
|
||
|
'00000000',
|
||
|
'00011000',
|
||
|
'00100100',
|
||
|
'01011010',
|
||
|
'01010010',
|
||
|
'00100100',
|
||
|
'00011000',
|
||
|
], 16);
|
||
|
}
|
||
|
|
||
|
this.background = makeImageData([
|
||
|
'1110',
|
||
|
'0101',
|
||
|
'0011',
|
||
|
'1001',
|
||
|
], 16);
|
||
|
|
||
|
this.timer = [
|
||
|
makeImageData([
|
||
|
'110',
|
||
|
'010',
|
||
|
'010',
|
||
|
'010',
|
||
|
'111',
|
||
|
], 16),
|
||
|
makeImageData([
|
||
|
'111',
|
||
|
'001',
|
||
|
'111',
|
||
|
'100',
|
||
|
'111',
|
||
|
], 16),
|
||
|
makeImageData([
|
||
|
'111',
|
||
|
'001',
|
||
|
'011',
|
||
|
'001',
|
||
|
'111',
|
||
|
], 16)
|
||
|
];
|
||
|
|
||
|
if (!this.options.level) {
|
||
|
this.options.level = '00102';
|
||
|
} else {
|
||
|
this.options.level = '0' + this.options.level;
|
||
|
}
|
||
|
|
||
|
if (!this.options.jump) {
|
||
|
this.options.jump = [1000, 3000, 0.25];
|
||
|
}
|
||
|
|
||
|
if (!this.options.shoot) {
|
||
|
this.options.shoot = [300, 300, 0.1];
|
||
|
}
|
||
|
|
||
|
this.collect = [this.options.shoot[0], this.options.jump[1], (this.options.shoot[2] + this.options.jump[2]) / 2];
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
Game.prototype.update = function() {
|
||
|
if (this.start && (gKeyDown['0'] || gKeyDown['1'])) {
|
||
|
return;
|
||
|
}
|
||
|
this.start = false;
|
||
|
|
||
|
if (this.countdown > 0) {
|
||
|
this.countdown -= gTimeDelta / 1000;
|
||
|
if (this.countdown > 0) {
|
||
|
var i = Math.min(Math.floor(this.countdown), 2);
|
||
|
gContext.putImageData(this.timer[i], 640 / 2 - 16 * 3, 480 / 2 - 16 * 3);
|
||
|
} else {
|
||
|
this.countdown = 0;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < this.options.level.length; i++) {
|
||
|
if (!this.gone[i]) {
|
||
|
var c = this.options.level.charAt(i);
|
||
|
if (c == '1') {
|
||
|
gContext.putImageData(this.options.enemy, i * 16 * 8 * 2 - this.position[0] + this.camera, 240);
|
||
|
} else if (c == '2') {
|
||
|
gContext.putImageData(this.options.point, i * 16 * 8 * 2 - this.position[0] + this.camera, 240);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var tile = Math.round(this.position[0] / (16 * 8 * 2));
|
||
|
if (!this.gone[tile]) {
|
||
|
var hit = this.options.level.charAt(tile);
|
||
|
if (hit == '1' && this.position[1] <= 16 * 8) {
|
||
|
if (this.velocity[1] < 0) {
|
||
|
this.gone[tile] = true;
|
||
|
this.velocity[1] = 1.5;
|
||
|
this.kills++;
|
||
|
tone(this.collect[1], this.collect[0], this.collect[2]);
|
||
|
} else {
|
||
|
this.gameOver = true;
|
||
|
this.gone[tile] = true;
|
||
|
}
|
||
|
} else if (hit == '2' && this.position[1] <= 16 * 8) {
|
||
|
tone(this.collect[0], this.collect[1], this.collect[2]);
|
||
|
this.points += this.options.score;
|
||
|
this.gone[tile] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.projectile != null) {
|
||
|
gContext.fillStyle = '#fff';
|
||
|
var screenPosition = this.projectile - this.position[0] + this.camera;
|
||
|
gContext.fillRect(screenPosition, 240 + 16 * 8 / 2, 16, 16);
|
||
|
this.projectile += gTimeDelta;
|
||
|
var p = Math.floor(this.projectile / (16 * 8 * 2));
|
||
|
var hit = this.options.level.charAt(p);
|
||
|
if (hit == '1' && !this.gone[p]) {
|
||
|
this.gone[p] = true;
|
||
|
this.projectile = null;
|
||
|
}
|
||
|
if (screenPosition > 640) {
|
||
|
this.projectile = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gContext.putImageData(this.options.player, this.camera, 240 - this.position[1]);
|
||
|
this.position[0] += this.velocity[0] * gTimeDelta;
|
||
|
this.position[1] += this.velocity[1] * gTimeDelta;
|
||
|
this.velocity[1] -= 0.005 * gTimeDelta;
|
||
|
if (this.position[1] <= 0 && !this.gameOver) {
|
||
|
this.position[1] = 0;
|
||
|
this.velocity[1] = 0;
|
||
|
}
|
||
|
|
||
|
if (this.gameOver) {
|
||
|
this.position[0] -= 2 * gTimeDelta;
|
||
|
if (this.position[1] > -480) {
|
||
|
this.position[1] -= gTimeDelta * 0.1;
|
||
|
} else {
|
||
|
var fontSize = 16;
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.font = 'bold ' + fontSize + 'px courier new';
|
||
|
gContext.fillText('game over', 640 / 2 - gContext.measureText('game over').width / 2, 240 - 2 * fontSize);
|
||
|
gContext.font = fontSize + 'px courier new';
|
||
|
gContext.fillText('1 retry', 640 / 2 - gContext.measureText('1 retry').width / 2, 240 + 16 * 8 + 2 * fontSize);
|
||
|
gContext.fillText('0 back', 640 / 2 - gContext.measureText('1 retry').width / 2, 240 + 16 * 8 + 3 * fontSize);
|
||
|
if (gKeyPressed['1'] && this.completed) {
|
||
|
this.completed(1);
|
||
|
}
|
||
|
if (gKeyPressed['0'] && this.completed) {
|
||
|
this.completed(0);
|
||
|
}
|
||
|
}
|
||
|
} else if (tile > this.options.level.length) {
|
||
|
var targetCamera = 640 / 2 - (16 * 8) / 2;
|
||
|
if (this.camera < targetCamera) {
|
||
|
this.camera += gTimeDelta / 5;
|
||
|
} else {
|
||
|
this.camera = targetCamera;
|
||
|
if (this.velocity[1] == 0) {
|
||
|
this.velocity[1] = 1.5;
|
||
|
}
|
||
|
var fontSize = 16;
|
||
|
gContext.font = fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText('1 again', 640 / 2 - gContext.measureText('1 again').width / 2, 240 + 16 * 8 + 2 * fontSize);
|
||
|
gContext.fillText('0 back', 640 / 2 - gContext.measureText('1 again').width / 2, 240 + 16 * 8 + 3 * fontSize);
|
||
|
|
||
|
var results = '';
|
||
|
if (this.points) {
|
||
|
if (results.length) {
|
||
|
results += ', ';
|
||
|
}
|
||
|
results += this.points + (this.points == 1 ? ' point' : ' points');
|
||
|
}
|
||
|
if (this.kills) {
|
||
|
if (results.length) {
|
||
|
results += ', ';
|
||
|
}
|
||
|
results += this.kills + (this.kills == 1 ? ' enemy vanquished' : ' enemies vanquished');
|
||
|
}
|
||
|
gContext.fillText(results, 640 / 2 - gContext.measureText(results).width / 2, 240 + 16 * 8 + 5 * fontSize);
|
||
|
if (gKeyPressed['1'] && this.completed) {
|
||
|
this.completed(1);
|
||
|
}
|
||
|
if (gKeyPressed['0'] && this.completed) {
|
||
|
this.completed(0);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
for (var i = 0; i < 12; i++) {
|
||
|
for (var j = 0; j < 2; j++) {
|
||
|
gContext.putImageData(this.background, i * 16 * 4 - this.position[0] % (16 * 4), 240 + 16 * 8 + j * 16 * 4);
|
||
|
}
|
||
|
}
|
||
|
var jump = this.velocity[1] == 0 &&
|
||
|
(this.options.button0 == 0 && gKeyDown['0'] ||
|
||
|
this.options.button1 == 0 && gKeyDown['1']);
|
||
|
var shoot = this.projectile == null &&
|
||
|
(this.options.button0 == 1 && gKeyDown['0'] ||
|
||
|
this.options.button1 == 1 && gKeyDown['1']);
|
||
|
var move = this.projectile == null &&
|
||
|
(this.options.button0 == 2 && gKeyDown['0'] ||
|
||
|
this.options.button1 == 2 && gKeyDown['1'] ||
|
||
|
this.options.button0 != 2 && this.options.button1 != 2);
|
||
|
if (jump) {
|
||
|
this.velocity[1] = 1.5;
|
||
|
tone(this.options.jump[0], this.options.jump[1], this.options.jump[2]);
|
||
|
}
|
||
|
if (shoot) {
|
||
|
this.projectile = this.position[0];
|
||
|
tone(this.options.shoot[0], this.options.shoot[1], this.options.shoot[2]);
|
||
|
}
|
||
|
if (move) {
|
||
|
this.velocity[0] = 1;
|
||
|
} else {
|
||
|
this.velocity[0] = 0;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function packImage(imageData) {
|
||
|
var result = '';
|
||
|
for (var i = 0; i < imageData.width / 16; i++) {
|
||
|
for (var j = 0; j < imageData.height / 16; j++) {
|
||
|
result += (imageData.data[j * imageData.width * 4 * 16 + i * 4 * 16] ? '1' : '0');
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
function unpackImageData(data) {
|
||
|
var pixels = [];
|
||
|
console.debug(data);
|
||
|
for (var i = 0; i < 8; i++) {
|
||
|
var line = '';
|
||
|
for (var j = 0; j < 8; j++) {
|
||
|
line += data.charAt(j * 8 + i);
|
||
|
}
|
||
|
pixels.push(line);
|
||
|
}
|
||
|
return makeImageData(pixels, 16);
|
||
|
}
|
||
|
|
||
|
function store(game) {
|
||
|
var simple = {};
|
||
|
for (var k in game) {
|
||
|
if (game[k] instanceof ImageData) {
|
||
|
simple[k] = packImage(game[k]);
|
||
|
} else {
|
||
|
simple[k] = game[k];
|
||
|
}
|
||
|
}
|
||
|
return JSON.stringify(simple);
|
||
|
};
|
||
|
|
||
|
function load(game) {
|
||
|
var simple = JSON.parse(game);
|
||
|
for (var k in simple) {
|
||
|
if (['player', 'enemy'].indexOf(k) != -1) {
|
||
|
simple[k] = unpackImageData(simple[k]);
|
||
|
}
|
||
|
}
|
||
|
return simple;
|
||
|
};
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
var gAudio = null;
|
||
|
var gGain = null;
|
||
|
|
||
|
function tone(frequency0, frequency1, duration) {
|
||
|
var Context = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext;
|
||
|
if (!gAudio && Context) {
|
||
|
gAudio = new Context();
|
||
|
}
|
||
|
if (!gGain && gAudio) {
|
||
|
gGain = gAudio.createGain();
|
||
|
gGain.connect(gAudio.destination);
|
||
|
gGain.gain.value = 0.15;
|
||
|
}
|
||
|
var now = gAudio.currentTime;
|
||
|
var oscillator = gAudio.createOscillator();
|
||
|
oscillator.frequency.setValueAtTime(frequency0, now);
|
||
|
oscillator.frequency.linearRampToValueAtTime(frequency1, now + duration);
|
||
|
oscillator.type = 'sine';
|
||
|
oscillator.connect(gGain);
|
||
|
oscillator.start(now);
|
||
|
oscillator.stop(gAudio.currentTime + duration);
|
||
|
}
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
function Menu(configuration) {
|
||
|
this.title = configuration.title;
|
||
|
this.options = configuration.options;
|
||
|
this.footer = configuration.footer;
|
||
|
this.image = configuration.image;
|
||
|
this.bitsNeeded = 0;
|
||
|
this.fontSize = 16;
|
||
|
this.bits = '';
|
||
|
var count = this.options.length;
|
||
|
while (count > 1) {
|
||
|
this.bitsNeeded += 1;
|
||
|
count /= 2;
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
Menu.prototype.update = function() {
|
||
|
gContext.font = 'bold ' + this.fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText(this.title, 640 / 2 - gContext.measureText(this.title).width / 2, this.fontSize);
|
||
|
|
||
|
var maxWidth = 0;
|
||
|
for (var i = 0; i < this.options.length; i++) {
|
||
|
maxWidth = Math.max(maxWidth, gContext.measureText(this.options[i]).width);
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < this.options.length; i++) {
|
||
|
gContext.font = this.fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText(bits(i, this.bitsNeeded) + ' ' + this.options[i], 640 / 2 - maxWidth / 2, this.fontSize * (i + 3));
|
||
|
}
|
||
|
gContext.font = this.fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
var text = '';
|
||
|
for (var i = 0; i < this.bitsNeeded; i++) {
|
||
|
text += '0';
|
||
|
}
|
||
|
gContext.fillText(this.bits, 640 / 2 - maxWidth / 2, this.fontSize * (this.options.length + 4));
|
||
|
gContext.fillStyle = blinkColor();
|
||
|
gContext.fillRect(640 / 2 - maxWidth / 2 + gContext.measureText(this.bits).width, this.fontSize * (this.options.length + 3), 10, this.fontSize);
|
||
|
|
||
|
if (this.footer) {
|
||
|
gContext.font = this.fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
gContext.fillText(this.footer, 640 / 2 - gContext.measureText(this.footer).width / 2, 480 - this.fontSize);
|
||
|
}
|
||
|
|
||
|
if (this.image) {
|
||
|
gContext.putImageData(this.image, 640 / 2 - this.image.width / 2, 480 / 2 - this.image.height / 2);
|
||
|
}
|
||
|
|
||
|
if (gKeyPressed['0']) {
|
||
|
this.bits += '0';
|
||
|
} else if (gKeyPressed['1']) {
|
||
|
this.bits += '1';
|
||
|
}
|
||
|
|
||
|
if (this.bits.length >= this.bitsNeeded && this.completed) {
|
||
|
var result = 0;
|
||
|
for (var i = 0; i < this.bits.length; i++) {
|
||
|
result *= 2;
|
||
|
if (this.bits.charAt(i) == '1') {
|
||
|
result |= 1;
|
||
|
}
|
||
|
}
|
||
|
this.completed(result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
"use strict";
|
||
|
var gContext;
|
||
|
var gKeyDown = {}
|
||
|
var gKeyPressed = {}
|
||
|
var gMode;
|
||
|
var gTimeDelta = 0;
|
||
|
var gLastFrame = Date.now();
|
||
|
var gTimer;
|
||
|
|
||
|
var gGame = {};
|
||
|
|
||
|
document.addEventListener("DOMContentLoaded", function() {
|
||
|
var canvas = document.getElementById("canvas");
|
||
|
gContext = canvas.getContext("2d");
|
||
|
|
||
|
if (document.location.hash) {
|
||
|
try {
|
||
|
gGame = load(document.location.hash.substring(1));
|
||
|
} catch (e) {
|
||
|
console.debug(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.body.onkeydown = function(event) {
|
||
|
if (event.which == 48 || event.which == 96) {
|
||
|
gKeyDown['0'] = true;
|
||
|
}
|
||
|
if (event.which == 49 || event.which == 97) {
|
||
|
gKeyDown['1'] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.body.onkeyup = function(event) {
|
||
|
if (event.which == 48 || event.which == 96) {
|
||
|
gKeyDown['0'] = false;
|
||
|
}
|
||
|
if (event.which == 49 || event.which == 97) {
|
||
|
gKeyDown['1'] = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.body.onkeypress = function(event) {
|
||
|
if (event.which == 48 || event.which == 96) {
|
||
|
gKeyPressed['0'] = true;
|
||
|
}
|
||
|
if (event.which == 49 || event.which == 97) {
|
||
|
gKeyPressed['1'] = true;
|
||
|
}
|
||
|
if ((event.which == 48 || event.which == 49 || event.which == 96 || event.which == 97) &&
|
||
|
!(gMode instanceof Game) &&
|
||
|
!(gMode instanceof SoundEditor)) {
|
||
|
|
||
|
var min = 40;
|
||
|
var max = 2000;
|
||
|
var f = Math.random() * (max - min) + min;
|
||
|
tone(f, f, 0.1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
makeMainMenu();
|
||
|
window.requestAnimationFrame(update);
|
||
|
});
|
||
|
|
||
|
function makeMainMenu() {
|
||
|
var menu = new Menu({
|
||
|
title: 'become a game developer in 60 seconds',
|
||
|
options: ['create', 'test'],
|
||
|
footer: 'buttons: 0 and 1',
|
||
|
image: makeImageData([
|
||
|
'11001110110011101100111',
|
||
|
'01001010010010100100101',
|
||
|
'01001010010010100100101',
|
||
|
'01001010010010100100101',
|
||
|
'11101110111011101110111',
|
||
|
'00000000000000000000000',
|
||
|
'00111100100100010111000',
|
||
|
'00100001010110110100000',
|
||
|
'00101101110101010110000',
|
||
|
'00100101010100010100000',
|
||
|
'00111101010100010111000',
|
||
|
], 16)}
|
||
|
);
|
||
|
menu.completed = function(result) {
|
||
|
if (result == 0) {
|
||
|
gTimer = 60;
|
||
|
makeCreateMenu();
|
||
|
} else {
|
||
|
gTimer = undefined;
|
||
|
makeGame();
|
||
|
}
|
||
|
};
|
||
|
gMode = menu;
|
||
|
}
|
||
|
|
||
|
function makeCreateMenu() {
|
||
|
var menu = new Menu({title: 'make game', options: [
|
||
|
'[' + (gGame.player ? 'x' : ' ') + '] player',
|
||
|
'[' + (gGame.enemy ? 'x' : ' ') + '] enemy',
|
||
|
'[' + (gGame.score ? 'x' : ' ') + '] score',
|
||
|
'[' + (gGame.jump ? 'x' : ' ') + '] jump',
|
||
|
'[' + (gGame.shoot ? 'x' : ' ') + '] shoot',
|
||
|
'[' + (gGame.button0 != undefined ? 'x' : ' ') + '] buttons',
|
||
|
'[' + (gGame.level ? 'x' : ' ') + '] level',
|
||
|
' ship it',
|
||
|
]});
|
||
|
menu.completed = function(result) {
|
||
|
if (result == 0) {
|
||
|
makeImageEditor('player',
|
||
|
'00111100'+
|
||
|
'00111100'+
|
||
|
'00111100'+
|
||
|
'10011001'+
|
||
|
'11111111'+
|
||
|
'00011000'+
|
||
|
'00100100'+
|
||
|
'00100100');
|
||
|
} else if (result == 1) {
|
||
|
makeImageEditor('enemy',
|
||
|
'00111100'+
|
||
|
'01000010'+
|
||
|
'10000001'+
|
||
|
'10100101'+
|
||
|
'10000001'+
|
||
|
'11011011'+
|
||
|
'01011010'+
|
||
|
'01111110');
|
||
|
} else if (result == 2) {
|
||
|
gGame.score = 1;
|
||
|
makeScoreEditor();
|
||
|
} else if (result == 3) {
|
||
|
makeSoundEditor('jump');
|
||
|
} else if (result == 4) {
|
||
|
makeSoundEditor('shoot');
|
||
|
} else if (result == 5) {
|
||
|
makeButtonsMenu();
|
||
|
} else if (result == 6) {
|
||
|
gGame.level = '';
|
||
|
makeLevelEditor();
|
||
|
} else if (result == 7) {
|
||
|
gTimer = undefined;
|
||
|
makeGame();
|
||
|
}
|
||
|
}
|
||
|
gMode = menu;
|
||
|
}
|
||
|
|
||
|
function makeImageEditor(what, suggested) {
|
||
|
var editor = new ImageEditor();
|
||
|
editor.suggested = suggested;
|
||
|
editor.title = 'draw ' + what + ' by pressing 1 and 0';
|
||
|
editor.completed = function(result) {
|
||
|
gGame[what] = result;
|
||
|
makeCreateMenu();
|
||
|
};
|
||
|
gMode = editor;
|
||
|
}
|
||
|
|
||
|
function makeSoundEditor(what) {
|
||
|
var editor = new SoundEditor();
|
||
|
editor.completed = function(result) {
|
||
|
gGame[what] = result;
|
||
|
makeCreateMenu();
|
||
|
};
|
||
|
gMode = editor;
|
||
|
}
|
||
|
|
||
|
function makeButtonMenu(button) {
|
||
|
var menu = new Menu({title: 'what does ' + button + ' do?', options: [
|
||
|
'jump',
|
||
|
'shoot',
|
||
|
'move',
|
||
|
'nothing',
|
||
|
]});
|
||
|
return menu;
|
||
|
}
|
||
|
|
||
|
function makeButtonsMenu(button) {
|
||
|
var menu = makeButtonMenu('0');
|
||
|
menu.completed = function(result) {
|
||
|
gGame['button0'] = result;
|
||
|
menu = makeButtonMenu('1');
|
||
|
menu.completed = function(result) {
|
||
|
gGame['button1'] = result;
|
||
|
makeCreateMenu();
|
||
|
}
|
||
|
gMode = menu;
|
||
|
};
|
||
|
gMode = menu;
|
||
|
}
|
||
|
|
||
|
function update() {
|
||
|
gContext.fillStyle = '#000';
|
||
|
gContext.fillRect(0, 0, 640, 480);
|
||
|
|
||
|
var now = Date.now();
|
||
|
gTimeDelta = now - gLastFrame;
|
||
|
gLastFrame = now;
|
||
|
|
||
|
if (gTimer != undefined) {
|
||
|
if (gTimer > 0) {
|
||
|
gTimer -= gTimeDelta / 1000;
|
||
|
} else {
|
||
|
gTimer = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (gMode) {
|
||
|
gMode.update();
|
||
|
} else {
|
||
|
gContext.fillStyle = pulseColor();
|
||
|
gContext.fillRect(0, 0, 640, 480);
|
||
|
}
|
||
|
|
||
|
for (var i in gKeyPressed) {
|
||
|
gKeyPressed[i] = false;
|
||
|
}
|
||
|
|
||
|
if (!(gMode instanceof Game) && gTimer !== undefined) {
|
||
|
var fontSize = 16;
|
||
|
gContext.font = 'bold ' + fontSize + 'px courier new';
|
||
|
gContext.fillStyle = '#fff';
|
||
|
var remaining = Math.round(gTimer);
|
||
|
if (gTimer <= 0) {
|
||
|
remaining = 'OUT OF TIME! SHIP IT! OUT OF TIME! SHIP IT! OUT OF TIME! 111!';
|
||
|
gContext.fillStyle = pulseColor();
|
||
|
}
|
||
|
gContext.fillText(remaining, 640 - gContext.measureText(remaining).width - 1, 480 - 1);
|
||
|
}
|
||
|
|
||
|
window.requestAnimationFrame(update);
|
||
|
}
|
||
|
|
||
|
function pulseColor() {
|
||
|
var pulse = (Math.sin(15 * Date.now() / 1000) + 1) / 2;
|
||
|
var value = Math.floor(255 * pulse).toString();
|
||
|
return 'rgb(' + value + ',' + value + ',' + value + ')';
|
||
|
}
|
||
|
|
||
|
function blinkColor() {
|
||
|
var blink = (Math.floor(4 * Date.now() / 1000) & 1) == 0;
|
||
|
var value = Math.floor(255 * blink).toString();
|
||
|
return 'rgb(' + value + ',' + value + ',' + value + ')';
|
||
|
}
|
||
|
|
||
|
function bits(value, count) {
|
||
|
var result = '';
|
||
|
for (var i = 0; i < count; i++) {
|
||
|
if ((value & (1 << i)) != 0) {
|
||
|
result = '1' + result;
|
||
|
} else {
|
||
|
result = '0' + result;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
function makeImageData(pixels, scale) {
|
||
|
var height = pixels.length;
|
||
|
var width = pixels[0].length;
|
||
|
|
||
|
var data = gContext.createImageData(width * scale, height * scale);
|
||
|
for (var i = 0; i < width * scale; i++) {
|
||
|
for (var j = 0; j < height * scale; j++) {
|
||
|
var v = pixels[Math.floor(j / scale)].charAt(Math.floor(i / scale)) == '1';
|
||
|
data.data[4 * (j * width * scale + i) + 0] = v ? 255 : 0;
|
||
|
data.data[4 * (j * width * scale + i) + 1] = v ? 255 : 0;
|
||
|
data.data[4 * (j * width * scale + i) + 2] = v ? 255 : 0;
|
||
|
data.data[4 * (j * width * scale + i) + 3] = 255;
|
||
|
}
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
function makeGame() {
|
||
|
var game = new Game(gGame);
|
||
|
game.completed = function(result) {
|
||
|
if (result == 1) {
|
||
|
makeGame();
|
||
|
} else {
|
||
|
makeMainMenu();
|
||
|
}
|
||
|
};
|
||
|
gMode = game;
|
||
|
}
|
||
|
|
||
|
function makeLevelEditor() {
|
||
|
var menu = new Menu({title: 'choose object for location ' + gGame.level.length, options: [
|
||
|
'nothing',
|
||
|
'enemy',
|
||
|
'points',
|
||
|
'finish',
|
||
|
]});
|
||
|
menu.completed = function(result) {
|
||
|
if (result == 0) {
|
||
|
gGame.level += '0';
|
||
|
makeLevelEditor();
|
||
|
} else if (result == 1) {
|
||
|
gGame.level += '1';
|
||
|
makeLevelEditor();
|
||
|
} else if (result == 2) {
|
||
|
gGame.level += '2';
|
||
|
makeLevelEditor();
|
||
|
} else if (result == 3) {
|
||
|
makeCreateMenu();
|
||
|
}
|
||
|
};
|
||
|
gMode = menu;
|
||
|
}
|
||
|
|
||
|
function makeScoreEditor() {
|
||
|
var menu = new Menu({title: 'how many points?: ' + gGame.score, options: [
|
||
|
'more',
|
||
|
'done',
|
||
|
]});
|
||
|
menu.completed = function(result) {
|
||
|
if (result == 0) {
|
||
|
gGame.score *= 10;
|
||
|
if (gGame.score < 10000000000000000000) {
|
||
|
makeScoreEditor();
|
||
|
} else {
|
||
|
makeCreateMenu();
|
||
|
}
|
||
|
} else if (result == 1) {
|
||
|
makeCreateMenu();
|
||
|
}
|
||
|
};
|
||
|
gMode = menu;
|
||
|
}
|
||
|
</script>
|
||
|
<style>
|
||
|
body {
|
||
|
background-color: #444;
|
||
|
color: #fff;
|
||
|
}
|
||
|
canvas {
|
||
|
padding-left: 0;
|
||
|
padding-right: 0;
|
||
|
margin-left: auto;
|
||
|
margin-right: auto;
|
||
|
display: block;
|
||
|
width: 640px;
|
||
|
}
|
||
|
#share {
|
||
|
color: #fff;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<canvas id="canvas" width="640" height="480"></canvas>
|
||
|
<div><a id="share" href="http://www.unprompted.com/ldjam/compo34/">share link, updated on test</a></div>
|
||
|
</body>
|
||
|
</html>
|
||
|
`,
|
||
|
width: 720,
|
||
|
height: 512,
|
||
|
});
|