1
0
mirror of https://github.com/osmarks/website synced 2025-08-31 01:37:57 +00:00

initial commit

This commit is contained in:
2020-03-08 17:13:14 +00:00
commit ccd4f72d2d
54 changed files with 4730 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
---
title: AutoScorer
description: Automatic score keeper, designed for handling Monopoly money.
slug: scorer
---
<div id="app">
<input type="number" v-model.number="startingScore">
<input type="text" v-model="newPlayerName">
<button @click="players.push(newPlayerName)">Add</button>
<ul>
<li
is="player"
v-for="(player, idx) in players"
:key="player"
:title="player"
:name="player"
:start-score="startingScore"
@delete="players.splice(idx, 1)">
</li>
</ul>
</div>
<script src="/assets/js/vue.js"></script>
<script type="text/x-template" id="score-template">
<div class="score">
<input v-model.number="score" type="number">
<button @click="decrement">-</button>
<input v-model.number="change" type="number">
<button @click="increment">+</button>
</div>
</script>
<script>
Vue.component("score", {
props: ["startScore"],
data: function() {
return {
change: 1,
score: this.startScore,
};
},
template: "#score-template",
methods: {
increment: function() {
this.score += this.change;
},
decrement: function() {
this.score -= this.change;
}
}
});
Vue.component("player", {
props: ["startScore", "name"],
template: '<div class="player"><h1 class="player-name">{{name}}</h1><score :startScore="startScore"></score><button @click="$emit(\'delete\')">Delete</button></div>'
});
var vm = new Vue({
el: "#app",
data: {
players: [],
startingScore: 0,
newPlayerName: "Name Here"
}
});
</script>

View File

@@ -0,0 +1,106 @@
---
title: Colours of the Alphabet
slug: alphacol
description: Colorizes the Alphabet, using highly advanced colorizational algorithms.
---
<style>
#colOut .charOut {
font-size: 2em;
font-family: Fira Mono, Courier New, Courier, monospace;
}
#chars {
width: 100%;
}
textarea {
font-size: 2em;
}
</style>
<script src="/assets/js/vue.js"></script>
<div id="app">
<div id="colOut">
<span v-for="c in coloredChars" :style="c.style" class="charOut">{{c.char}}</span>
</div>
<hr>
<textarea rows="5" type="text" placeholder="Enter some text" id="chars" v-model="input"></textarea>
<div>Colorization Mode: </div>
<select v-model="method">
<option value="hsl">HSL</option>
<option value="rgb">RGB</option>
</select>
</div>
<script>
String.prototype.forEach = function(callback)
{
for (var i = 0; i < this.length; i++)
callback(this[i]);
}
var beginAlpha = 97;
var endAlpha = 122;
var numChars = endAlpha - beginAlpha + 1
var colMax = 4096;
var alpha = {}
for (var charCode = beginAlpha; charCode <= endAlpha; charCode++)
{
alpha[String.fromCharCode(charCode)] = charCode - beginAlpha;
}
var charOut = document.getElementById("charOut");
var colOut = document.getElementById("colOut");
var pad3 = function(num) {return ("000" + num).substr(-3, 3)}; // Pads out a number so that it is exactly 3 digits long.
function rescaleAlpha(char, max) {
return alpha[char] / numChars * max;
}
function toShortHex(col)
{
var hexCode = Math.round(col).toString(16);
return "#" + pad3(hexCode);
}
var vm = new Vue({
el: "#app",
data: {
input: "",
method: "rgb"
},
computed: {
coloredChars: function() {
var conversionFunction = function() {return "red";}
if (this.method == "rgb") {
conversionFunction = this.charToRGB;
} else if (this.method == "hsl") {
conversionFunction = this.charToHSL;
}
return this.input.split("").map(function(character) {
return {style: {"color": conversionFunction(character.toLowerCase())}, char: character}
});
}
},
methods: {
charToRGB: function(char) {
return toShortHex(rescaleAlpha(char, colMax));
},
charToHSL: function(char) {
return "hsl(" + rescaleAlpha(char, 360) + ", 100%, 50%)"
}
}
})
</script>

View File

