All of the changes that have been sitting on tildepi for ages. For posterity.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3530 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2020-09-23 01:58:13 +00:00
parent d6018736d5
commit d293637741
29 changed files with 3380 additions and 8 deletions

View File

@ -16,6 +16,8 @@ Tilde Friends is [routinely](https://www.unprompted.com/projects/build/tildefrie
scons uv=path/to/libuv v8=path/to/v8 scons uv=path/to/libuv v8=path/to/v8
``` ```
Note for Raspberry Pi: http://www.mccarroll.net/blog/v8_pi2/index.html
## Running ## Running
Running the built tildefriends executable will start a web server. This is a good starting point: <http://localhost:12345/>. Running the built tildefriends executable will start a web server. This is a good starting point: <http://localhost:12345/>.

View File

@ -0,0 +1,34 @@
//! {"require": ["libiframe"]}
// Server-side blink! Woo!
require("libiframe").iframe({
source: `
document.body.style.color = '#fff';
document.body.style.backgroundColor = '#000';
var canvas = document.createElement("canvas");
document.body.append(canvas);
canvas.width = 640;
canvas.height = 480;
var context = canvas.getContext("2d");
rpc.export({
setFillStyle: function(style) { context.fillStyle = style; },
fillRect: context.fillRect.bind(context),
fillText: context.fillText.bind(context),
});`,
style: `
border: 0;
`}).then(draw).catch(terminal.print);
var blink = true;
function draw(iframe) {
iframe.setFillStyle('#222');
iframe.fillRect(0, 0, 640, 480);
if (blink) {
iframe.setFillStyle('#fff');
iframe.fillText("Hello, world!", 50, 50);
}
blink = !blink;
setTimeout(function() { draw(iframe); }, 500);
}

View File

@ -286,9 +286,9 @@ function updateUsers() {
terminal.cork(); terminal.cork();
terminal.split([ terminal.split([
{type: "horizontal", children: [ {type: "horizontal", children: [
{name: "windows", basis: "2in", grow: "0", shrink: "0"}, {name: "windows", basis: "1in", grow: "0", shrink: "0"},
{name: "terminal", grow: "1"}, {name: "terminal", grow: "1"},
{name: "users", basis: "2in", grow: "0", shrink: "0"}, {name: "users", basis: "1in", grow: "0", shrink: "0"},
]}, ]},
]); ]);
updateTitle(); updateTitle();
@ -444,7 +444,7 @@ function printMessage(message) {
{class: "base3", value: from}, {class: "base3", value: from},
" ", " ",
formatMessage(message.message)); formatMessage(message.message));
} else { } else if (message.message) {
terminal.print( terminal.print(
{class: "base0", value: niceTime(lastTimestamp, now)}, {class: "base0", value: niceTime(lastTimestamp, now)},
" ", " ",
@ -453,6 +453,11 @@ function printMessage(message) {
{class: "base00", value: ">"}, {class: "base00", value: ">"},
" ", " ",
formatMessage(message.message)); formatMessage(message.message));
} else {
terminal.print(
{class: "base0", value: niceTime(lastTimestamp, now)},
" ",
{class: "base3", value: JSON.stringify(message)});
} }
lastTimestamp = now; lastTimestamp = now;
} }

View File

@ -0,0 +1,76 @@
"use strict";
//! {
//! "chat": {
//! "version": 1,
//! "settings": [
//! {"name": "nickname", "type": "text"},
//! {"name": "password", "type": "password"}
//! ]
//! },
//! "category": "libraries"
//! }
let gServices = {};
class ChatService {
constructor(options) {
let self = this;
self._name = options.name;
self._callback = options.callback;
setTimeout(function() {
self._callback({
action: "message",
from: "service",
to: self._name,
message: "Hello, world!",
conversation: null,
});
}, 1000);
}
sendMessage(target, message) {
let self = this;
setTimeout(function() {
self._callback({
action: "message",
from: target,
to: self._name,
message: "I saw you say: " + message,
conversation: target,
});
}, 500);
return self._callback({
action: "message",
from: self._name,
to: target,
message: message,
conversation: target,
});
}
}
core.register("onMessage", function(sender, message) {
print(message);
let service = gServices[message.name];
if (!service) {
service = new ChatService(message);
gServices[message.name] = service;
} else {
service._callback = message.callback || service._callback;
}
setTimeout(function() {
service._callback({
action: "message",
from: "service",
to: service._name,
message: "I got your message.",
conversation: null,
});
}, 500);
return {
sendMessage: service.sendMessage.bind(service),
};
});

View File

@ -0,0 +1,198 @@
"use strict";
//! {"require": ["ui"]}
terminal.setEcho(false);
terminal.setTitle("Programming Contest Test");
let kProblem = {
description: "Write a function foo that returns a number.",
tests: [
[1, 1],
[2, 2],
[4, 4],
],
default: `// Problem 1
// ${core.user.name}
// ${new Date()}
function foo() {
print("hi cory");
return 0;
}
foo();`,
};
function back() {
terminal.split([{name: "terminal"}]);
if (gEditEvent) {
gEditEvent.back();
}
}
function runScript(script, input) {
var results = {};
try {
var output = [];
function print() {
for (var i in arguments) {
output.push(arguments[i].toString());
}
output.push("\n");
}
results.results = eval(script || "");
results.output = output.join("");
} catch (error) {
results.error = error;
}
return results;
}
function sendResults(document) {
var results = runScript(document);
var message = `${kProblem.description}
Return value:
${results.results}
Output:
${results.output}
`;
if (results.error) {
message += `Error:
${results.error}`;
}
terminal.postMessageToIframe("iframe", { results: message });
}
function loadScript() {
return database.get(core.user.name).then(function(script) {
return script || kProblem.default;
}).catch(function(error) {
return kProblem.default;
});
}
core.register("onWindowMessage", function(event) {
if (event.message.ready) {
loadScript().then(function(script) {
terminal.postMessageToIframe("iframe", {contents: script});
sendResults(script);
});
} else if (event.message.contents) {
database.set(core.user.name, event.message.contents).then(function() {
sendResults(event.message.contents);
});
}
});
//if (!core.user.credentials.permissions || !core.user.credentials.permissions.authenticated) {
// terminal.print("Please authenticate.");
//} else {
showEditor();
//}
function showEditor() {
terminal.split([{name: "terminal", type: "vertical"}]);
terminal.clear();
terminal.print({iframe: `<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.css"></link>
<style>
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
padding: 0;
}
#menu {
flex: 0 0 auto;
}
#container {
flex: 1 1 auto;
display: flex;
flex-direction: row;
width: 100%;
background-color: white;
}
.CodeMirror {
width: 100%;
height: 100%;
}
.CodeMirror-scroll {
}
#edit { background-color: white }
#preview {
background-color: #ccc;
font-family: monospace;
}
#edit, #preview {
display: flex;
overflow: auto;
flex: 0 0 50%;
}
</style>
<script>
var gEditor;
function index() {
parent.postMessage({index: true}, "*");
}
function submit() {
parent.postMessage({
contents: gEditor.getValue(),
}, "*");
}
function cursorActivity() {
var selection = gEditor.listSelections();
var key = "test";
var a = selection[0].anchor;
var b = selection[0].head;
if (b.line < a.line || a.line == b.line && b.ch < a.ch) {
[a, b] = [b, a];
}
parent.postMessage({cursor: {start: a, end: b}}, "*");
}
</script>
<script>
window.addEventListener("message", function(message) {
if (message.data.contents) {
gEditor.setValue(message.data.contents);
} else if (message.data.results) {
preview.innerText = message.data.results;
}
}, false);
window.addEventListener("load", function() {
gEditor = CodeMirror.fromTextArea(document.getElementById("contents"), {
lineNumbers: true
});
parent.postMessage({ready: true}, "*");
});
</script>
</head>
<body>
<div id="menu">
<input type="button" value="Back" onclick="index()">
` + (core.user.credentials.permissions && core.user.credentials.permissions.authenticated ? `
<input type="button" value="Save" onclick="submit()">
` : "") +
` </div>
<div id="container">
<div id="edit"><textarea id="contents"></textarea></div>
<div id="preview"></div>
</div>
</body>
</html>`, name: "iframe", style: "flex: 1 1 auto; border: 0; width: 100%"});
}

