mirror of
https://github.com/osmarks/website
synced 2025-08-31 01:37:57 +00:00
initial commit
This commit is contained in:
71
experiments/auto-score-keeper/index.html
Normal file
71
experiments/auto-score-keeper/index.html
Normal 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>
|
106
experiments/colours-of-the-alphabet/index.html
Executable file
106
experiments/colours-of-the-alphabet/index.html
Executable 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>
|
414
experiments/emu-war/index.html
Normal file
414
experiments/emu-war/index.html
Normal 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>
|
25
experiments/game-of-life/index.html
Executable file
25
experiments/game-of-life/index.html
Executable 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>
|
1
experiments/game-of-life/out.js
Normal file
1
experiments/game-of-life/out.js
Normal file
File diff suppressed because one or more lines are too long
47
experiments/guihacker/index.html
Normal file
47
experiments/guihacker/index.html
Normal 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>
|
245
experiments/idea-generator/index.html
Normal file
245
experiments/idea-generator/index.html
Normal 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>
|
6
experiments/incdec/index.html
Normal file
6
experiments/incdec/index.html
Normal 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.
|
236
experiments/infipage/index.html
Normal file
236
experiments/infipage/index.html
Normal 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>
|
26
experiments/joe/index.html
Normal file
26
experiments/joe/index.html
Normal 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
85
experiments/lorem/index.html
Executable 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>
|
36
experiments/points/index.html
Normal file
36
experiments/points/index.html
Normal 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
110
experiments/points/index.js
Normal 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
15
experiments/rpncalc-v2/calc.css
Executable 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
157
experiments/rpncalc-v2/calc.js
Executable 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};
|
||||
}
|
16
experiments/rpncalc-v2/index.html
Executable file
16
experiments/rpncalc-v2/index.html
Executable 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>
|
1
experiments/rpncalc-v3/app.js
Normal file
1
experiments/rpncalc-v3/app.js
Normal file
File diff suppressed because one or more lines are too long
9
experiments/rpncalc-v3/index.html
Normal file
9
experiments/rpncalc-v3/index.html
Normal 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>
|
98
experiments/rpncalc-v3/style.css
Normal file
98
experiments/rpncalc-v3/style.css
Normal 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;
|
||||
}
|
116
experiments/websocket-terminal/index.html
Normal file
116
experiments/websocket-terminal/index.html
Normal 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>
|
8
experiments/whorl/index.html
Normal file
8
experiments/whorl/index.html
Normal 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>
|
18
experiments/whorl/src.3854b3a8.js
Normal file
18
experiments/whorl/src.3854b3a8.js
Normal file
File diff suppressed because one or more lines are too long
1
experiments/whorl/src.3854b3a8.js.map
Normal file
1
experiments/whorl/src.3854b3a8.js.map
Normal file
File diff suppressed because one or more lines are too long
1
experiments/whorl/src.6e636393.css
Normal file
1
experiments/whorl/src.6e636393.css
Normal 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}
|
Reference in New Issue
Block a user