@@ -0,0 +1,414 @@
---
title: Emu War
description: Survive as long as possible against emus and other wildlife. Contributed by Aidan.
---
<span style="position: fixed">
<span id="bar">
</span>
<span id="text">
</span>
</span>
<div id="screen">
</div>
<div>
<button onclick="lastIns='r';mainLoop()"></button>
<div style="display: inline-block"</div>
<button onclick="lastIns='u';mainLoop()"></button>
<button onclick="lastIns='d';mainLoop()"></button>
</div>
<button onclick="lastIns='l';mainLoop()"></button>
</div>
<script type="text/javascript">
const WIDTH = 30
const HEIGHT = 20
const genTable = (x, y, width, height) => {
let tbody = document.createElement("tbody")
tbody.style = "width:50%;display:block;margin:auto;font-size:0.75rem"
for (let row = 0; row < y; row++) {
let tr = document.createElement("tr")
for (let col = 0; col < x; col++) {
let th = document.createElement("th")
th.innerHTML = " "
th.style = "width:" + width + ";height:" + height
tr.appendChild(th)
}
tbody.appendChild(tr)
}
return tbody
}
const setPixel = (elem, x, y, val) => {
elem.childNodes[1].childNodes[y].childNodes[x].innerHTML = val
}
const fillScreen = (elem, width, height, text) => {
for (let x = 0; x < WIDTH; x++) {
for (let y = 0; y < HEIGHT; y++) {
setPixel(elem, x, y, text)
}
}
}
const writeToScreen = (elem, width, startx, starty, words) => {
let nletter = 0
let x = startx
let y = starty
while(nletter < words.length) {
if (words[nletter] == '\n') {
y++
x=0
nletter++
continue
}
if (x >= width) {
y++
x=0
}
if (words[nletter] == ' ' && x==0) {
nletter++
continue
}
setPixel(elem, x, y, words[nletter])
x++
nletter++
}
}
const say = (elem, text) => {
elem.innerHTML += "<br>" + text
}
const clear = (elem) => {
elem.innerHTML = ""
}
const clamp = (x, max, min) => {
if (x >= max) return max
else if (x <= min) return min
else return x
}
let screen = document.getElementById("screen")
let saybox = document.getElementById("text")
let bar = document.getElementById("bar")
screen.style = "font-size:1rem;font-family:monospace"
let tablescreen = genTable(WIDTH, HEIGHT, "1rem", "1.125rem")
screen.appendChild(tablescreen)
const startDesc = document.createElement("div")
screen.appendChild(startDesc)
startDesc.innerHTML = `Welcome to Aidan's RPG thing!
<br>
You are an intrepid explorer who got lost in a cavernous cavern, infested with all sorts of dangerous critters, from rats to\nmighty ogres.
<br>
You cannot survive, but how many foes will you take with you?
<br>
Use WASD or buttons to move and attack, and 'r' to\nrestart.
<br>
Press W, A, S or D to begin.`
const enemy_icons = ["I", "K", "S", "E", "O", "R"]
const enemy_names = ["ibis", "kestrel", "spider", "emu", "ogre", "rat"]
const enemy_hp = [50, 30, 20, 80, 150, 15]
const enemy_dmg_min = [04, 02, 02, 05, 005, 02]
const enemy_dmg_range = [05, 15, 05, 20, 015, 05]
const enemy_reward = [03, 02, 01, 04, 005, 01]
const START_STR = 10
let hp = 500
let str = 0
let score = 0
let turns = 0
let x = Math.floor(WIDTH / 2)
let y = Math.floor(HEIGHT / 2)
let lastIns = ""
let base_dmg = 10
let enemies = []
let bonuses = []
const restart = () => {
hp = 500
str = 0
score = 0
turns = 0
x = Math.floor(WIDTH / 2)
y = Math.floor(HEIGHT / 2)
lastIns = ""
base_dmg = 10
enemies = []
bonuses = []
clear(bar)
clear(saybox)
fillScreen(screen, WIDTH, HEIGHT, " ")
startDesc.style.display = "block"
tablescreen.style.display = "none"
}
restart()
const BONUS_HP = 128
const BONUS_STR = 8
const MAX_BONUS = 8
const MAX_ENEMY = 24
const BASE_SCALE_FACTOR = 0.5
const gameOverMessage = "GAME OVER!"
let modif = 1
const calcModif = () => {
modif = str / START_STR + 1
}
const encounter = (enemy_id) => {
let enemy_dmg_done = Math.floor(enemy_dmg_min[enemy_id] + Math.random() * enemy_dmg_range[enemy_id] * modif)
say(saybox, "A " + enemy_names[enemy_id] + " attacked and did " + enemy_dmg_done + " damage!")
hp -= enemy_dmg_done
}
const spawnEnemy = (x, y, id) => {
enemies.push({
x: x,
y: y,
id: id,
hp: Math.floor(enemy_hp[id] * modif),
})
}
const spawnBonus = (x, y, hp, str, icon) => {
bonuses.push({
x: x,
y: y,
hp: hp,
str: str,
icon: icon,
})
}
const drawEnemy = (enemy_index) => {
const enemy = enemies[enemy_index]
setPixel(screen, enemy.x, enemy.y, enemy_icons[enemy.id])
}
const drawBonus = (bonus_index) => {
const bonus = bonuses[bonus_index]
setPixel(screen, bonus.x, bonus.y, bonus.icon)
}
const pickupBonus = (bonus_index) => {
const bonus = bonuses[bonus_index]
if (bonus.x == x && bonus.y == y) {
hp += bonus.hp
str += bonus.str
base_dmg += Math.floor(bonus.str * BASE_SCALE_FACTOR)
say(saybox, "Gained " + bonus.hp + "hp and " + bonus.str + "str from pickup!")
return true
}
return false
}
const drawPlayer = () => {
setPixel(screen, x, y, "@")
}
const checkCollision = (enemy_index) => {
let enemy = enemies[enemy_index]
for (let i = 0; i < enemies.length; i++) {
if (i == enemy_index) continue
let other = enemies[i]
if (other.x == enemy.x && other.y == enemy.y) return true
}
return (enemy.x == x && enemy.y == y)
}
const getEnemy = (x, y) => {
for (let i = 0; i < enemies.length; i++) {
let other = enemies[i]
if (other.x == x && other.y == y) return i
}
return -1
}
const canAttackPlayer = (enemy_index) => {
let enemy = enemies[enemy_index]
return enemy.x == x && enemy.y == y
}
const moveEnemy = (enemy_index) => {
let enemy = enemies[enemy_index]
let px = enemy.x
let py = enemy.y
let dx = x - enemy.x
let dy = y - enemy.y
if (Math.random() > 0.75 || dx*dx + dy*dy > 36) {
dx = Math.random() - 0.5
dy = Math.random() - 0.5
}
if (Math.abs(dx) > Math.abs(dy)) {
enemy.x += Math.sign(dx)
} else {
enemy.y += Math.sign(dy)
}
enemy.x = clamp(enemy.x, WIDTH-1, 0)
enemy.y = clamp(enemy.y, HEIGHT-1, 0)
if (canAttackPlayer(enemy_index)) {
encounter(enemies[enemy_index].id)
}
}
const attack = (enemy_index) => {
let done_dmg = base_dmg + Math.floor(Math.random() * (str + START_STR))
enemies[enemy_index].hp -= done_dmg
if (enemies[enemy_index].hp > 0) {
say(saybox, "Attacked " + enemy_names[enemies[enemy_index].id] + " and did " + done_dmg + " damage! It now has " + enemies[enemy_index].hp + "hp!")
}
}
const handleInput = (ins) => {
let lookX = x
let lookY = y
if (ins == "u") {
lookY += -1
} else if (ins == "d") {
lookY += 1
} else if (ins == "l") {
lookX += 1
} else {
lookX += -1
}
lookX = clamp(lookX, WIDTH-1, 0)
lookY = clamp(lookY, WIDTH-1, 0)
let id = getEnemy(lookX, lookY)
if (id == -1) {
x = lookX
y = lookY
} else {
attack(id)
if (enemies[id].hp < 0) {
say(saybox, "Killed a " + enemy_names[enemies[id].id] + " gaining " + enemy_reward[enemies[id].id] + "str!")
str += enemy_reward[enemies[id].id]
base_dmg += Math.floor(enemy_reward[enemies[id].id] * BASE_SCALE_FACTOR)
enemies.splice(id, 1)
x = lookX
y = lookY
score++
}
}
}
const mainLoop = () => {
if (hp <= 0) {
return
}
turns++
fillScreen(screen, WIDTH, HEIGHT, ".")
startDesc.style.display = "none"
tablescreen.style.display = "block"
clear(saybox)
calcModif()
handleInput(lastIns)
for (let i = 0; i < enemies.length; i++) {
let origX = enemies[i].x
let origY = enemies[i].y
moveEnemy(i)
if (checkCollision(i)) {
enemies[i].x = origX
enemies[i].y = origY
}
if (canAttackPlayer(i)) {
encounter(enemies[i].id)
}
drawEnemy(i)
}
let toDel = []
for (let i = 0; i < bonuses.length; i++) {
if (pickupBonus(i)) {
toDel.push(i)
}
drawBonus(i)
}
for (let i = 0; i < toDel.length; i++) {
bonuses.splice(toDel[i], 1)
}
let freqModif = 1 / (0.5*str / START_STR + 1)
if (Math.random() > 0.8 * freqModif && enemies.length <= MAX_ENEMY) {
let randID = Math.floor(Math.random() * enemy_icons.length)
let randX = Math.floor(Math.random() * WIDTH)
let randY = Math.floor(Math.random() * HEIGHT)
spawnEnemy(randX, randY, randID)
}
if (Math.random() > 0.75 && bonuses.length <= MAX_BONUS) {
let randHP = Math.floor(Math.random() * BONUS_HP)
let randStr = Math.floor(Math.random() * BONUS_STR)
let randX = Math.floor(Math.random() * WIDTH)
let randY = Math.floor(Math.random() * HEIGHT)
spawnBonus(randX, randY, randHP, randStr, "?")
}
drawPlayer()
clear(bar)
say(bar, "HP: " + hp + " Str: " + (str + START_STR) + " Base dmg: " + base_dmg)
say(bar, "Vanquished " + score + " foes.")
say(bar, "Turn " + turns)
if (hp <= 0) {
if ("points" in window) {
if (score >= 10) { points.unlockAchievement("emuwar10") }
points.updateMetric("foesVanquished", function(x) { return x + score }, 0)
points.updateMetric("deaths", function(x) { return x + 1 }, 0)
}
clear(saybox)
say(saybox, gameOverMessage + " Press R to restart.")
writeToScreen(screen, WIDTH, Math.floor(WIDTH/2 - gameOverMessage.length/2), Math.floor(HEIGHT/2)-1, gameOverMessage)
}
}
document.addEventListener('keydown', function(event) {
if(event.keyCode == 65) { // A
lastIns = 'r'
} else if(event.keyCode == 87) { // W
lastIns = 'u'
} else if(event.keyCode == 68) { // D
lastIns = 'l'
} else if(event.keyCode == 83) { // S
lastIns = 'd'
} else if(event.keyCode == 82) {
restart()
return
} else {
return
}
mainLoop()
});
function asyncDoLoop(fn) {
setTimeout(() => {
fn()
setTimeout(() => asyncDoLoop(fn), 0)
})
}
</script>