269
packages/cory/db/db.js Normal file
View File

@ -0,0 +1,269 @@
//! {"category": "libraries"}
class DatabaseList {
constructor(name) {
this._name = name;
}
_getList() {
let self = this;
return database.get(this._name).then(function(node) {
return JSON.parse(node);
}).catch(function(error) {
return self._emptyNode();
});
}
_randomKey() {
let kAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let kBytes = 32;
let result = "";
for (let i = 0; i < kBytes; i++) {
result += kAlphabet.charAt(Math.floor(Math.random() * kAlphabet.length));
}
return this._name + "_" + result;
}
_emptyNode() {
return {next: null, previous: null};
}
_getNode() {
var self = this;
var key = self._randomKey();
return database.get(key).then(function(found) {
if (!found) {
return key;
} else {
return self._getNode();
}
});
}
_getRelative(node, next, count) {
}
_insertEnd(item, after) {
let self = this;
return this._getList().then(function(listNode) {
let readPromises = [self._getNode()];
readPromises.push(listNode.previous ? database.get(listNode.previous).then(JSON.parse) : null);
readPromises.push(listNode.next ? database.get(listNode.next).then(JSON.parse) : null);
return Promise.all(readPromises).then(function(results) {
let newNodeName = results[0];
let previousNode = results[1];
let nextNode = results[2];
let newNode = self._emptyNode();
let oldPrevious = listNode.previous;
let oldNext = listNode.next;
newNode.value = item;
newNode.previous = listNode.previous || newNodeName;
newNode.next = listNode.next || newNodeName;
if (after) {
listNode.previous = newNodeName;
listNode.next = listNode.next || newNodeName;
} else {
listNode.next = newNodeName;
listNode.previous = listNode.previous || newNodeName;
}
let writePromises = [
database.set(self._name, JSON.stringify(listNode)),
database.set(newNodeName, JSON.stringify(newNode)),
];
if (oldPrevious && oldPrevious == oldNext) {
previousNode.next = newNodeName;
previousNode.previous = newNodeName;
writePromises.push(database.set(oldPrevious, JSON.stringify(previousNode)));
} else {
if (previousNode) {
previousNode.next = newNodeName;
writePromises.push(database.set(oldPrevious, JSON.stringify(previousNode)));
}
if (nextNode) {
nextNode.previous = newNodeName;
writePromises.push(database.set(oldNext, JSON.stringify(nextNode)));
}
}
return Promise.all(writePromises);
});
});
}
_removeEnd(after) {
let self = this;
return this._getList().then(function(listNode) {
if (listNode.next) {
if (listNode.next == listNode.previous) {
return database.get(listNode.next).then(JSON.parse).then(function(removedNode) {
let removedName = listNode.next;
listNode.next = null;
listNode.previous = null;
return Promise.all([
database.remove(removedName),
database.set(self._name, JSON.stringify(listNode)),
]).then(function() {
return removedNode.value;
});
});
} else {
let name1 = listNode.previous;
let name2 = listNode.next;
return Promise.all([
database.get(listNode.previous),
database.get(listNode.next),
]).then(function(nodes) {
var node1 = JSON.parse(nodes[0]);
var node2 = JSON.parse(nodes[1]);
if (after) {
let name0 = node1.previous;
return database.get(name0).then(JSON.parse).then(function(node0) {
node0.next = name2;
node2.previous = name0;
listNode.previous = name0;
return Promise.all([
database.set(name0, JSON.stringify(node0)),
database.remove(name1),
database.set(name2, JSON.stringify(node2)),
database.set(self._name, JSON.stringify(listNode)),
])
}).then(function() {
return node1.value;
});
} else {
let name3 = node2.next;
return database.get(name3).then(JSON.parse).then(function(node3) {
node1.next = name3;
node3.previous = name1;
listNode.next = name3;
terminal.print(name1, " ", name2, " ", name3, " ", self._name);
terminal.print(JSON.stringify(listNode));
return Promise.all([
database.set(name1, JSON.stringify(node1)),
database.remove(name2),
database.set(name3, JSON.stringify(node3)),
database.set(self._name, JSON.stringify(listNode)),
])
}).then(function() {
return node2.value;
});
}
});
}
}
});
}
push(item) {
return this._insertEnd(item, true);
}
pop() {
return this._removeEnd(true);
}
shift() {
return this._removeEnd(false);
}
unshift(item) {
return this._insertEnd(item, false);
}
_sliceInternal(head, count, next, end, result) {
let self = this;
if (head[next] == end && count > 0) {
result.push(head.value);
return new Promise(function(resolve, reject) { resolve(result); });
} else {
if (count > 0) {
return database.get(head[next]).then(JSON.parse).then(function(retrieved) {
return self._sliceInternal(retrieved, count - 1, next, end, result).then(function(result) {
result.push(head.value);
return result;
});
});
} else {
return new Promise(function(resolve, reject) { resolve(result); });
}
}
}
get(count) {
let self = this;
return self._getList().then(function(listNode) {
if (listNode.next) {
return database.get(count > 0 ? listNode.next : listNode.previous).then(JSON.parse).then(function(head) {
if (head) {
return self._sliceInternal(
head,
Math.abs(count),
count > 0 ? "next" : "previous",
count > 0 ? listNode.next : listNode.previous,
[]).then(function(result) {
result.reverse();
return result;
});
} else {
return null;
}
});
}
});
}
}
function wipeDatabase() {
let promises = [];
return database.getAll().then(function(list) {
for (let i = 0; i < list.length; i++) {
promises.push(database.remove(list[i]));
}
});
return Promise.all(promises);
}
function dumpDatabase() {
return database.getAll().then(function(list) {
let promises = [];
for (let i = 0; i < list.length; i++) {
promises.push(database.get(list[i]));
}
return Promise.all(promises).then(function(values) {
for (let i = 0; i < list.length; i++) {
terminal.print(list[i], " ", values[i]);
}
});
}).catch(function(error) {
terminal.print(error);
});
}
if (imports.terminal) {
x = new DatabaseList("list");
core.register("onInput", function(input) {
if (input == "clear") {
wipeDatabase().then(function() {
terminal.print("Database is now empty.");
});
} else if (input.substring(0, "push ".length) == "push ") {
x.push(input.substring("push ".length)).then(dumpDatabase).catch(terminal.print);
} else if (input.substring(0, "unshift ".length) == "unshift ") {
x.unshift(input.substring("unshift ".length)).then(dumpDatabase).catch(terminal.print);
} else if (input == "pop") {
x.pop().then(function(out) {
terminal.print("POPPED: ", out);
}).then(dumpDatabase).catch(terminal.print);
} else if (input == "shift") {
x.shift().then(function(out) {
terminal.print("SHIFTED: ", out);
}).then(dumpDatabase).catch(terminal.print);
} else if (input.substring(0, "get ".length) == "get ") {
let parts = input.split(" ");
x.get(parseInt(parts[1])).then(function(result) {
terminal.print(JSON.stringify(result))
}).catch(terminal.print);
} else {
dumpDatabase();
}
});
}

View File

