improve GUIHacker, add TTT, fix RSS, add blog post

This commit is contained in:
osmarks 2022-02-25 20:10:16 +00:00
parent d30cf5ed1c
commit bac2a75be6
8 changed files with 584 additions and 22 deletions

View File

@ -14,13 +14,26 @@ jargonWords = {
"RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "PHP", "UDP",
"TPS", "RX", "ASCII", "CD-ROM", "CGI", "CPU", "DDR", "DHCP", "BIOS", "IDE", "IP", "MAC",
"MP3", "AAC", "PPPoE", "SSD", "SDRAM", "VGA", "XHTML", "Y2K", "GUI", "EPS", "SATA", "SAS",
"VM", "LAN", "DRAM", "L3", "L2", "DNS", "UEFI", "UTF-8", "DDOS"],
"VM", "LAN", "DRAM", "L3", "L2", "DNS", "UEFI", "UTF-8", "DDOS", "HDMI", "GPU", "RSA", "AES",
"L7", "ISO", "HTTPS", "SSH", "SIMD", "GNU", "PDF", "LPDDR5", "ARM", "RISC", "CISC", "802.11",
"5G", "LTE", "3GPP", "MP4", "2FA", "RCE", "JBIG2", "ISA", "PCIe", "NVMe", "SHA", "QR", "CUDA",
"IPv4", "IPv6", "ARP", "DES", "IEEE", "NoSQL", "UTF-16", "ADSL", "ABI", "TX", "HEVC", "AVC",
"AV1", "ASLR", "ECC", "HBA", "HAL", "SMT", "RPC", "JIT", "LCD", "LED", "MIME", "MIMO", "LZW",
"LGA", "OFDM", "ORM", "PCRE", "POP3", "SMTP", "802.3", "PSU", "RGB", "VLIW", "VPS", "VPN",
"XMPP", "IRC", "GNSS"],
adjectives:
["auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform",
"redundant", "online", "haptic", "multi-byte", "bluetooth", "wireless", "1080p", "neural",
"redundant", "online", "haptic", "multi-byte", "Bluetooth", "wireless", "1080p", "neural",
"optical", "solid state", "mobile", "unicode", "backup", "high speed", "56k", "analog",
"fiber optic", "central", "visual", "ethernet", "Griswold", "binary", "ternary",
"secondary", "web-scale", "persistent", "Java"
"secondary", "web-scale", "persistent", "Java", "cloud", "hyperscale", "seconday", "cloudscale",
"software-defined", "hyperconverged", "x86", "Ethernet", "WiFi", "4k", "gigabit", "neuromorphic",
"sparse", "machine learning", "authentication", "multithreaded", "statistical", "nonlinear",
"photonic", "streaming", "concurrent", "memory-safe", "C", "electromagnetic", "nanoscale",
"high-level", "low-level", "distributed", "accelerated", "base64", "purely functional",
"serial", "parallel", "compute", "graphene", "recursive", "denormalized", "orbital",
"networked", "autonomous", "applicative", "acausal", "hardened", "category-theoretic",
"ultrasonic"
],
nouns:
["driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card",
@ -30,7 +43,14 @@ jargonWords = {
"virus", "malware", "spyware", "network", "internet", "field", "acutator", "tetryon",
"beacon", "resonator", "diode", "oscillator", "vertex", "shader", "cache", "platform",
"hyperlink", "device", "encryption", "node", "headers", "botnet", "applet", "satellite",
"Unix", "byte"],
"Unix", "byte", "Web 3", "metaverse", "microservice", "ultrastructure", "subsystem",
"call stack", "gate", "filesystem", "file", "database", "bitmap", "Bloom filter", "tensor",
"hash table", "tree", "optics", "silicon", "hardware", "uplink", "script", "tunnel",
"server", "barcode", "exploit", "vulnerability", "backdoor", "computer", "page",
"regex", "socket", "platform", "IP", "compiler", "interpreter", "nanochip", "certificate",
"API", "bitrate", "acknowledgement", "layout", "satellite", "shell", "MAC", "PHY", "VLAN",
"SoC", "assembler", "interrupt", "directory", "display", "functor", "bits", "logic",
"sequence", "procedure", "subnet", "invariant", "monad", "endofunctor", "borrow checker"],
participles:
["backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating",
"indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing",
@ -38,7 +58,11 @@ jargonWords = {
"injecting", "transcoding", "encoding", "attaching", "disconnecting", "networking",
"triaxilating", "multiplexing", "interplexing", "rewriting", "transducing",
"acutating", "polarising", "diffracting", "modulating", "demodulating", "vectorizing",
"compiling", "jailbreaking", "proxying", "Linuxing"
"compiling", "jailbreaking", "proxying", "Linuxing", "quantizing", "multiplying",
"scanning", "interpreting", "routing", "rerouting", "tunnelling", "randomizing",
"underwriting", "accessing", "locating", "rotating", "invoking", "utilizing",
"normalizing", "hijacking", "integrating", "type-checking", "uploading", "downloading",
"allocating", "receiving", "decoding"
]};
// Generates a random piece of jargon
@ -56,6 +80,7 @@ function jargon() {
var raw = choose(jargonWords.participles) + " " + thing
} else {
var raw = thing + " " + choose(jargonWords.participles)
.replace("writing", "wrote")
.replace("overriding", "overriden")
.replace("shutting", "shut")
.replace("ying", "ied")
@ -195,7 +220,18 @@ function GuiHacker(){
"Locating crossbows...",
"Enabling algorithms and coding",
"Collapsing Subdirectories...",
"Enabling Ping Wall..."
"Enabling Ping Wall...",
"Obtaining sunglasses...",
"Rehashing hashes.",
"Randomizing numbers.",
"Greening text...",
"Accessing system32",
"'); DROP DATABASE system;--",
"...Nesting VPNs...",
"Opening Wireshark.",
"Breaking fifth wall....",
"Flipping arrows and applying yoneda lemma",
"Rewriting in Rust"
];
this.isProcessing = false;
this.processTime = 0;
@ -274,7 +310,9 @@ function scaryNum() {
}
}
GuiHacker.prototype.consoleOutput = function(){
var accessDenied = document.querySelector(".accessdenied")
GuiHacker.prototype.consoleOutput = function(initiatedByTyping){
var textEl = document.createElement('p');
if(this.isProcessing){
@ -283,6 +321,9 @@ GuiHacker.prototype.consoleOutput = function(){
if(Date.now() > this.lastProcess + this.processTime){
this.isProcessing = false;
}
if (initiatedByTyping) {
this.processTime -= 500
}
}else{
var commandType = ~~(Math.random()*4);
switch(commandType){
@ -301,6 +342,9 @@ GuiHacker.prototype.consoleOutput = function(){
}
var outputConsole = settings.outputConsole;
if (outputConsole.childNodes.length > 1000) {
outputConsole.removeChild(outputConsole.firstChild)
}
outputConsole.scrollTop = outputConsole.scrollHeight;
outputConsole.appendChild(textEl);
@ -312,7 +356,7 @@ GuiHacker.prototype.consoleOutput = function(){
}
var self = this;
setTimeout(function(){self.consoleOutput();}, ~~(Math.random()*200));
if (!initiatedByTyping) { setTimeout(function(){self.consoleOutput();}, ~~(Math.random()*200)) };
};
@ -347,7 +391,7 @@ if (hash){
}
var adjustCanvas = function(){
if(settings.gui){
if(settings.gui) {
settings.canvas.width = (window.innerWidth/3)*2;
settings.canvas.height = window.innerHeight / 3;
@ -362,13 +406,24 @@ var adjustCanvas = function(){
settings.vpy = settings.canvas.height / 2;
settings.ctx.strokeStyle = settings.ctxBars.strokeStyle = settings.ctxBars.fillStyle = settings.color;
}else{
} else {
document.querySelector(".hacker-3d-shiz").style.display = "none";
document.querySelector(".bars-and-stuff").style.display = "none";
}
document.body.style.color = settings.color;
}(),
guiHacker = new GuiHacker(settings);
document.body.style.color = settings.color;
}
guiHacker = new GuiHacker(settings);
window.addEventListener('resize', adjustCanvas);
window.addEventListener("resize", adjustCanvas)
window.addEventListener("keydown", ev => {
if (ev.key === "d" && ev.altKey) {
console.log("denying access")
accessDenied.style.display = accessDenied.style.display === "none" ? "block" : "none"
ev.preventDefault()
}
else if (Math.random() > 0.8) {
guiHacker.consoleOutput(true)
}
})
adjustCanvas()

View File

@ -21,4 +21,4 @@ Update (19/07/2021): also consider reading [this](https://boingboing.net/2012/01
Update (06/08/2021): [Oh look, Apple just did the client-side scanning thing](https://appleprivacyletter.com/). I do not think this sets a good precedent; this is the most obviously defensible usecase for this technology, and now future extensions can just be portrayed as a natural extension of it. The best case is that this is a prelude to E2EE iCloud, but this is still a fundamental hole in the security of such a thing. Whatever happens, given government pressure, reverting this will be quite hard.
Update (19/08/2021): As it turns out, NeuralHash, which Apple intend to use for the above, is [easily collidable](https://github.com/anishathalye/neural-hash-collider) (using a fairly generic technique which should be applicable to any other neural-network-based implementation). This seems like something which should have been caught prior to release. And apparently it has [significant variations](https://github.com/AsuharietYgvar/AppleNeuralHash2ONNX) from floating point looseness, somehow. The "1 in 1 trillion" false positive rate is maybe not very likely. It [is claimed](https://www.theverge.com/2021/8/18/22630439/apple-csam-neuralhash-collision-vulnerability-flaw-cryptography) that this is not a significant issue primarily because the hashes are secret (because of course); however, this still creates a possible issues for the system, like editing the hash of an actually-bad image to avoid detection, or (with this and some way to get around the later review stages, like [adverserial image scaling](https://bdtechtalks.com/2020/08/03/machine-learning-adversarial-image-scaling/) or just using legal content likely to trigger a human false-positive) generating otherwise okay-looking images which are flagged. Also, the [Apple announcement](https://www.apple.com/child-safety/) explicitly says "These efforts will evolve and expand over time", which is a worrying thing I did not notice before.
Update (19/08/2021): As it turns out, NeuralHash, which Apple intend to use for the above, is [easily collidable](https://github.com/anishathalye/neural-hash-collider) (using a fairly generic technique which should be applicable to any other neural-network-based implementation). This seems like something which should have been caught prior to release. And apparently it has [significant variations](https://github.com/AsuharietYgvar/AppleNeuralHash2ONNX) from floating point looseness, somehow. The "1 in 1 trillion" false positive rate is maybe not very likely. It [is claimed](https://www.theverge.com/2021/8/18/22630439/apple-csam-neuralhash-collision-vulnerability-flaw-cryptography) that this is not a significant issue primarily because the hashes are secret (because of course); however, this still creates a possible issues for the system, like editing the hash of an actually-bad image to avoid detection, or (with this and some way to get around the later review stages, like [adverserial image scaling](https://bdtechtalks.com/2020/08/03/machine-learning-adversarial-image-scaling/) or just [using legal content likely to trigger a human false-positive](https://news.ycombinator.com/item?id=28238071)) generating otherwise okay-looking images which are flagged. Also, the [Apple announcement](https://www.apple.com/child-safety/) explicitly says "These efforts will evolve and expand over time", which is a worrying thing I did not notice before.

18
blog/stack.md Normal file
View File

@ -0,0 +1,18 @@
---
title: Site tech stack
description: Learn about how osmarks.net works internally! Spoiler warning if you wanted to reverse-engineer it yourself.
created: 24/02/2022
---
As you may know, osmarks.net is a website, served from computers which are believed to exist. But have you ever wondered exactly how it's all set up? If not, you may turn elsewhere and live in ignorance. Otherwise, continue reading.
Many similar personal sites are hosted on free static site services or various cloud platforms, but mine actually runs on a physical server. This was originally done because of my general distrust of SaaS/cloud platforms, to learn about Linux administration, and desire to run some non-web things, but now it's necessary to run the full range of weird components which are now important to the website. The hardware has remained the same since early 2019, before I actually had a public site, apart from the addition of more disk capacity and a spare GPU for occasional machine learning workloads - I am using an old HP ML110 G7 tower server. Despite limited RAM and CPU power compared to contemporary rackmount models, it was cheap, has continued to work amazingly reliably, and is much more power-efficient than those would have been. It mostly only runs at about 5% CPU load and 2GB of RAM in use anyway, so it's not been an issue.
The main site itself, which you're currently reading, is in fact just a simple static website. Over the years the exact implementation has varied a lot, from the original not-actually-that-static version using Caddy, some weird PHP scripts for Markdown, and a few folders of HTML files, to the later strange combination of Haskell (using Hakyll) and makefiles to the current somewhat horrible Node.js program (which also interacts with someone else's Go program. Fun!). The modern implementation of the compiler does templating, dependency resolution, Markdown and some optimization tasks in about 300 lines of poorly-described JavaScript.
Being static files, many, many different webservers could have been used for this site. In practice, it's mostly alternated randomly between [caddy](https://caddyserver.com/) (a more recent, Go-based webserver with automatic LetsEncrypt integration) and [nginx](https://nginx.org/) (an older and more powerful but slightly quirky program) - caddy generally had easier configuration, but I arbitrarily preferred nginx in some ways. After caddy v2 suddenly required me to rewrite my configuration and introduced a bunch of weird issues, I permanently switched over to nginx and haven't changed back. The configuration file is now 600 lines or so, even with inclusion of includes to shorten things, but it... works, at least. This is mostly to accommodate the bizzarely large set of subdomains I now have for various people, and reverse proxy configuration for backend services. I also use a custom-compiled build of nginx with HTTP/3 (QUIC) support and some modules compiled in.
Some of these backend things are only for personal use, but a few are related to the site itself. For example, the comment server is a standalone Python program, [isso](https://posativ.org/isso/), with corresponding JS embedded in each page. This works pretty well, but has lead to some weird quirkiness, such as each separate 404-erroring URL having its own list of comments. There's also the Random Stuff API, a custom assemblage of about 15 different Python libraries and external programs which, while technically not linked on the site, does interact with other projects like [PotatOS](https://git.osmarks.net/osmarks/potatOS/), and internal services on the same infrastructure like my [RSS reader](https://miniflux.app/). The images subdomain also uses a [PHP program](https://larsjung.de/h5ai/) to generate a nice searchable index; in fact, it is one of two PHP things I have unfortunately not yet been able to purge. There also used to be a publicly available status page using some custom code, but this doesn't work very well and has now been dropped; previously I had a Grafana (and earlier Netdata) instance there, but this has now been cancelled because it leaks a worrying amount of information.
As for the underlying OS everything runs on, I currently use [Arch Linux](https://i.osmarks.net/memes-or-something/arch-btw.png) (as well as Alpine on a few lower-resourced cloud servers). Some form of Linux is inevitable - BSDs aren't really compatible with much, and Windows is obviously unsuited for server duty - but I mostly use Arch for its stability (this sounds sarcastic, but I've actually found it to be very reliable with regular updates), wide range of packages (particularly from the AUR; as I don't really run critical production infrastructure, I can generally afford to compile stuff from source a lot), and better general ease-of-use than Alpine. As much as I vaguely resent it, this is mostly down to systemd - despite it being a horrific bloated monolith, `journalctl` is very convenient and unit files are pleasant and easy to write compared to the weird OpenRC scripts Alpine uses.
I am actually considering yet another redesign, however; switching to a dynamic site implementation instead would allow me to integrate the comment system and achievement system better, make things like the "from other blogs" tiles actually update at reasonable intervals, and arbitrarily A/B test users, although it would break some nice things like this site's very aggressive caching and fast serving. Please leave your thoughts or lack of thoughts on this in the comments.

View File

@ -40,8 +40,28 @@ description: <a href="https://github.com/osmarks/guihacker">My fork</a> of GUIHa
.description {
display: none;
}
.overlay {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: fixed;
display: flex;
justify-content: center;
align-items: center;
}
.accessdenied {
font-size: 4em;
background: #a00;
color: red;
padding: 0.5em;
border: 0.05em solid #666;
}
</style>
<canvas class='hacker-3d-shiz'></canvas>
<canvas class='bars-and-stuff'></canvas>
<div class="output-console"></div>
<div class="overlay"><div class="accessdenied" style="display: none">ACCESS DENIED</div></div>
<script src="/assets/js/h4xx0r.js"></script>

View File

@ -0,0 +1,469 @@
---
title: Tic-Tac-Toe (4³)
description: Your favourite* tic-tac-toe game in 3 dimensions, transplanted onto the main website via a slightly horrifically manual process! Technically this game is solved and always leads to player 1 winning with optimal play, but the AI is not good enough to do that without more compute!
slug: tictactoe
---
<style>
.row {
width: fit-content;
height: 4em;
}
.layer {
margin: 1em;
width: fit-content;
border: 1px solid black;
display: inline-block;
}
.cell {
width: 4em;
height: 4em;
display: inline-block;
border: 1px solid gray;
}
.cell-1 {
background: blue;
}
.cell-2 {
background: red;
}
.highlight {
background: yellow;
width: 50%;
height: 50%;
top: 25%;
left: 25%;
position: relative;
}
.view3d .layer {
display: block;
margin-bottom: 0;
margin-top: 0;
transform: rotateX(60deg) rotateZ(-45.8deg);
margin-left: 4em;
}
.view3d .layer + .layer {
margin-top: -4em;
}
.control, .control button, .control select {
border: 1px solid black;
border-radius: 0;
padding: 0.3em;
}
.control select {
transform: translateY(-1px)
}
#info {
font-size: 1.5em;
padding: 1rem;
}
</style>
</head>
<body>
<div id="screen"></div>
<div id="info"></div>
<div id="controls">
<button id="rotate" class="control">Rotate View</button>
<span class="control"><input type="checkbox" name="view3d" id="view3d"><label for="view3d">3D</label></span>
<span class="control"><input type="checkbox" name="lineassist" id="lineassist"><label for="lineassist">Line Assist</label></span>
<span class="control">
<select id="opponent">
<option value="ai1">"AI" v1</option>
<option value="ai2">"AI" v2</option>
<option value="human">Local Human</option>
</select>
Select Opponent
</span>
</div>
<script id="game-logic">
var size = 4
var gsize = size ** 3
var grid = new Uint8Array(gsize)
var range = Array(size).fill(null).map((_, index) => index)
function packCoord([l, r, c]) {
return l + r * size + c * size * size
}
function cartesianProduct(xss) {
var prods = [[]]
for (const xs of xss) {
prods = xs.flatMap(x => prods.map(p => p.concat([x])))
}
return prods
}
function rotateArray(xs, by) {
return xs.slice(by).concat(xs.slice(0, by))
}
function zip(xs, ys) {
return xs.map((x, i) => [x, ys[i]])
}
var inPlaneDiagonals = cartesianProduct([[zip(range, range), zip(range, range.map(x => x).reverse())], range]).map(([diag, a]) => diag.map(([b, c]) => [a, b, c]))
var throughLayerLines = cartesianProduct([range, range]).map(withinLayerPosition => range.map(i => withinLayerPosition.concat([i]))).concat(inPlaneDiagonals)
var winConditions = throughLayerLines.concat(throughLayerLines.map(l => l.map(c => rotateArray(c, 1))), throughLayerLines.map(l => l.map(c => rotateArray(c, 2))))
function insertDiagonalFrom(row, cell) {
var diagonal = []
var dirR = row == 0 ? 1 : -1
var dirC = cell == 0 ? 1 : -1
for (var layer = 0; layer < size; layer++) {
diagonal.push([layer, row, cell])
row += dirR
cell += dirC
}
winConditions.push(diagonal)
}
insertDiagonalFrom(0, 0)
insertDiagonalFrom(0, size - 1)
insertDiagonalFrom(size - 1, 0)
insertDiagonalFrom(size - 1, size - 1)
winConditions = winConditions.map(w => w.map(packCoord))
var lineLookup = Array(gsize).fill(null).map((_, index) => winConditions.map((w, c) => [w, c]).filter(([w, c]) => w.includes(index)).map(x => x[0]))
function containsWin(grid, last) {
outer: for (var winCondition of (last ? lineLookup[last] : winConditions)) {
var fst = grid[winCondition[0]]
for (var i = 1; i < winCondition.length; i++) {
var val = grid[winCondition[i]]
if (val != fst || val == 0) {
continue outer
}
}
return fst
}
}
function unfilledLineLocation(grid, player, last) {
outer: for (var winCondition of (last ? lineLookup[last] : winConditions)) {
var count = 0
var last = null
for (var i = 0; i < winCondition.length; i++) {
if (grid[winCondition[i]] === player) {
count++
} else if (grid[winCondition[i]] == 0) {
last = winCondition[i]
} else {
continue outer
}
}
if (count == size - 1) { return last }
}
}
// heuristic score for minmax - rate position based on difference in uncontested lines
function heuristicScore(grid, player) {
var self = 0
var other = 0
outer: for (var winCondition of winConditions) {
var count = 0
var fst = null
for (var i = 0; i < winCondition.length; i++) {
var val = grid[winCondition[i]]
if (val != 0) {
if (!fst) {
fst = val
}
if (val != fst) {
continue outer
} else {
count++
}
}
}
if (count > 0) {
if (fst === player) {
self += 1 << count
} else {
other += 1 << count
}
}
}
return self - other
}
function possibleMoves(grid) {
var out = []
for (var i = 0; i < gsize; i++) {
if (!grid[i]) {
out.push(i)
}
}
return out
}
function set(grid, cell, val) {
var out = new Uint8Array(grid.length)
out.set(grid)
out[cell] = val
return out
}
function otherPlayer(p) {
return 3 - p
}
function randomPick(xs) {
return xs[Math.floor(xs.length * Math.random())]
}
function hash(ar, seed=0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed
for (let i = 0, ch; i < ar.length; i++) {
ch = ar[i]
h1 = Math.imul(h1 ^ ch, 2654435761)
h2 = Math.imul(h2 ^ ch, 1597334677)
}
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909)
//h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909)
return /*4294967296 * (2097151 & h2) + */(h1>>>0)
}
function runRandomGame(grid, player) {
var targetPlayer = player
while (true) {
var moves = possibleMoves(grid)
if (moves.length === 0) {
return 0
}
var move = randomPick(moves)
grid = set(grid, move, player)
var winner = containsWin(grid, move)
if (winner === targetPlayer) {
return 1
} else if (winner === otherPlayer(targetPlayer) || unfilledLineLocation(grid, otherPlayer(player), move)) {
return -1
}
player = otherPlayer(player)
}
}
function scoreGrid(grid, player) {
let score = 0
for (let i = 0; i < 128; i++) {
score += runRandomGame(grid, player)
}
return score
}
function brokenMctsPolicy(grid, player) {
// hardcoded limited nonstupidity
var nextLoc = unfilledLineLocation(grid, player)
if (nextLoc != null) { return nextLoc }
nextLoc = unfilledLineLocation(grid, otherPlayer(player))
if (nextLoc != null) { return nextLoc }
var bestscore = -Infinity
var best = null
var moves = possibleMoves(grid)
for (const p of moves) {
var optn = scoreGrid(set(grid, p, player), player)
if (optn > bestscore) {
bestscore = optn
best = p
}
}
return best
}
function minimax(grid, depth, forPlayer, maximizingPlayer, alpha, beta, last=null) {
var winner = containsWin(grid, last)
if (winner) {
return winner === forPlayer ? 100000 : -100000
}
if (depth === 0) {
return heuristicScore(grid, forPlayer)
}
var moves = possibleMoves(grid)
if (moves.length === 0) { // draw
return 99999
}
if (maximizingPlayer) {
var value = -Infinity
for (var move of moves) {
value = Math.max(value, minimax(set(grid, move, forPlayer), depth - 1, forPlayer, false, alpha, beta, move))
if (value >= beta) {
break
}
alpha = Math.max(alpha, value)
}
return value
}
else {
value = Infinity
for (var move of moves) {
value = Math.min(value, minimax(set(grid, move, otherPlayer(forPlayer)), depth - 1, forPlayer, true, alpha, beta, move))
if (value <= alpha) {
break
}
beta = Math.min(beta, value)
}
return value
}
}
function minimaxPolicy(grid, player) {
var best = -Infinity
var optn = null
for (var move of possibleMoves(grid)) {
var r = minimax(set(grid, move, player), 3, player, false, -Infinity, Infinity)
if (r > best) {
optn = move
best = r
}
}
return optn
}
var policies = {
ai1: brokenMctsPolicy,
ai2: minimaxPolicy
}
</script>
<script>
// copyright (2022 CE) © © © osmarks.net hypercomputational tetrational metahexagonal industrial - unsigned integer division
var workerGlue = `
onmessage = event => {
var [policy, ...args] = event.data
postMessage(policies[policy](...args))
}
`
var highlight = new Uint8Array(gsize)
var worker = new Worker(`data:application/javascript;base64,${btoa(document.getElementById("game-logic").innerHTML + workerGlue)}`)
var screen = document.getElementById("screen")
var info = document.getElementById("info")
var rotation = 0
document.getElementById("rotate").onclick = () => {
rotation += 1
rotation %= 3
render()
}
function set3D() {
if (document.getElementById("view3d").checked) {
screen.classList.add("view3d")
} else {
screen.classList.remove("view3d")
}
}
document.getElementById("view3d").onclick = set3D
set3D()
var currentPlayer = 1
var gameOver = false
function render() {
var html = ""
for (var l = 0; l < size; l++) {
html += '<div class="layer">'
for (var r = 0; r < size; r++) {
html += '<div class="row">'
for (var c = 0; c < size; c++) {
var coord = packCoord(rotateArray([l, r, c], rotation))
var hl = ""
if (highlight[coord]) {
hl = `<div class="highlight" style="background: hsl(${highlight[coord] * 20 + 40}deg, 100%, 60%)"></div>`
}
html += `<div class="cell cell-${grid[coord]}" id="cell-${coord}">${hl}</div>`
}
html += '</div>'
}
html += '</div>'
}
screen.innerHTML = html
if (!gameOver) info.innerHTML = `Player ${currentPlayer}'s turn`
}
function reset() {
gameOver = false
grid = new Uint8Array(gsize)
currentPlayer = 1
render()
}
var opponent = document.querySelector("#opponent")
function opponentIsAI() {
return opponent.value.startsWith("ai")
}
function onTurn(move) {
if (gameOver) return true
grid[move] = currentPlayer
currentPlayer = otherPlayer(currentPlayer)
render()
var winner = containsWin(grid)
var isDraw = possibleMoves(grid).length === 0
if (winner != null || isDraw) {
setTimeout(() => {
if (winner) {
info.innerHTML = `${winner} wins! Click to reset.`
} else {
info.innerHTML = "Draw! Click to reset."
}
var el = () => {
reset()
info.removeEventListener("click", el)
}
info.addEventListener("click", el)
gameOver = true
})
return true
}
}
function findTarget(ev) {
var target = ev.target
if (target.classList.contains("highlight")) {
return target.parentNode
} else {
return target
}
}
worker.onmessage = event => {
onTurn(event.data)
}
var holdHighlight = false
screen.onmousemove = event => {
var [start, coord] = findTarget(event).id.split("-")
var coord = parseInt(coord)
if (holdHighlight) {
if (event.shiftKey) {
holdHighlight = !holdHighlight
}
return
}
highlight = new Uint8Array(gsize)
if (!isNaN(coord) && typeof coord == "number" && start == "cell" && document.getElementById("lineassist").checked) {
highlight = new Uint8Array(gsize)
for (var [line, id] of lineLookup[coord].map((x, i) => [x, i])) {
for (var pos of line) {
highlight[pos] = id + 1
}
}
}
if (event.shiftKey) {
holdHighlight = true
}
render()
return
}
screen.onclick = event => {
if (gameOver) return
holdHighlight = false
if (currentPlayer != 1 && opponentIsAI()) { return }
var [start, coord] = findTarget(event).id.split("-")
var coord = parseInt(coord)
if (isNaN(coord) || typeof coord != "number" || start != "cell") { return }
if (grid[coord]) { return }
if (!onTurn(coord) && opponentIsAI()) {
worker.postMessage([opponent.value, grid, currentPlayer])
}
}
render()
</script>

View File

@ -1,6 +1,6 @@
---
title: Whorl
description: Dice-rolling webapp.
description: Dice-rolling webapp. Not very useful pending me writing a good parser.
slug: whorl
---
<link rel="stylesheet" href="src.6e636393.css">

View File

@ -1,5 +1,5 @@
{
"name": "Oliver's Website",
"name": "osmarks' website",
"domain": "osmarks.net",
"siteDescription": "Whimsical uselessness available conveniently online.",
"feeds": [

View File

@ -9,8 +9,8 @@ rss(version='2.0')
each item in items
item
title= item.title
description= item.description
link= `https://${domain}/${item.slug}`
if item.updated
pubDate= item.updated.toDate().toUTCString()
title= item.title
description= item.description
link= `https://${domain}/${item.slug}`
if item.updated
pubDate= item.updated.toDate().toUTCString()