View File

@@ -0,0 +1,25 @@
---
title: Game of Life V2
description: Obligatory (John Conway's) Game of Life implementation.
slug: gol
---
<style>
td.on {
background: white;
}
td.off {
background: black;
}
td {
width: 1vw;
height: 1vw;
border: 1px solid gray;
}
table {
border-collapse: collapse;
}
</style>
<div id="app"></div>
<script src="out.js"></script>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,47 @@
---
title: GUIHacker
comments: off
description: <a href="https://github.com/osmarks/guihacker">My fork</a> of GUIHacker. Possibly the only version actually on the web right now since the original website is down.
---
<style>
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'),
}
body {
font-family: 'Source Code Pro', 'Fira Code', 'Inconsolata', 'Courier New', 'Courier', monospace;
background: #000;
color: #00FF00;
margin: 0;
display: block;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
.bars-and-stuff{
left: 66.6%;
}
.output-console {
position: fixed;
overflow: hidden;
}
p{margin:0}
nav {
display: none;
}
.description {
display: none;
}
</style>
<canvas class='hacker-3d-shiz'></canvas>
<canvas class='bars-and-stuff'></canvas>
<div class="output-console"></div>
<script src="/assets/js/h4xx0r.js"></script>

View File

@@ -0,0 +1,245 @@
---
title: Idea Generator
slug: ideas
description: Generates ideas. Terribly. Don't do them. These are not good ideas.
---
<noscript>
The Idea Generator requires JavaScript to function.
</noscript>
<div id="app">
<div id="out">ERROR</div>
<button onclick="set()">Randomize</button>
</div>
<script>
// Picks a random element from an array
function pickRandom(l) {
return l[Math.floor(Math.random() * l.length)];
}
function genMany(f, times) {
return Array.apply(null, Array(times)).map(f);
}
// Makes an array by repeating "x" "times" times.
// Uses stackoverflow magic.
function repeat(x, times) {
return genMany(function() { return x; }, times);
}
// Weighted random: takes an array formatted as [[1, "a"], [2, "b"]], interprets first element of each pair as a weight, and then the second as a value
// Weights must be integers and should be small, or they'll eat all memory.
function pickWeightedRandom(arr) {
var longArray = arr.map(function(pair) {
return repeat(pair[1], pair[0]);
}).reduce(function(acc, x) {
return acc.concat(x);
});
return pickRandom(longArray);
}
// Returns thing with given probability, otherwise an empty string
function addMaybe(probability, thing) {
return Math.random() < probability ? thing : "";
}
var vowels = ["a", "e", "i", "o", "u"];
// Correctly discriminates between a/an given the word coming after it
function connectorA(after) {
var firstLetter = after[0];
return vowels.indexOf(firstLetter) > -1 ? "an" : "a";
}
// Fed to reduce in order to evaluate a pattern
function evalPatternReducer(output, current) {
var last = output[output.length - 1]; // Pick the previous output (this will be the one coming afterwards in the actual final output)
return output.concat([current(last)]);
}
function randomize() {
if (Math.random() < 0.02) {
return pickRandom(others);
}
return pickWeightedRandom(patterns).reduceRight(evalPatternReducer, []).reverse().join(" ");
}
function set() {
document.getElementById("out").innerText = randomize();
}
function applyConnector(prev) {
var c = pickRandom(connectors);
if (typeof c == "string") {
return c
} else {
return c(prev);
}
}
function always(x) {
return function() {
return x
}
}
var adverbActionAdjectiveNounPattern = [
function() { return addMaybe(0.3, pickRandom(adverbs)); },
function() { return pickRandom(actions); },
applyConnector,
function() { return addMaybe(0.5, pickRandom(adjectives)); },
function() { return pickRandom(things); }
]
var patterns = [
[4, adverbActionAdjectiveNounPattern],
[1, [always("do not")].concat(adverbActionAdjectiveNounPattern)],
[1, adverbActionAdjectiveNounPattern.concat([always("or else")])]
];
var things = [
"pancake",
"cat",
"app",
"idea generator",
"dog",
"idea",
"problem",
"chair",
"sandwich",
"ice cream",
"town",
"book",
"country",
"computer",
"duct tape",
"apple juice",
"cow",
"lamp",
"politician",
"car",
"chair",
"swivel chair",
"ethernet switch",
"radio",
"watermelon",
"point",
"data",
"spaceship",
"planet",
"moon",
"star",
"black hole",
"decagon",
"crate",
"javascript framework"
];
var adverbableAdjectives = [
"quick",
"slow",
"huge",
"large",
"great",
"awful",
"irritating",
"stupid",
"ideal",
"wonderful",
"brilliant",
"political",
"cool",
"orthogonal",
"strange",
"weird",
"excellent"
]
var otherAdjectives = [
"5-gigabit",
"tall",
"fast",
"small",
"triangular",
"octagonal",
"nonexistent",
"imaginary",
"hyperbolic"
]
var adjectives = adverbableAdjectives.concat(otherAdjectives);
var otherAdverbs = [
]
var adverbs = adverbableAdjectives.map(function(a) {return a + "ly";}).concat(otherAdverbs);
var actions = [
"talk to",
"make",
"find",
"delete",
"meet",
"solve",
"sit on",
"eat",
"enlighten",
"believe in",
"change",
"take",
"study",
"use",
"visit",
"understand",
"read",
"travel to",
"buy",
"sell",
"encourage",
"argue with",
"load",
"fix",
"argue about",
"create"
];
var connectors = [
"your",
"my",
connectorA,
"the",
"someone's",
"another"
]
var optimalConfigurations = [
"Firefox 23 on a Difference Engine",
"manual HTTP over Telnet on a Pentium 3",
"NetPositive on Haiku (<0.6)",
"Vanadium 764 on an AlphaHoloCore P-series (> 150THz recommended)",
"a quantum cheeseputer",
"emulating a CPU on paper",
"a computer outside of the Matrix",
"an iPhone 2 (the Angular 3/PHP 6 version is required on this platform)",
"a computer with an x87 processor",
"a 1048576-bit device supporting the x186 architecture"
]
var others = [
"Protocol Omega has been activated.",
"Error. Out of 1s.",
"Don't believe his lies.",
"Error. You have broken the Internet.",
"They are coming for you. RUN!",
"Help, I'm trapped in an idea generator!",
"*** Prelude.undefined: matrix/src/Physics/SubquantumGravity.hs:1337",
"Please switch to " + pickRandom(optimalConfigurations) + " for an optimal experience.",
"User error. The user will be deleted.",
genMany(function() { return String.fromCharCode(Math.random() * 2048); }, 1024).join("")
]
set();
</script>

View File

@@ -0,0 +1,6 @@
---
title: IncDec
slug: incdec
description: The exciting game of incrementing and decrementing!
---
If you're reading this, then this is not linking to actual IncDec as it should due to a server misconfiguration.

View File

@@ -0,0 +1,236 @@
---
title: Infipage
description: Outdoing all other websites with <b>INFINITE PAGES!</b>
url: /infipage/0
---
<style>
#prev, #next {
text-decoration: none;
}
#main {
padding-top: 10vh;
font-size: 2em;
display: flex;
justify-content: space-around;
}
#main.mobile {
display: block;
text-align: center;
}
#pagecount {
max-width: 70vw;
overflow-wrap: break-word;
}
#error {
padding-top: 0.5em;
color: red;
text-align: center;
}
</style>
<div id="main">
<a id="prev">Previous Page</a>
<div id="pagecount"></div>
<a id="next">Next Page</a>
</div>
<div id="error"></div>
<script src="https://unpkg.com/big.js@5.2.1/big.js"></script>
<script>
Big.PE = Infinity
Big.DP = 0
// From MDN somewhere
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, pos) {
return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
};
}
if (!String.prototype.repeat) {
String.prototype.repeat = function(times) {
return new Array(num + 1).join(this);
};
}
function encodeRLEArray(input) {
var encoding = [];
var prev, count, i;
for (count = 1, prev = input[0], i = 1; i < input.length; i++) {
if (input[i] != prev || count == 35) {
encoding.push([count, prev])
count = 1
prev = input[i]
}
else {
count++
}
}
encoding.push([count, prev])
return encoding
}
function encodeRLEString(rleArr) {
var out = ""
rleArr.forEach(function(pair) {
out += pair[0].toString(36) + pair[1]
})
return out
}
/*function encodeRLEString2(rleArr) {
var out = ""
rleArr.forEach(function(pair) {
var count = pair[0]
var char = pair[1]
if (count !== 1) {
out += "." + count.toString(36) + char
} else {
out += char
}
})
return out
}*/
function decodeRLEArray(arr) {
var out = ""
arr.forEach(function(pair) {
out += pair[1].repeat(pair[0])
})
return out
}
function decodeRLEPair(r) {
return [ parseInt(r[0], 36), r[1] ]
}
function decodeRLEString(str) {
var results = []
var re = /[0-9a-z][0-9A-Za-z_-]/g
var match
while (match = re.exec(str)) {
results.push(decodeRLEPair(match[0]))
}
return results
}
function encodeRLE(str) {
return encodeRLEString(encodeRLEArray(str))
}
function decodeRLE(str) {
return decodeRLEArray(decodeRLEString(str))
}
function b64e(str) {
return btoa(str).replace(/\+/g, "_").replace(/\//g, "-")
}
function b64d(b64) {
return atob(b64.replace(/_/g, "+").replace(/\-/g, "/"))
}
var base = Big(256)
var basediv2 = base.div(2)
function addEncoding(arr, func) {
return arr.concat(arr.map(func))
}
function findShortest(arr) {
return arr.reduce(function(prev, now) {
if (typeof prev === "string") {
return now.length < prev.length ? now : prev
} else {
return now
}
}, null)
}
function encodeBignum(b) {
var out = ""
var curr = b
// Pretend it's a positive int until the end
if (b.s === -1) { curr = b.times(-1) }
var byte = 0
while (!curr.eq(0)) {
var now = curr.mod(base)
out += String.fromCharCode(+now)
// Attempt to emulate an actual bitshift
curr = curr.div(base)
if (now.gte(basediv2)) { curr = curr.minus(1) }
}
var start = b.s === -1 ? "n" : "p"
var b64 = start + b64e(out)
var decimal = b.toString()
var possibilities = [b64, decimal]
possibilities = addEncoding(possibilities, function(x) { return "r" + encodeRLE(x) })
return findShortest(possibilities)
}
function decodeBignum(str) {
if (str.startsWith("r")) {
return decodeBignum(decodeRLE(str.substring(1)))
}
// If string is not tagged with its sign, it's just a regular number
if (!str.startsWith("n") && !str.startsWith("p")) {
return Big(str)
}
var start = str.substring(0, 1)
var bin = b64d(str.substring(1))
var out = Big(0)
// Split string into bytes
var bytes = bin.split("").map(function(x) { return x.charCodeAt(0) })
// Add each byte to string, multiplied by 2 ^ its position in string
bytes.forEach(function(byte, exponent) {
var thisByte = Big(byte).times(base.pow(exponent))
out = out.plus(thisByte)
})
if (start === "n") { out = out.times(-1) }
return out
}
var page
var pageString
var prev = document.querySelector("#prev"), next = document.querySelector("#next"), count = document.querySelector("#pagecount"), main = document.querySelector("#main"), error = document.querySelector("#error")
function loadPage() {
try {
var afterSlash = /infipage\/([A-Za-z0-9_=-]+)/.exec(window.location.pathname)[1]
page = decodeBignum(afterSlash)
} catch(e) {
console.warn("Page Number Decode Failure")
console.warn(e)
error.innerText = "Page number invalid - " + e + ". Defaulting to 0."
page = Big(0)
}
pageString = page.toString()
console.log("Page", pageString)
var canonical = encodeBignum(page)
if (canonical !== afterSlash) {
console.log(canonical, afterSlash, "Mismatch!")
window.location.href = `/infipage/${canonical}`
}
prev.href = encodeBignum(page.minus(1))
next.href = encodeBignum(page.plus(1))
pagecount.innerText = "Page " + page.toString()
}
loadPage()
if ("ontouchstart" in window) { main.classList.add("mobile") }
</script>
<script>
window.addEventListener("load", function() {
if ("points" in window) {
console.log("running update")
points.updateMetric("greatestInfipage", function(current) {
console.log("updated", current, pageString)
if (!current || page.abs().gt(Big(current))) { return pageString }
else { return current }
})
}
})
</script>

View File

@@ -0,0 +1,26 @@
---
title: JHLT
description: Tells you how late Joe's homework is.
---
<div id="timer"></div>
<script src="/assets/js/moment.min.js"></script>
<script>
var joe = moment("2017-03-23T09:15:00");
var el = document.getElementById("timer");
function format(dur) {
return "Joe's homework is " +
+ dur.years() + " years "
+ dur.months() + " months "
+ dur.days() + " days "
+ dur.hours() + " hours "
+ dur.minutes() + " minutes "
+ dur.seconds() + " seconds late.";
}
setInterval(function() {
var diff = moment.duration(-joe.diff(moment()));
el.innerText = format(diff);
}, 1000);
</script>

85
experiments/lorem/index.html Executable file
View File

@@ -0,0 +1,85 @@
---
title: Lorem Ipsum
slug: lorem
description: Lorem Ipsum (latin-like placeholder text)... FOREVER.
---
<style>
body {
overflow-y: scroll;
overflow-x: hidden;
position: relative;
}
#marker {
width: 1px;
height: 1px;
position: absolute;
bottom: 10vh;
}
#text {
padding-left: 2vw;
padding-right: 2vw;
padding-top: 2vw;
}
</style>
<div id="text"></div>
<script src="/assets/js/lorem.js"></script>
<script src="/assets/js/infiscroll.js"></script>
<script>
var textEl = document.querySelector("#text");
var count = 0;
function pickRandom(l) {
return l[Math.floor(Math.random() * l.length)];
}
var extra = [
"Protocol Omega has been activated.",
"Error. Out of 1s.",
"Don't believe his lies.",
"I have the only antidote.",
"They are coming for you.",
"Help, I'm trapped in an infinite scroll webpage!",
];
function throttle (callback, limit) {
var wait = false;
return function () {
if (!wait) {
callback.call();
wait = true;
setTimeout(function () {
wait = false;
}, limit);
}
}
}
var count = 0
var unaddedCount = 0
var handlePoints = throttle(function() {
if (count >= 400) { points.unlockAchievement("lorem400") }
points.updateMetric("loremParagraphs", function(x) { return x + unaddedCount }, 0).then(function() { unaddedCount = 0 })
}, 300)
window.addEventListener("load", function() {
infiscroll(function() {
if (Math.random() > 0.02) {
textEl.appendChild(document.createTextNode(" " + Lorem.createText(1, Lorem.TYPE.PARAGRAPH)))
} else {
textEl.appendChild(document.createTextNode(" " + pickRandom(extra)))
}
count++
unaddedCount++
console.log("Scrolled down, generating lorem.", count);
if ("points" in window) {
handlePoints()
}
}, "30vh", textEl, 10);
})
</script>