@ -0,0 +1,201 @@
"use strict";
class Client {
async start() {
let self = this;
terminal.setTitle("Emoji Push");
terminal.setSendKeyEvents(true);
self.send({action: "ready"});
core.register("key", function(event) {
if (event.type == "keydown") {
switch (event.keyCode) {
case 37:
self.requestMove(-1, 0);
break;
case 38:
self.requestMove(0, -1);
break;
case 39:
self.requestMove(1, 0);
break;
case 40:
self.requestMove(0, 1);
break;
}
}
});
core.register("onMessage", function(sender, message) {
if (message.action == "display") {
terminal.cork();
terminal.clear();
terminal.print(message.board);
terminal.uncork();
}
});
}
async send(message) {
(await core.getService("server")).postMessage(message);
}
async requestMove(dx, dy) {
await this.send({action: "move", delta: [dx, dy]});
}
}
class Server {
constructor() {
this.rows = 17;
this.columns = 17;
this.players = {};
this.objects = [];
for (let i = 2; i < this.rows - 1; i += 2) {
for (let j = 2; j < this.columns - 1; j += 2) {
let block = new Entity();
block.position = [j, i];
block.symbol = '#';
this.objects.push(block);
}
}
}
async start() {
let self = this;
core.register("onMessage", function(sender, message) {
if (!self.players[sender.index]) {
let player = new Entity();
player.symbol = '@';
player.position = self.findNewPlayerPosition();
self.players[sender.index] = player;
self.objects.push(player);
}
switch (message.action) {
case "move":
self.move(sender.index, message.delta);
self.redisplay();
break;
case "ready":
self.redisplay();
break;
}
});
core.register("onSessionEnd", function(session) {
let player = self.players[session.index];
if (player) {
delete self.players[session.index];
self.objects.splice(self.objects.indexOf(player), 1);
self.redisplay();
}
});
}
occupied(position) {
let board = this.getBoard();
if (position[0] < 0 || position[1] < 0 || position[1] >= board.length || position[0] >= board[position[1]].length) {
return true;
}
if (board[position[1]].charAt(position[0]) != ' ') {
return true;
}
if (this.objects.some(x => position[0] == x.position[0] && position[1] == x.position[1])) {
return true;
}
}
move(index, delta) {
let newPosition = [
this.players[index].position[0] + delta[0],
this.players[index].position[1] + delta[1],
];
if (!this.occupied(newPosition)) {
this.players[index].position = newPosition;
}
}
findNewPlayerPosition() {
let best = null;
let bestDistance = -1;
let players = Object.values(this.players);
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (!this.occupied([j, i])) {
let distance = Math.min.apply(null, players.map(x => Math.sqrt(
(x.position[0] - j) * (x.position[0] - j)
+ (x.position[1] - i) * (x.position[1] - i))));
if (distance > bestDistance) {
best = [j, i];
bestDistance = distance;
}
}
}
}
return best;
}
redisplay() {
let board;
try {
board = this.getBoardString();
} catch (error) {
board = error;
}
core.broadcast({action: "display", board: board});
}
getBoard() {
let board = [];
for (let i = 0; i < this.rows; i++) {
let row = "";
for (let j = 0; j < this.columns; j++) {
if (i == 0 && j == 0) {
row += '┌';
} else if (i == 0 && j == this.columns - 1) {
row += '┐';
} else if (i == this.rows - 1 && j == 0) {
row += '└';
} else if (i == this.rows - 1 && j == this.columns - 1) {
row += '┘';
} else if (i == 0 || i == this.rows - 1) {
row += '─';
} else if (j == 0 || j == this.columns - 1) {
row += '│';
} else {
row += ' ';
}
}
board.push(row);
}
for (let object of this.objects) {
board[object.position[1]] = board[object.position[1]].slice(0, object.position[0]) + object.symbol + board[object.position[1]].slice(object.position[0] + 1);
}
return board;
}
getBoardString() {
let board = this.getBoard();
let colors = board.map(x => x.split("").map(y => '#fff'));
return board.map((r, row) => r.split("").map((v, column) => ({style: "color: " + colors[row][column], value: v})).concat(["\n"]));
}
}
class Entity {
constructor() {
this.position = [0, 0];
this.symbol = '?';
this.color = '#fff';
}
}
if (imports.terminal) {
new Client().start().catch(terminal.print);
} else {
new Server().start().catch(print);
}

View File

@ -0,0 +1,31 @@
"use strict";
//! {"category": "sample"}
async function main() {
terminal.print("Hi. What's your name?");
let name = await terminal.readLine();
terminal.print("Hello, " + name + ".");
let number = Math.floor(Math.random() * 100);
let guesses = 0;
while (true) {
terminal.print("Guess the number.");
try {
let guess = parseInt(await terminal.readLine());
guesses++;
if (guess < number) {
terminal.print("Too low.");
} else if (guess > number) {
terminal.print("Too high.");
} else {
terminal.print("You got it in " + guesses.toString() + " guesses! It was " + number.toString() + ". Good job, " + name + ".");
break;
}
} catch (error) {
terminal.print(error);
}
}
}
main().catch(terminal.print);

View File

@ -0,0 +1,3 @@
core.register("fileDrop", function(event) {
terminal.print({image: event.file});
});

View File

@ -0,0 +1,260 @@
"use strict";
terminal.setEcho(false);
terminal.setPrompt("🍔");
terminal.split([
{
type: "horizontal",
children: [
{
name: "form",
},
{
type: "vertical",
basis: "240px",
grow: "0",
shrink: "0",
children: [
{
name: "image",
basis: "8em",
shrink: "0",
},
{
name: "fizzbuzz",
grow: "1",
},
],
},
],
},
]);
database.getAll().then(function(data) {
for (var i = 0; i < data.length; i++) {
database.remove(data[i]);
}
});
let kBurger = ` ......
/'.'.'.\\
&%@%@%@&
(######)
\\,,,,,,/`;
terminal.select("image");
terminal.print(kBurger);
terminal.select("form");
function countUp() {
var current = 0;
function countUpNext() {
terminal.select("fizzbuzz");
++current;
var result = "";
if (current % 3 == 0) {
result += "Fizz";
}
if (current % 5 == 0) {
result += "Buzz";
}
if (current % 150 == 0) {
result += "Grumble";
}
if (result == "") {
result = current.toString();
}
terminal.print(result);
terminal.select("form");
setTimeout(countUpNext, 1000);
};
setTimeout(countUpNext, 1000);
}
countUp();
var user = core.user.name;
let kIntroduction = [
{style: "font-size: xx-large; font-family: sans", value: "Summer of Cory"},
"Hi, it is warm outside again, and as is tradition, I want you to join me "+
"after work for some burgers on my deck. There are some quick questions "+
"below to gauge interest and availability. Please answer as best you can "+
"by clicking the options below, and I will get back to you with a plan.",
[],
];
let kBottom = [
{
iframe: `<html style="width: 100%; height: 100%; margin: 0; padding: 0">
<body style="width: 100%; height: 100%; margin: 0; padding: 0">
<textarea id="text" style="left: 0; top: 0; width: 100vw; height: 100vh; resize: none; margin: 0; padding: 0"></textarea>
</body>
</html>
`,
style: "border: 0; width: 640px; height: 240px; margin: 0; padding: 0",
},
];
let kQuestions = [
{text: "Do you want to be a part of the Summer of Cory?", options: ["yes", "no"]},
{text: "Tuesday after work works best for me. Does that work for you?", options: ["yes", "no"]},
{text: "How often would you expect to join us?", options: ["weekly", "fortnightly", "monthly", "impossible to predict"]},
//{text: "Pick numbers", options: ["1", "2", "3"], allowMultiple: true},
//{text: "Pick a letter", options: ["a", "b", "c", "d"]},
//{text: "Pick whatever", options: ["1", "2", "3", "a", "b", "c", "d"], writeIn: true},
];
let choices = [];
let writeIn = [];
let writingIn = undefined;
function getOptions(index) {
return kQuestions[index].options.concat(writeIn[index] || []);
}
function isChosen(question, choice) {
let selected = false;
if (kQuestions[question].allowMultiple) {
selected = choices[question] && choices[question].indexOf(choice) != -1;
} else {
selected = choices[question] == choice;
}
return selected;
}
function readData() {
return database.getAll().then(function(keys) {
var promises = [];
for (var i = 0; i < keys.length; i++) {
promises.push(database.get(keys[i]));
}
return Promise.all(promises);
}).then(function(allData) {
writeIn = [];
for (var i = 0; i < allData.length; i++) {
if (allData[i]) {
var entry = JSON.parse(allData[i]);
if (entry && entry.writeIn) {
for (var j = 0; j < entry.writeIn.length; j++) {
if (writeIn.indexOf(entry.writeIn[j]) == -1) {
writeIn.push(entry.writeIn[j]);
}
}
}
}
}
return database.get("data_" + user);
}).then(function(data) {
if (data) {
choices = JSON.parse(data).choices;
}
}).catch(function(error) {
terminal.select("image");
terminal.print(error);
terminal.select("form");
});
}
function writeData() {
return database.set("data_" + user, JSON.stringify({choices: choices, writeIn: writeIn})).catch(function(error) {
terminal.select("image");
terminal.print(error);
terminal.select("form");
});
}
function render() {
return readData().then(function() {
terminal.cork();
try {
terminal.clear();
for (let line in kIntroduction) {
terminal.print(kIntroduction[line]);
}
let allowFill = writingIn === undefined;
for (let i in kQuestions) {
let availableOptions = getOptions(i);
let options = [];
for (let j in availableOptions) {
options.push(" ");
let value = (isChosen(i, availableOptions[j]) ? "☒" : "☐") + availableOptions[j];
if (allowFill) {
options.push({
command: JSON.stringify({action: "toggle", question: i, option: availableOptions[j]}),
value: value,
});
} else {
options.push(value);
}
}
if (kQuestions[i].writeIn) {
options.push(" or ");
if (writingIn !== undefined) {
options.push("write in another option below")
options.push(" ");
options.push({command: JSON.stringify({action: "endWriteIn"}), value: "cancel"});
} else {
options.push({command: JSON.stringify({action: "beginWriteIn", question: i}), value: "write in another option"});
}
}
terminal.print(kQuestions[i].text, ":", options);
terminal.print();
}
} finally {
for (let line in kBottom) {
terminal.print(kBottom[line]);
}
terminal.uncork();
}
});
}
core.register("onInput", function(input) {
if (writingIn !== undefined && input != "cancel") {
readData().then(function() {
if (!writeIn[writingIn]) {
writeIn[writingIn] = [];
}
if (input && writeIn[writingIn].indexOf(input) == -1) {
writeIn[writingIn].push(input);
}
writingIn = undefined;
terminal.setPrompt("> ");
return writeData();
}).then(render);
} else {
let command = JSON.parse(input);
if (command.action == "toggle") {
readData().then(function() {
if (kQuestions[command.question].allowMultiple) {
if (choices[command.question] && choices[command.question].indexOf(command.option) != -1) {
choices[command.question].splice(choices[command.question].indexOf(command.option), 1);
} else {
if (!choices[command.question]) {
choices[command.question] = [];
}
choices[command.question].push(command.option);
}
} else {
choices[command.question] = command.option;
}
return writeData();
}).then(render);
} else if (command.action == "beginWriteIn") {
writingIn = command.question;
terminal.setPrompt('Enter new option for "' + kQuestions[command.question].text + '": ');
render();
} else if (command.action == "endWriteIn") {
writingIn = undefined;
terminal.setPrompt("> ");
render();
}
}
});
render().catch(function(error) {
terminal.print(error);
});

View File

@ -0,0 +1,179 @@
"use strict";
//! {"require": ["smtp"], "permissions": ["network"]}
let administrator = core.user.name == core.user.packageOwner;
function lameHash(value) {
let result = 12945;
for (let i = 0; i < value.length; i++) {
result += value.charCodeAt(i);
result *= 31;
result &= 0x7fffffff;
}
return result;
}
terminal.setEcho(false);
if (administrator) {
terminal.print({style: "font-size: x-large", value: "Edit", command: "command:" + JSON.stringify("command")});
}
let gInvite = null;
function display() {
return database.get("invite").then(function(data) {
if (data) {
let invite = JSON.parse(data);
let promises = [];
for (let i in invite.emails) {
promises.push(database.get("rsvp." + lameHash(invite.emails[i])));
}
return Promise.all(promises).then(function(rsvps) {
for (let i in rsvps) {
if (rsvps[i]) {
rsvps[i] = JSON.parse(rsvps[i]);
}
}
terminal.print({style: "font-size: xx-large", value: invite.title});
terminal.print(invite.message);
for (let i in invite.food) {
let line = [invite.food[i], "?", " ", "+", " ", "-"];
// hi
terminal.print({style: "font-size: x-large", value: line});
}
gInvite = invite;
});
}
}).catch(function(error) {
terminal.print(error);
});
}
core.register("hashChange", function(event) {
let hash = event.hash;
if (hash && hash.charAt(0) == '#') {
hash = hash.substring(1);
}
if (hash) {
let invite = gInvite || {};
for (let i in invite.emails) {
let email = invite.emails[i];
if (lameHash(email).toString() == hash) {
terminal.print("hash match!", email);
}
}
}
})
core.register("submit", function(event) {
let invite = event.value;
invite.emails = invite.emails.split(",");
invite.food = invite.food.split(",");
for (let i in invite.emails) {
let url = "https://www.tildefriends.net/~cory/invite2#" + lameHash(invite.emails[i]).toString();
terminal.print({href: url});
}
database.set("invite", JSON.stringify(invite));
});
display();
core.register("onInput", function(input) {
if (input.substring(0, "command:".length) == "command:") {
command = JSON.parse(input.substring("command:".length));
if (command == "create") {
let invite = gInvite || {};
terminal.print("Title:", {input: "text", name: "title", style: "width: 512px", value: invite.title});
terminal.print("Message:", {input: "text", name: "message", style: "width: 512px", value: invite.message});
terminal.print("Email Addresses:", {input: "text", name: "emails", style: "width: 512px", value: invite.emails.join(",")});
terminal.print("Food:", {input: "text", name: "food", style: "width: 512px", value: invite.food.join(",")});
terminal.print({input: "submit", name: "Create", value: "Create"});
}
}
});
/*
require("smtp").sendMail({
from: core.user.name + "@unprompted.com",
to: "test1@unprompted.com",
subject: input,
body: input,
}).then(function() {
terminal.print("sent");
}).catch(function(error) {
terminal.print("error: ", error);
});
*/
let kEmojis = [
"🍇 Grapes",
"🍈 Melon",
"🍉 Watermelon",
"🍊 Tangerine",
"🍋 Lemon",
"🍌 Banana",
"🍍 Pineapple",
"🍎 Red Apple",
"🍏 Green Apple",
"🍐 Pear",
"🍑 Peach",
"🍒 Cherries",
"🍓 Strawberry",
"🍅 Tomato",
"🍆 Aubergine",
"🌽 Ear of Maize",
"🌶 Hot Pepper",
"🍄 Mushroom",
"🌰 Chestnut",
"🍞 Bread",
"🧀 Cheese Wedge",
"🍖 Meat on Bone",
"🍗 Poultry Leg",
"🍔 Hamburger",
"🍟 French Fries",
"🍕 Slice of Pizza",
"🌭 Hot Dog",
"🌮 Taco",
"🌯 Burrito",
"🍳 Cooking",
"🍲 Pot of Food",
"🍿 Popcorn",
"🍱 Bento Box",
"🍘 Rice Cracker",
"🍙 Rice Ball",
"🍚 Cooked Rice",
"🍛 Curry and Rice",
"🍜 Steaming Bowl",
"🍝 Spaghetti",
"🍠 Roasted Sweet Potato",
"🍢 Oden",
"🍣 Sushi",
"🍤 Fried Shrimp",
"🍥 Fish Cake With Swirl Design",
"🍡 Dango",
"🍦 Soft Ice Cream",
"🍧 Shaved Ice",
"🍨 Ice Cream",
"🍩 Doughnut",
"🍪 Cookie",
"🎂 Birthday Cake",
"🍰 Shortcake",
"🍫 Chocolate Bar",
"🍬 Candy",
"🍭 Lollipop",
"🍮 Custard",
"🍯 Honey Pot",
"🍼 Baby Bottle",
"☕ Hot Beverage",
"🍵 Teacup Without Handle",
"🍶 Sake Bottle and Cup",
"🍾 Bottle With Popping Cork",
"🍷 Wine Glass",
"🍸 Cocktail Glass",
"🍹 Tropical Drink",
"🍺 Beer Mug",
"🍻 Clinking Beer Mugs",
"🍽 Fork and Knife With Plate",
"🍴 Fork and Knife",
];