View File

@@ -0,0 +1,36 @@
---
title: Arbitrary Points
description: Collect Arbitrary Points and achievements by doing things on this website! See how many you have! Do nothing with them because you can't! This is the final form of gamification.
slug: points
---
<div id="app">
<noscript>You need JS on for this to work. I really don't know what you expected at this point.</noscript>
</div>
<div class="smallinfo">All Arbitrary Points data is stored and processed only on your device.
There is no serverside component whatsoever.
If you don't like this regardless, you can bug me to implement an off switch, attempt to ignore it, or use Internet Explorer 6.
Ideas for features and achievements and whatever else wanted and may be accepted.
This is very easy to meddle with using the browser console, as I haven't tried to prevent that, but if you cheat all the time you may ruin any fun this might have brought.
</div>
<script src="/assets/js/mithril.js"></script>
<script>
if (!("Promise" in window)) {
document.getElementById("app").innerHTML = "Your browser is seemingly too outdated for this to work. Sorry! Try installing/updating Firefox."
}
</script>
<script defer src="./index.js">
</script>
<style>
button {
border: 1px solid black;
background: lightgray;
border-radius: 0;
padding: 0.5em;
}
.metricname {
padding-right: 1em;
}
</style>

110
experiments/points/index.js Normal file
View File

@@ -0,0 +1,110 @@
const round = x => Math.round(x * 1e10) / 1e10
const prefixes = ["", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta"]
const siPrefix = (value, unit) => {
let i = 0
let b = value
while (b > 1000) {
b /= 1000
i++
}
return `${round(b).toString()} ${prefixes[i]}${unit}${value !== 1 ? "s" : ""}`
}
const metricDisplayInfo = {
pagesVisited: { name: "Pages visited", units: "page" },
timeSpent: { name: "Time spent", units: "second" },
achievements: { name: "Achievements" },
foesVanquished: { name: "Foes vanquished", units: "foe" },
deaths: { name: "Deaths", units: "death" },
loremParagraphs: { name: "Lorem Ipsum paragraphs seen", units: "paragraph" },
commentsPosted: { name: "Comments posted", units: "comment" },
greatestInfipage: { name: "Largest infipage visited" }
}
const displayMetric = metric => {
let name = metric[0]
let value = metric[1]
const displayInfo = metricDisplayInfo[name]
if (displayInfo) {
name = displayInfo.name
if (displayInfo.units) {
value = siPrefix(value, displayInfo.units)
}
}
return m("tr", m("td.metricname", name), m("td.metricvalue", value))
}
const Metrics = {
metrics: null,
load: async () => {
Metrics.metrics = await points.readAllMetrics()
m.redraw()
},
view: () => m("p", Metrics.metrics === null ? "Loading..." : m("table.metrics", Array.from(Metrics.metrics.entries()).map(displayMetric)))
}
const zfill = (num, z) => num.toString().padStart(z, "0")
const formatDate = x => `${zfill(x.getHours(), 2)}:${zfill(x.getMinutes(), 2)}:${zfill(x.getSeconds(), 2)} ${zfill(x.getDate(), 2)}/${zfill(x.getMonth() + 1, 2)}/${zfill(x.getFullYear(), 4)}`
const renderAchievement = a =>
m(".achievement", { style: `background-color: ${colHash(a.title)}` }, [
a.timestamp && m(".title", { title: a.id }, `Achievement achieved at ${formatDate(new Date(a.timestamp))}`),
m(".title", a.title),
m(".description", a.description),
m(".conditions", `Unlocked by: ${a.conditions}`),
a.points && m(".points", `${a.points} points`)
])
const Achievements = {
achievements: [],
load: async () => {
const raw = await points.getAchievements()
Achievements.achievements = raw.map(ach => {
const info = points.achievementInfo[ach.id]
const out = {
title: ach.id || "???",
description: `Unrecognized achievement ${ach.id}.`,
conditions: "???",
...ach
}
if (info) { Object.assign(out, info) }
m.redraw()
return out
})
Achievements.achievements.sort((a, b) => a.timestamp < b.timestamp)
},
view: () => m(".achievements-listing", Achievements.achievements.map(renderAchievement))
}
const reset = async () => {
if (prompt(`This will reset your points, achievements and metrics. If you are sure, please type "yes I am definitely sure".`) === "yes I am definitely sure") {
if (confirm("Are you really very sure?")) {
await points.reset()
window.location.reload()
}
}
}
let pointsCount = "[loading...]"
const reloadPoints = async () => { pointsCount = await points.getPoints() }
const App = {
view: () => m("div", [
m("h2", `Points: ${pointsCount}`),
m("button", { onclick: reset }, "Reset"),
m(Metrics),
m(Achievements)
])
}
m.mount(document.getElementById("app"), App)
Metrics.load()
reloadPoints()
Achievements.load()
points.unlockAchievement("visitArbitraryPoints")
document.addEventListener("points-update", () => { reloadPoints(); Achievements.load() })

15
experiments/rpncalc-v2/calc.css Executable file
View File

@@ -0,0 +1,15 @@
.stack-box {
text-align: center;
border: solid 1px black;
min-width: 30vmin;
max-width: 30vmin; /* will normally be dynamically set */
line-height: 30vmin;
height: 30vmin;
font-size: 7vmin;
margin: -1px; /* make edges join neatly */
}
#input {
width: 100%;
font-size: 2em;
}

157
experiments/rpncalc-v2/calc.js Executable file
View File

@@ -0,0 +1,157 @@
// Reads the input box for a RPN expression
// Calculates result
// Outputs it as nicely formatted boxes.
function calculateFromInput() {
var expr = document.getElementById("input").value;
var output = document.getElementById("output")
var result = calculateRPN(expr.split(" "));
output.innerHTML = ""; // Clear the output div
result.stack.forEach(function(num) {
num = num.toString().replace("NaN", "Error");
var box = createBox(num);
box.style["max-width"] = num.length + "em";
output.appendChild(box);
});
if (result.errors.length > 0) { // If errors exist output them separated by line breaks.
result.errors.forEach(error => {
output.appendChild(document.createTextNode(error.toString()))
output.appendChild(document.createElement("br"))
})
}
}
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
function divide(x, y) {
return x / y;
}
function flip(x, y) {
return [y, x];
}
function subtract(x, y) {
return x - y;
}
function sum(vals) {
var acc = 0;
vals.forEach(function(el) {
acc += el;
});
return acc;
}
function range(low, high) {
var r = [];
for (var i = low; i <= high; i++) {
r.push(i);
}
return r;
}
function createBox(contents) {
var el = document.createElement("div");
el.setAttribute("class", "stack-box");
el.innerText = contents;
return el;
}
// Takes a two-argument function and lifts it to a binary stack op
function binaryOp(fun) {
return function(stack) {
var x = stack.pop();
var y = stack.pop();
stack.push(fun(y, x));
return stack;
}
}
// Takes a function and lifts it to a unary op using a stack. Returns new stack.
function unaryOp(fun) {
return function(stack) {
var x = stack.pop();
stack.push(fun(x));
return stack;
}
}
// Takes a function and lifts it to an op which takes the entire stack and reduces it to one value. Returns new stack.
function greedyOp(fun) {
return function(stack) {
return [fun(stack)];
}
}
// Lifs to an operator which takes two values and adds an array of them to the stack. Returns a new stack.
function binaryMultioutOp(fun) {
return function(stack) {
var x = stack.pop();
var y = stack.pop();
return stack.concat(fun(y, x));
}
}
function calculateRPN(tokens) {
var stack = [];
var errors = [];
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
switch (token) {
case "+":
stack = binaryOp(add)(stack);
break;
case "*":
stack = binaryOp(multiply)(stack);
break;
case "/":
stack = binaryOp(divide)(stack);
break;
case "-":
stack = binaryOp(subtract)(stack);
break;
case "range":
stack = binaryMultioutOp(range)(stack);
break;
case "flip":
stack = binaryMultioutOp(flip)(stack);
break;
case "sum":
stack = greedyOp(sum)(stack);
break;
case "": // This may be entered by accident.
break;
default:
var parsed = parseFloat(token);
if (parsed === parsed) { // check for NaNs from a failed parse
stack.push(parsed);
} else {
errors.push("Token '" + token + "' (#" + (i + 1) + ")" + " invalid");
}
}
}
return {stack: stack, errors: errors};
}