View File

@ -0,0 +1,936 @@
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,
});

View File

@ -23,6 +23,7 @@ function parseUrl(url) {
function parseResponse(data) { function parseResponse(data) {
var firstLine; var firstLine;
var headers = {}; var headers = {};
var headerArray = [];
while (true) { while (true) {
var endLine = data.indexOf("\r\n"); var endLine = data.indexOf("\r\n");
@ -33,11 +34,14 @@ function parseResponse(data) {
break; break;
} else { } else {
var colon = line.indexOf(":"); var colon = line.indexOf(":");
headers[line.substring(0, colon).toLowerCase()] = line.substring(colon + 1).trim(); var key = line.substring(0, colon);
var value = line.substring(colon + 1).trim();
headers[key.toLowerCase()] = value;
headerArray.push([key, value]);
} }
data = data.substring(endLine + 2); data = data.substring(endLine + 2);
} }
return {body: data, headers: headers}; return {body: data, headers: headers, headerArray: headerArray};
} }
function get(url) { function get(url) {

View File

@ -0,0 +1,137 @@
"use strict";
//! {"category": "libraries"}
const kRpcSource = `
class RPC {
constructor() {
this._functions = [];
this.exports = null;
this.sendCallback = null;
}
export(functions) {
return this._send(["export", functions]);
}
_call(index) {
return this._send(["call", index, Array.prototype.slice.call(arguments, 1)]);
}
_send(message) {
var serialized = this._serialize(message);
if (this.sendCallback) {
return this.sendCallback(serialized);
}
return serialized;
}
deliver(message) {
var deserialized = this._deserialize(message);
if (deserialized[0] == "export") {
this.exports = deserialized[1];
} else if (deserialized[0] == "call") {
var f = this._functions[deserialized[1]];
f.apply(null, deserialized[2]);
}
}
_deserialize(data) {
var result = null;
if (data[0] == "array") {
result = new Array(data[1].length);
for (var i = 0; i < data[1].length; i++) {
result[i] = this._deserialize(data[1][i]);
}
} else if (data[0] == "object") {
result = {};
for (var i in data[1]) {
result[i] = this._deserialize(data[1][i]);
}
} else if (data[0] == "number" || data[0] == "boolean" || data[0] == "string") {
result = data[1];
} else if (data[0] == "function") {
result = this._call.bind(this, data[1]);
}
return result;
}
_serialize(data) {
var result = null;
if (Array.isArray(data)) {
result = ["array", data.map(x => this._serialize(x, this.functions))];
} else if (typeof data == "function") {
var index = this._functions.indexOf(data);
if (index == -1) {
index = this._functions.length;
this._functions.push(data);
}
result = ["function", index];
} else if (typeof data == "object") {
var fields = {};
while (data) {
var own = Object.getOwnPropertyNames(data);
for (var i = 0; i < own.length; i++) {
var name = own[i];
if (name != "constructor") {
var descriptor = Object.getOwnPropertyDescriptor(data, name);
if (descriptor&& typeof descriptor.value == "function") {
fields[name] = this._serialize(descriptor.value, this.functions);
}
}
}
if (data.__proto__ == Object.prototype) {
break;
}
data = Object.getPrototypeOf(data);
}
result = ["object", fields];
} else {
result = [typeof data, data];
}
return result;
}
}`
exports.iframe = function(options) {
return new Promise(function(resolve, reject) {
var iframeName = options.name || "iframe";
var RPC = eval(kRpcSource);
var rpc = new RPC();
rpc.sendCallback = terminal.postMessageToIframe.bind(null, iframeName);
core.register("onWindowMessage", function(message) {
if (message.message == "ready") {
resolve(rpc.exports);
} else {
rpc.deliver(message.message);
}
});
terminal.split([{name: "terminal", type: "vertical"}]);
terminal.print({
iframe: `
<!DOCTYPE html>
<body style="background-color: #fff"></body>
<script language="javascript">
${kRpcSource}
window.addEventListener("load", function() {
parent.postMessage("ready", "*");
});
let rpc = new RPC();
window.addEventListener("message", function(event) {
rpc.deliver(event.data);
});
rpc.sendCallback = function(data) {
parent.postMessage(data, "*");
}
${options.source}
</script>
`,
name: iframeName,
style: "flex: 1 1; " + options.style,
width: null,
height: null,
});
});
}

View File

@ -98,6 +98,8 @@ class IrcService {
let person = prefix.split('!')[0]; let person = prefix.split('!')[0];
let conversation = parts[1]; let conversation = parts[1];
this._service.notifyPresenceChanged(conversation, person, "unavailable"); this._service.notifyPresenceChanged(conversation, person, "unavailable");
} else if (parts[0] == "PONG") {
// this._service.notifyMessageReceived("", {from: prefix, message: "RTT: " + (new Date().getTime() - parseInt(parts[2]))});
} else if (parts[0] == "QUIT") { } else if (parts[0] == "QUIT") {
let person = prefix.split('!')[0]; let person = prefix.split('!')[0];
let conversations = this._service.getConversations(); let conversations = this._service.getConversations();
@ -164,9 +166,15 @@ class IrcService {
self._service.notifyStateChanged("connected"); self._service.notifyStateChanged("connected");
self._send("USER " + options.user + " 0 * :" + options.realName); self._send("USER " + options.user + " 0 * :" + options.realName);
self._send("NICK " + options.nick); self._send("NICK " + options.nick);
self._sendPing();
}).catch(self._service.reportError); }).catch(self._service.reportError);
} }
_sendPing() {
this._send("PING :" + new Date().getTime());
setTimeout(this._sendPing.bind(this), 3000);
}
sendMessage(target, text) { sendMessage(target, text) {
if (!target) { if (!target) {
this._socket.write(text + "\r\n"); this._socket.write(text + "\r\n");

View File

@ -771,11 +771,11 @@ class XmppService {
} else if (stanza.attributes.id == "session0") { } else if (stanza.attributes.id == "session0") {
self._socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>"); self._socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>");
self._schedulePing(); self._schedulePing();
//self._conversations["chadhappyfuntime@conference.jabber.troubleimpact.com"] = {participants: [], history: []};
} else if (stanza.children.length && stanza.children[0].name == "ping") { } else if (stanza.children.length && stanza.children[0].name == "ping") {
// Ping response. // Ping response.
} else { } else {
self._service.notifyMessageReceived(null, {unknown: stanza}); self._service.notifyMessageReceived(null, {unknown: stanza});
self._socket.write(`<iq id="${stanza.attributes.id}" type="error" from="${stanza.attributes.to}" to="${stanza.attributes.from}"><error type="cancel"><item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>`);
} }
} else if (stanza.name == "message") { } else if (stanza.name == "message") {
let message = self._convertMessage(stanza); let message = self._convertMessage(stanza);

View File

@ -0,0 +1,128 @@
//! {"permissions": ["network"], "require": ["libencoding", "libhttp"]}
let libhttp = require("libhttp");
async function getEvents() {
let now = new Date();
let cacheVersion = "1";
let useCache = await database.get("cacheVersion") == cacheVersion;
let events = [];
let url = "https://api.meetup.com/The-Most-Informal-Running-Club-Ever-TMIRCE-Upstate/events?&sign=true&photo-host=public&status=past";
let done = false;
while (!done) {
let response;
if (useCache) {
response = await database.get(url);
if (response) {
response = JSON.parse(response);
}
}
if (!response) {
response = await libhttp.get(url);
await database.set(url, JSON.stringify(response));
}
let nextLink;
let theseEvents = JSON.parse(response.body);
for (let j in theseEvents) {
if (new Date(theseEvents[j].time) < now) {
events.push(theseEvents[j]);
} else {
done = true;
break;
}
}
for (let j in response.headerArray) {
let link = response.headerArray[j];
if (link[0] == "Link" && link[1].split("; ")[1] == 'rel="next"') {
link = link[1].split("; ")[0];
link = link.substring(1, link.length - 1);
nextLink = link;
}
}
if (nextLink) {
url = nextLink;
} else {
break;
}
}
await database.set("cacheVersion", cacheVersion);
return events;
}
async function trackWorkouts(events) {
for (let i in events) {
let event = events[i];
if (event && event.venue && event.venue.name
&& new Date(event.time).getDay() == 1) {
let match = /follow (?:my|our) (.*?) workout/.exec(event.description);
let workout;
if (match) {
workout = match[1];
} else if (/snowshoe/.exec(event.description)
|| /No run/.exec(event.description)) {
// nothing
} else {
terminal.print(event.description);
}
if (workout) {
terminal.print(workout);
}
}
}
}
async function locationByDay(day, events) {
for (let i in events) {
let event = events[i];
if (event && event.venue && event.venue.name
&& new Date(event.time).getDay() == day) {
terminal.print(event.venue.name);
}
}
}
async function redisplay(action, actions, events) {
terminal.clear();
if (actions[action]) {
terminal.setHash(action);
terminal.print({style: "font-size: xx-large", value: action});
}
for (let i in actions) {
terminal.print({command: i});
}
if (actions[action]) {
await actions[action](events);
}
}
async function main() {
terminal.print("loading events...");
let events = await getEvents();
terminal.print("loaded ", events.length.toString(), " events");
let actions = {
"Sunday Locations": locationByDay.bind(null, 0),
"Monday Track Workouts": trackWorkouts,
"Tuesday Locations": locationByDay.bind(null, 2),
"Wednesday Locations": locationByDay.bind(null, 3),
"Thursday Locations": locationByDay.bind(null, 4),
"Friday Locations": locationByDay.bind(null, 5),
"Saturday Locations": locationByDay.bind(null, 6),
}
core.register("hashChange", function(event) {
if (event.hash && event.hash[0] == '#') {
terminal.clear();
let hash = event.hash.substring(1);
redisplay(hash, actions, events);
}
});
let action = null;
while (true) {
redisplay(action, actions, events);
action = await terminal.readLine();
}
}
main().catch(terminal.print);

View File

@ -0,0 +1,15 @@
if (imports.terminal) {
core.register("onMessage", function(sender, message) {
terminal.print(JSON.stringify(core.user.index), " ", message);
});
core.register("onInput", function(input) {
core.getService("global").then(function(service) {
service.postMessage(input);
});
});
} else {
core.register("onMessage", function(sender, message) {
core.broadcast(message);
});
}

View File

@ -0,0 +1,30 @@
"use strict";
//! {"category": "tests"}
terminal.print("Time to buy a new life jacket.");
/*
terminal.print("hi");
terminal.print("here is some svg:");
terminal.print({svg: {
attributes: {
width: 32,
height: 32,
},
children: [
{
name: "circle",
attributes: {
cx: 16,
cy: 16,
r: 15,
stroke: "green",
"stroke-width": "4",
fill: "yellow",
},
},
],
}});
terminal.print("end of svg");*/

View File

@ -0,0 +1,33 @@
"use strict";
//! {"category": "tests"}
terminal.print("Hello, world!");
terminal.split([
{name: "h1", grow: 4},
{name: "h2", grow: 1},
{type: "vertical", children: [
{name: "h3.v1"},
{name: "h3.v2"},
{type: "horizontal", children: [
{name: "h3.v3.h1"},
{name: "h3.v3.h2"},
{name: "h3.v3.h3"},
]},
{name: "h3.v4"},
]},
{name: "h4"}
]);
function multiprint(text) {
var terminals = ["h1", "h2", "h3.v1", "h3.v2", "h3.v3.h1", "h3.v3.h2", "h3.v3.h3", "h3.v4", "h4"];
for (var i = 0; i < terminals.length; i++) {
terminal.select(terminals[i]);
terminal.print({style: "color: red", value: terminals[i]}, " ", text);
}
}
multiprint("hello");
core.register("onInput", function(input) {
multiprint(input);
});

View File

@ -0,0 +1,208 @@
"use strict";
//! {"require": ["libiframe"]}
let isClient = imports.terminal != null;
if (isClient) {
require("libiframe").iframe({
source: `
document.documentElement.setAttribute("style", "width: 100%; height: 100%; margin: 0; padding: 0");
document.body.setAttribute("style", "display: flex; flex-direction: column; height: 100%; width: 100%; margin: 0; padding: 0");
document.body.style.color = '#fff';
document.body.style.backgroundColor = '#000';
var canvas = document.createElement("canvas");
canvas.setAttribute("style", "flex: 1 1; image-rendering: pixelated");
canvas.width = 128;
canvas.height = 128;
document.body.append(canvas);
var buttonBar = document.createElement("div");
buttonBar.setAttribute("style", "flex: 0 1 1in; display: flex; flex-direction: row");
var buttons = ["<", "^", "v", ">", "Fire!"];
for (var i = 0; i < buttons.length; i++) {
var button = document.createElement("input");
button.setAttribute("type", "button");
button.setAttribute("style", "flex: 1 1");
button.value = buttons[i];
button.onclick = buttonClicked.bind(null, buttons[i]);
buttonBar.append(button);
}
document.body.append(buttonBar);
var buttonCallback;
function buttonClicked(button) {
if (buttonCallback) {
buttonCallback(button);
}
}
var kExportNames = [
"beginPath",
"fill",
"save",
"restore",
"scale",
"rotate",
"translate",
"transform",
"arc",
"moveTo",
"lineTo",
"fillRect",
"fillText",
"stroke",
];
var context = canvas.getContext("2d");
var exports = {};
for (var i = 0; i < kExportNames.length; i++) {
exports[kExportNames[i]] = context[kExportNames[i]].bind(context);
}
exports.setButtonCallback = function(callback) { buttonCallback = callback; };
exports.setFillStyle = function(style) { context.fillStyle = style; };
exports.setStrokeStyle = function(style) { context.strokeStyle = style; };
exports.setLineWidth = function(width) { context.lineWidth = width; };
rpc.export(exports);
`,
style: `
border: 0;
`}).then(initialize).catch(terminal.print);
function initialize(iframe) {
iframe.setButtonCallback(click);
draw(iframe);
core.register("onMessage", onMessage.bind(null, iframe));
core.getService("server").then(function(server) {
server.postMessage({ready: true});
});
}
async function click(button) {
(await core.getService("server")).postMessage({button: button});
}
function draw(iframe) {
iframe.setFillStyle('#222');
iframe.fillRect(0, 0, 640, 480);
}
function drawTank(iframe, x, y, color, angle) {
iframe.save();
iframe.setLineWidth(1);
iframe.setFillStyle(color);
iframe.setStrokeStyle(color);
iframe.translate(x, y);
iframe.beginPath();
iframe.arc(0, 0, 4, Math.PI, 2 * Math.PI);
iframe.fill();
iframe.beginPath();
var radians = Math.PI + angle * Math.PI / 180.0;
iframe.moveTo(Math.cos(radians) * 2, Math.sin(radians) * 2 - 0.5);
iframe.lineTo(Math.cos(radians) * 10, Math.sin(radians) * 10 - 0.5);
iframe.stroke();
iframe.restore();
}
function onMessage(iframe, sender, message) {
iframe.setFillStyle('#222');
iframe.fillRect(0, 0, 640, 480);
for (let i = 0; i < message.users.length; i++) {
var tank = message.users[i];
drawTank(iframe, tank.position.x, tank.position.y, tank.user == core.user.index ? '#f00' : '#0f0', tank.angle);
}
iframe.setFillStyle('#0f0');
for (let i = 0; i < message.terrain.length; i++) {
let h = message.terrain[i];
iframe.fillRect(i, 128 - h, i + 1, h);
}
}
} else {
let users = {};
let terrain = new Array(128);
let updating = false;
function startUpdating() {
if (!updating) {
setTimeout(100, update);
updating = true;
}
}
function update() {
updating = false;
for (let u of Object.values(users)) {
user.position.y++;
updating = true;
}
broadcastUsers();
if (updating) {
setTimeout(100, update);
}
}
function initializeTerrain() {
for (let i = 0; i < terrain.length; i++) {
terrain[i] = 32;
}
}
function broadcastUsers() {
core.broadcast({
users: Object.keys(users).map(x => ({user: x, position: users[x].position, angle: users[x].angle})),
terrain: terrain,
});
}
function placeNewUser() {
let positions = Object.keys(users).map(x => users[x].position.x);
var best = 128 / 2;
var bestDistance = -1;
if (positions.length) {
for (var i = 10; i < 118; i++) {
var thisDistance = Math.min.apply(null, positions.map(x => Math.abs(i - x)));
if (thisDistance > bestDistance) {
bestDistance = thisDistance;
best = i;
}
}
}
return {x: best, y: 10, d: bestDistance};
}
initializeTerrain();
core.register("onMessage", function(sender, message) {
if (message.ready) {
users[sender.index] = {
user: sender,
angle: 0,
velocity: 100,
position: placeNewUser(),
};
broadcastUsers();
startUpdating();
} else if (message.button) {
switch (message.button) {
case "<":
users[sender.index].angle = Math.round(Math.max(users[sender.index].angle - 5.0, 0.0));
break;
case ">":
users[sender.index].angle = Math.round(Math.min(users[sender.index].angle + 5.0, 180.0));
break;
case "^":
users[sender.index].velocity++;
break;
case "v":
users[sender.index].velocity--;
break;
}
broadcastUsers();
}
});
core.register("onSessionEnd", function(user) {
delete users[user.index];
broadcastUsers();
});
}

View File

@ -0,0 +1,13 @@
async function main() {
terminal.print("Hello, world!");
terminal.print("Please enter your name:");
var name = await terminal.readLine();
terminal.print("Hello, " + name + ".");
for (var i = 0; i < 5; i++) {
terminal.print(i.toString());
}
}
main().catch(terminal.print);

View File

@ -0,0 +1,187 @@
"use strict";
//! {"permissions": ["network"], "require": ["libencoding", "libhttp"]}
let libhttp = require("libhttp");
async function getEvents() {
let now = new Date();
let cacheVersion = "1-" + now.toDateString();
if (await database.get("cacheVersion") != cacheVersion) {
terminal.print("Refreshing database...");
let keys = await database.getAll();
await Promise.all(keys.map(x => database.remove(x)));
await database.set("cacheVersion", cacheVersion);
}
let events = [];
let url = "https://api.meetup.com/The-Most-Informal-Running-Club-Ever-TMIRCE-Upstate/events?&sign=true&photo-host=public&status=past";
let done = false;
while (!done) {
let response;
response = await database.get(url);
if (response) {
response = JSON.parse(response);
}
if (!response) {
response = await libhttp.get(url);
await database.set(url, JSON.stringify(response));
}
let nextLink;
let theseEvents = JSON.parse(response.body);
for (let j in theseEvents) {
if (new Date(theseEvents[j].time) < now) {
events.push(theseEvents[j]);
} else {
done = true;
break;
}
}
for (let j in response.headerArray) {
let link = response.headerArray[j];
if (link[0] == "Link" && link[1].split("; ")[1] == 'rel="next"') {
link = link[1].split("; ")[0];
link = link.substring(1, link.length - 1);
nextLink = link;
}
}
if (nextLink) {
url = nextLink;
} else {
break;
}
}
return events;
}
async function trackWorkouts(events) {
let results = [];
for (let i in events) {
let event = events[i];
if (event && event.venue && event.venue.name
&& new Date(event.time).getDay() == 1) {
let match = /follow (?:my|our) (.*?) workout/.exec(event.description);
let workout;
if (match) {
workout = match[1];
} else if (/snowshoe/.exec(event.description)
|| /No run/.exec(event.description)) {
// nothing
} else {
results.push(event.description);
}
if (workout) {
results.push(workout);
}
}
}
return results;
}
async function locationByDay(day, events) {
let results = [];
for (let i in events) {
let event = events[i];
if (event && event.venue && event.venue.name
&& new Date(event.time).getDay() == day) {
results.push(event.venue.name);
}
}
return results;
}
async function redisplay(action, filter, actions, filters, events) {
//terminal.cork();
terminal.clear();
if (actions[action]) {
terminal.setHash(action);
terminal.print({style: "font-size: xx-large", value: action});
}
terminal.print("What:");
for (let i in actions) {
terminal.print(action == i ? "-> " : " * ", {command: i});
}
terminal.print("How:");
for (let i in filters) {
terminal.print(filter == i ? "-> " : " * ", {command: i});
}
terminal.print("--");
if (actions[action]) {
filters[filter](await actions[action](events));
}
//terminal.uncork();
}
function rawFilter(items) {
for (let i in items) {
terminal.print(items[i]);
}
}
function histogramFilter(items) {
let table = {};
for (let i in items) {
if (!table[items[i]]) {
table[items[i]] = 0;
}
table[items[i]] += 1;
}
let results = [];
for (let i in table) {
results.push([table[i], i]);
}
results.sort((x, y) => y[0] - x[0]);
for (let i in results) {
terminal.print(results[i][0].toString(), " ", results[i][1]);
}
}
async function main() {
terminal.print("loading events...");
let events = await getEvents();
terminal.print("loaded ", events.length.toString(), " events");
let actions = {
"Sunday Locations": locationByDay.bind(null, 0),
"Monday Track Workouts": trackWorkouts,
"Tuesday Locations": locationByDay.bind(null, 2),
"Wednesday Locations": locationByDay.bind(null, 3),
"Thursday Locations": locationByDay.bind(null, 4),
"Friday Locations": locationByDay.bind(null, 5),
"Saturday Locations": locationByDay.bind(null, 6),
}
let filters = {
"Chronological": rawFilter,
"Histogram": histogramFilter,
};
let action = null;
let filter = "Chronological";
core.register("hashChange", function(event) {
if (event.hash && event.hash[0] == '#') {
terminal.clear();
let hash = event.hash.substring(1);
if (filters[hash]) {
filter = hash;
} else if (actions[hash]) {
action = hash;
}
redisplay(action, filter, actions, filters, events);
}
});
while (true) {
redisplay(action, filter, actions, filters, events);
let command = await terminal.readLine();
if (filters[command]) {
filter = command;
} else if (actions[command]) {
action = command;
}
}
}
main().catch(terminal.print);

View File

@ -0,0 +1,143 @@
"use strict";
//! {"require": ["ui"]}
terminal.setEcho(false);
terminal.setTitle("What Next?");
let gEditEvent = null;
function back() {
terminal.split([{name: "terminal"}]);
if (gEditEvent) {
gEditEvent.back();
}
}
core.register("onWindowMessage", function(event) {
if (event.message.ready) {
terminal.postMessageToIframe("iframe", {title: gEditEvent.name, contents: gEditEvent.value});
} else if (event.message.index) {
back();
} else {
gEditEvent.save(event.message.title, event.message.contents).then(back);
}
});
function editPage(event) {
gEditEvent = event;
terminal.split([{name: "terminal", type: "vertical"}]);
terminal.clear();
terminal.print({iframe: `<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.css"></link>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/theme/base16-dark.min.css"></link>
<style>
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
padding: 0;
}
#menu {
flex: 0 0 auto;
}
#container {
flex: 1 1 auto;
display: flex;
flex-direction: row;
width: 100%;
background-color: white;
}
.CodeMirror {
width: 100%;
height: 100%;
}
.CodeMirror-scroll {
}
.cm-tab {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
background-position: right;
background-repeat: no-repeat;
-webkit-filter: invert(100%);
filter: invert(100%);
}
.cm-trailingspace {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
#edit { background-color: white }
#preview { background-color: white }
#edit, #preview {
display: flex;
overflow: auto;
flex: 0 0 50%;
}
</style>
<script>
var gEditor;
function index() {
parent.postMessage({index: true}, "*");
}
function submit() {
var contents = gEditor.getValue();
var lines = contents.split("\\n");
var title = lines[0].trim();
parent.postMessage({
title: title,
contents: contents,
}, "*");
}
function textChanged() {
var preview = document.getElementById("preview");
preview.innerText = gEditor.getValue();
}
window.addEventListener("message", function(message) {
gEditor.setValue(message.data.contents);
textChanged();
}, false);
window.addEventListener("load", function() {
gEditor = CodeMirror.fromTextArea(document.getElementById("contents"), {
theme: 'base16-dark',
lineNumbers: true,
tabSize: 4,
intentUnit: 4,
indentWithTabs: true,
showTrailingSpace: true,
});
gEditor.on("change", textChanged);
});
parent.postMessage({ready: true}, "*");
</script>
</head>
<body>
<div id="menu">
<input type="button" value="Back" onclick="index()">
` + (core.user.credentials.permissions && core.user.credentials.permissions.authenticated ? `
<input type="button" value="Save" onclick="submit()">
` : "") + `
</div>
<div id="container">
<div id="edit"><textarea id="contents" oninput="textChanged()"></textarea></div>
<div id="preview"></div>
</div>
</body>
</html>`, name: "iframe", style: "flex: 1 1 auto; border: 0; width: 100%"});
}
require("ui").fileList({
title: "What Next?",
edit: editPage,
});

View File

@ -32,6 +32,7 @@ function editPage(event) {
<head> <head>
<script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.css"></link> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/codemirror.min.css"></link>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.13.2/theme/base16-dark.min.css"></link>
<style> <style>
html { html {
height: 100%; height: 100%;
@ -61,6 +62,20 @@ function editPage(event) {
} }
.CodeMirror-scroll { .CodeMirror-scroll {
} }
.cm-tab {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
background-position: right;
background-repeat: no-repeat;
-webkit-filter: invert(100%);
filter: invert(100%);
}
.cm-trailingspace {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
#edit { background-color: white } #edit { background-color: white }
#preview { background-color: white } #preview { background-color: white }
#edit, #preview { #edit, #preview {
@ -98,7 +113,12 @@ function editPage(event) {
window.addEventListener("load", function() { window.addEventListener("load", function() {
gEditor = CodeMirror.fromTextArea(document.getElementById("contents"), { gEditor = CodeMirror.fromTextArea(document.getElementById("contents"), {
lineNumbers: true theme: 'base16-dark',
lineNumbers: true,
tabSize: 4,
intentUnit: 4,
indentWithTabs: true,
showTrailingSpace: true,
}); });
gEditor.on("change", textChanged); gEditor.on("change", textChanged);
}); });

View File

@ -0,0 +1,176 @@
"use strict";
const kWidth = 15;
const kHeight = 10;
class Client {
async start() {
let self = this;
terminal.setTitle("Xmas 2016");
terminal.setSendKeyEvents(true);
terminal.split([{name: "terminal", style: "text-align: center"}]);
self.send({action: "ready"});
core.register("key", function(event) {
if (event.type == "keydown") {
switch (event.keyCode) {
case 37:
self.requestMove(-1, 0);
break;
case 38:
self.requestMove(0, -1);
break;
case 39:
self.requestMove(1, 0);
break;
case 40:
self.requestMove(0, 1);
break;
}
}
});
core.register("onMessage", function(sender, message) {
if (message.action == "display") {
terminal.cork();
terminal.clear();
terminal.print(message.board);
terminal.uncork();
}
});
}
async send(message) {
(await core.getService("server")).postMessage(message);
}
async requestMove(dx, dy) {
this.send({action: "move", delta: [dx, dy]});
}
}
class Server {
constructor() {
this.players = {};
}
async start() {
let self = this;
core.register("onMessage", function(sender, message) {
if (!self.players[sender.index]) {
self.players[sender.index] = [Math.floor(Math.random() * 8) + 1, 8];
}
switch (message.action) {
case "move":
self.move(sender.index, message.delta);
self.redisplay();
break;
case "ready":
self.redisplay();
break;
}
});
core.register("onSessionEnd", function(session) {
delete self.players[session.index];
self.redisplay();
});
}
occupied(position) {
if (position[0] < 1 || position[1] < 1 || position[0] >= kWidth - 1 || position[1] >= kHeight - 1) {
return true;
}
if (Object.values(this.players).some(x => position[0] == x[0] && position[1] == x[1])) {
return true;
}
}
move(index, delta) {
let newPosition = [
this.players[index][0] + delta[0],
this.players[index][1] + delta[1],
];
let pushPosition = [
this.players[index][0] + 2 * delta[0],
this.players[index][1] + 2 * delta[1],
];
if (!this.occupied(newPosition)) {
this.players[index] = newPosition;
} else if (this.occupied(newPosition) && !this.occupied(pushPosition)) {
for (var i in this.players) {
if (this.players[i][0] == newPosition[0] && this.players[i][1] == newPosition[1]) {
this.players[i] = pushPosition;
}
}
this.players[index] = newPosition;
}
}
redisplay() {
core.broadcast({action: "display", board: this.getBoard()});
}
getBoard() {
let board = [
"┌─────────────┐",
"│ . │",
"│ .#. │",
"│ .%##. │",
"│ .##%##. │",
"│ .%###%##. │",
"│ .##%###%##. │",
"│.#%##%###%##.│",
"│ | │",
"└─────────────┘",
];
let merry = false;
let colors = board.map(x => x.split("").map(y => '#fff'));
for (let i in this.players) {
let player = this.players[i];
merry = merry || (player[0] == 7 && player[1] == 1);
let character = player[0] == 7 && player[1] == 1 ? '*' : '@';
board[player[1]] = board[player[1]].slice(0, player[0]) + character + board[player[1]].slice(player[0] + 1);
}
if (merry) {
for (let i = 0; i < colors.length; i++) {
for (let j = 0; j < colors[i].length; j++) {
switch (board[i].charAt(j)) {
case '#':
case '.':
colors[i][j] = '#080';
break;
case '%':
colors[i][j] = '#f00';
break;
case '|':
colors[i][j] = '#880';
break;
case '*':
colors[i][j] = '#ff0';
break;
case '@':
{
const palette = ['#f88', '#8f8', '#88f', '#fff'];
colors[i][j] = palette[(i * colors.length + j) % palette.length];
}
break;
}
}
}
}
let result = board.map((r, row) => r.split("").map((v, column) => ({style: "color: " + colors[row][column], value: v})).concat(["\n"]));
if (merry) {
result.push("Merry Christmas!");
} else {
result.push("use ←↑→↓");
}
return result;
}
}
if (imports.terminal) {
new Client().start().catch(terminal.print);
} else {
new Server().start().catch(print);
}

View File

@ -0,0 +1,63 @@
terminal.print({iframe: `
<style>
html, body {
padding: 0;
margin: 0;
overflow: hidden;
}
</style>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
parent.postMessage({
state: event.data,
url: event.target.getVideoUrl(),
}, "*");
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
</script>
`, width: 640, height: 390, style: "padding: 0; margin: 0; border: 0"});
terminal.print("hi");
core.register("onWindowMessage", function(event) {
terminal.print("message", JSON.stringify(event.message));
});

View File

@ -67,11 +67,23 @@ TlsSession* TlsContext_openssl::createSession() {
return new TlsSession_openssl(this); return new TlsSession_openssl(this);
} }
#include <iostream>
TlsContext_openssl::TlsContext_openssl() { TlsContext_openssl::TlsContext_openssl() {
SSL_library_init(); SSL_library_init();
SSL_load_error_strings(); SSL_load_error_strings();
_context = SSL_CTX_new(SSLv23_method()); const SSL_METHOD* method = SSLv23_method();
if (!method)
{
std::cerr << "SSLv23_method returned NULL\n";
}
_context = SSL_CTX_new(method);
if (!_context)
{
std::cerr << "SSL_CTX_new returned NULL\n";
}
SSL_CTX_set_default_verify_paths(_context); SSL_CTX_set_default_verify_paths(_context);
} }

View File

@ -22,3 +22,4 @@ certbot renew \
--work-dir data/global/letsencrypt/work \ --work-dir data/global/letsencrypt/work \
--cert-path data/global/httpd/certificate.pem \ --cert-path data/global/httpd/certificate.pem \
--key-path data/global/httpd/privatekey.pem \ --key-path data/global/httpd/privatekey.pem \
$*