View File

@@ -0,0 +1,16 @@
---
title: RPNCalc v2
slug: rpncalc2
description: A Reverse Polish Notation (check wikipedia) calculator, version 2. Buggy and kind of unreliable. This updated version implements subtraction.
---
<link rel="stylesheet" href="calc.css">
<center><div id="output"></div></center>
<hr>
<input type="text" id="input" default="Input RPN expression" oninput="calculateFromInput()">
</div>
<script src="calc.js"></script>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
---
title: RPNCalc v3
slug: rpncalc3
description: Reverse Polish Notation calculator, version 3 - with inbuilt docs, arbitrary-size rational numbers, utterly broken float/rational conversion and quite possibly Turing-completeness.
---
<link href="style.css" rel="stylesheet">
<div id="app"></div>
<script src="app.js"></script>

View File

@@ -0,0 +1,98 @@
.rpncalc {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.stack {
margin-top: 5vh;
margin-bottom: 5vh;
}
.item {
min-width: 10em;
min-height: 10em;
border: 1px solid black;
margin-top: -1px;
display: flex;
justify-content: center;
align-items: center;
}
.float-slider {
border: 1px solid lightgray;
margin-left: 0.2em;
margin-right: 0.2em;
}
.float-slider-label {
display: flex;
align-items: center;
}
.partial {
text-align: center;
padding: 1em;
}
.horizontal-center {
display: flex;
justify-content: center;
}
.partial-stack {
font-size: 0.8em;
padding: 1em;
}
.formatted-rational {
width: 33%;
}
hr {
width: 100%;
}
.exprinput {
margin-top: 1vh;
width: 100%;
font-size: 1.2em;
}
.config-panel {
background: lightgray;
padding: 1em;
margin-top: 1em;
}
.docs {
text-align: left;
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 3em;
flex-wrap: wrap;
}
.op-docs {
background: #8cb7c6;
color: black;
display: flex;
align-items: center;
margin: 1em;
max-width: 40vw;
}
.op-desc {
padding: 1em;
}
.op-name {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-left: 1em;
padding-right: 1em;
}

View File

@@ -0,0 +1,116 @@
---
title: Websocket Terminal
slug: wsterm
description: Type websocket URLs in the top bar and hit enter; type messages in the bottom bar, and also hit enter. Probably useful for some weirdly designed websocket services.
---
<div id="app"></div>
<style>
.messages li {
list-style-type: none;
}
.internal {
color: gray;
}
.user {
color: blue;
}
.user::before {
content: "> ";
}
.remote::before {
content: "< ";
}
#app input {
width: 100%;
}
</style>
<script src="/assets/js/hyperapp.min.js"></script>
<script src="/assets/js/hyperapp-html.min.js"></script>
<script>
const h = hyperappHtml;
const push = (xs, x) => xs.concat([x]);
const state = {
messages: [],
websocket: null
};
let windowVisible = true;
let notify = false;
window.onfocus = () => { windowVisible = true; notify = false; };
window.onblur = () => { windowVisible = false; };
const blinkTime = 1000;
// Blink title a bit by adding then removing ***.
setInterval(() => {
if (notify && !windowVisible) {
let title = document.title;
document.title = "*** " + title;
setTimeout(() => {
document.title = title;
}, blinkTime)
}
}, blinkTime * 2);
const actions = {
connect: value => (state, actions) => {
if (state.websocket != null && state.websocket.close) state.websocket.close();
let ws = new WebSocket(value);
ws.addEventListener("message", ev => {
actions.message([ev.data, "remote"]);
notify = true; // start notifications
});
ws.addEventListener("close", ev => actions.message(["Connection closed.", "internal"]));
ws.addEventListener("open", ev => actions.message(["Connected.", "internal"]));
return {websocket: ws}},
message: value => state => ({messages: push(state.messages, value)}),
send: value => state => {
if (state.websocket !== null && state.websocket.readyState === 1) {
state.websocket.send(value);
} else {
actions.message(["Not connected.", "internal"])
}
},
msgInput: event => (state, actions) => {
if (event.keyCode == 13) { // enter key
let val = event.target.value;
event.target.value = "";
actions.send(val);
actions.message([val, "user"]);
}
},
urlInput: event => (state, actions) => {
if (event.keyCode == 13) { // enter key
let val = event.target.value;
console.log(val);
actions.connect(val);
}
}
};
const cls = x => ({ class: x });
const scrollDown = () => {
let scrollEl = document.scrollingElement;
scrollEl.scrollTop = scrollEl.scrollHeight;
};
const view = (state, actions) => h.div([
h.div([
h.input({ onkeyup: actions.urlInput, placeholder: "URL" })
]),
h.ul({class: "messages", onupdate: (element, old) => scrollDown()}, state.messages.map(msg =>
h.li(cls(msg[1]), msg[0]))),
h.input({ onkeyup: actions.msgInput, placeholder: "Message" })
]);
const main = hyperapp.app(state, actions, view, document.getElementById("app"));
</script>

View File

@@ -0,0 +1,8 @@
---
title: Whorl
description: Dice-rolling webapp.
slug: whorl
---
<link rel="stylesheet" href="src.6e636393.css">
<div id="app"></div>
<script src="src.3854b3a8.js"></script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
#app{font-family:Fira Sans,sans-serif}.previous-rolls{border-collapse:collapse}.previous-rolls tr:first-child{background:#d3d3d3}.previous-rolls td,.previous-rolls th{padding:0 1em}.previous-rolls .raw-dice{font-weight:700;cursor:pointer}.controls{margin-bottom:1em}.controls button{width:10em;height:2em}.controls button,.controls input{border-radius:0;border:1px solid #000}.controls input{width:10em;height:2em;margin-right:1em}.error{color:red} main h1{margin-bottom:1em}