mirror of
https://github.com/osmarks/random-stuff
synced 2025-06-07 12:54:05 +00:00
change things in some way
This commit is contained in:
parent
d057781e3c
commit
f32abdf5b6
10
.gitignore
vendored
10
.gitignore
vendored
@ -13,3 +13,13 @@ code-guessing/analytics/people
|
|||||||
*/counts-old.csv
|
*/counts-old.csv
|
||||||
*/*.png
|
*/*.png
|
||||||
*.zst
|
*.zst
|
||||||
|
tacnet.sqlite3
|
||||||
|
basic
|
||||||
|
basic.png
|
||||||
|
complex
|
||||||
|
complex.png
|
||||||
|
minecraft_images
|
||||||
|
comparisons.jsonl
|
||||||
|
nouns_cache.db
|
||||||
|
nouns.json
|
||||||
|
*.scad
|
||||||
|
@ -63,6 +63,6 @@ This comes with absolutely no guarantee of support or correct function, although
|
|||||||
* `bmp280_prometheus.py` - read BMP280 temperature/pressure sensors and export as Prometheus metrics.
|
* `bmp280_prometheus.py` - read BMP280 temperature/pressure sensors and export as Prometheus metrics.
|
||||||
* `captive_portal.py` - automatically fill in captive portal forms (WIP).
|
* `captive_portal.py` - automatically fill in captive portal forms (WIP).
|
||||||
* `scd4x_prometheus.py` - read SCD4x temperature/humidity/CO2 sensors and export as Prometheus metrics.
|
* `scd4x_prometheus.py` - read SCD4x temperature/humidity/CO2 sensors and export as Prometheus metrics.
|
||||||
* `weight_painter.py` - paint arbitrary images into neural network weight matrices. Uses a permutation, so the distribution is preserved so training dynamics remain unaffected (so long as the network doesn't care about organization below the weight matrix level - this is not safe for attention heads etc).
|
* `weight_painter.py` - paint arbitrary images into neural network weight matrices. Uses a permutation, so the distribution is preserved so training dynamics should not be affected much.
|
||||||
* `cool-effect.glsl` - a nice effect I made by accident whilst trying to make a cooler one.
|
* `cool-effect.glsl` - a nice effect I made by accident whilst trying to make a cooler one.
|
||||||
* `portable_monitor_wallmount.py` - very simple CADQuery script which generates a frame for my portable monitor's top/bottom bezels so it can be stuck to the wall and slid out easily.
|
* `portable_monitor_wallmount.py` - very simple CADQuery script which generates a frame for my portable monitor's top/bottom bezels so it can be stuck to the wall and slid out easily.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class Primes:
|
class Primes:
|
||||||
def __init__(self, max):
|
def __init__(self, max):
|
||||||
self.internal = range(2,max+1)
|
self.internal = range(2,max+1)
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
i = self.internal.__iter__().__next__()
|
i = self.internal.__iter__().__next__()
|
||||||
self.internal = filter(lambda n : n % i != 0, self.internal)
|
self.internal = filter(lambda n : n % i != 0, self.internal)
|
||||||
@ -10,4 +10,4 @@ class Primes:
|
|||||||
def __iter__(self): return self
|
def __iter__(self): return self
|
||||||
|
|
||||||
for i in Primes(100):
|
for i in Primes(100):
|
||||||
print(i)
|
print(i)
|
||||||
|
260
autocrafter.py
Normal file
260
autocrafter.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from collections import Counter, defaultdict, deque
|
||||||
|
import math
|
||||||
|
from typing import Generator
|
||||||
|
import scipy.optimize as opt
|
||||||
|
import numpy as np
|
||||||
|
import graphviz
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Recipe:
|
||||||
|
slots: list[str]
|
||||||
|
quantity: int
|
||||||
|
processing: bool
|
||||||
|
|
||||||
|
short_names = {
|
||||||
|
"minecraft:stone": "st",
|
||||||
|
"minecraft:redstone": "re",
|
||||||
|
"minecraft:glass": "gl",
|
||||||
|
"minecraft:glass_pane": "gp",
|
||||||
|
"minecraft:gold_ingot": "gi",
|
||||||
|
"minecraft:iron_ingot": "ii",
|
||||||
|
"minecraft:oak_wood": "ow",
|
||||||
|
"minecraft:oak_planks": "op",
|
||||||
|
"minecraft:oak_chest": "oc",
|
||||||
|
"computercraft:computer": "cc",
|
||||||
|
"computercraft:computer_advanced": "ca",
|
||||||
|
"computercraft:turtle": "tu",
|
||||||
|
"minecraft:birch_wood": "bw",
|
||||||
|
"minecraft:birch_planks": "bp",
|
||||||
|
"quark:birch_chest": "bc",
|
||||||
|
"computercraft:turtle_advanced": "ta",
|
||||||
|
"minecraft:gold_block": "gb",
|
||||||
|
"minecraft:coal": "co",
|
||||||
|
"minecraft:charcoal": "ch",
|
||||||
|
"minecraft:gold_ore": "go",
|
||||||
|
"minecraft:iron_ore": "io",
|
||||||
|
"minecraft:sand": "sa",
|
||||||
|
None: "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
short_names_inv = {v: k for k, v in short_names.items()}
|
||||||
|
|
||||||
|
recipes = {}
|
||||||
|
|
||||||
|
def recipe_short(output, qty, inputs, processing=False):
|
||||||
|
recipes[short_names_inv[output]] = Recipe([short_names_inv.get(slot, slot) for slot in inputs.split()], qty, processing)
|
||||||
|
|
||||||
|
recipe_short("gp", 16, "gl gl gl gl gl gl")
|
||||||
|
recipe_short("op", 4, "ow")
|
||||||
|
recipe_short("oc", 1, "op op op op - op op op op")
|
||||||
|
recipe_short("cc", 1, "st st st st re st st gp st")
|
||||||
|
recipe_short("tu", 1, "ii ii ii ii cc ii ii oc ii")
|
||||||
|
recipe_short("ca", 1, "gi gi gi gi re gi gi gp gi")
|
||||||
|
recipe_short("ta", 1, "gi gi gi gi ca gi gi oc gi")
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Inventory:
|
||||||
|
contents: dict[str, int]
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self.contents.get(item, 0)
|
||||||
|
|
||||||
|
def add(self, item, quantity):
|
||||||
|
new_inventory = self.contents.copy()
|
||||||
|
new_inventory[item] = self.contents.get(item, 0) + quantity
|
||||||
|
return Inventory(new_inventory)
|
||||||
|
|
||||||
|
def take(self, item, quantity):
|
||||||
|
return self.add(item, -quantity)
|
||||||
|
|
||||||
|
class NoRecipe(BaseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def solve(item: str, quantity: int, inventory: Inventory, use_callback, craft_callback) -> Inventory:
|
||||||
|
directly_available = min(inventory[item], quantity) # Consume items from storage if available
|
||||||
|
if directly_available > 0:
|
||||||
|
use_callback(item, directly_available)
|
||||||
|
inventory = inventory.take(item, directly_available)
|
||||||
|
quantity -= directly_available
|
||||||
|
|
||||||
|
if quantity > 0:
|
||||||
|
if recipe := recipes.get(item):
|
||||||
|
recipe_runs = math.ceil(quantity / recipe.quantity)
|
||||||
|
for citem, cquantity in Counter(recipe.slots).items():
|
||||||
|
if citem is not None:
|
||||||
|
inventory = solve(citem, recipe_runs * cquantity, inventory, use_callback, craft_callback) # Recurse into subrecipe
|
||||||
|
craft_callback(recipe, item, recipe_runs)
|
||||||
|
inventory = inventory.add(item, recipe_runs * recipe.quantity - quantity) # Add spare items to tracked inventory
|
||||||
|
else:
|
||||||
|
raise NoRecipe(item, quantity) # We need to make this and can't
|
||||||
|
|
||||||
|
return inventory
|
||||||
|
|
||||||
|
final = solve("computercraft:turtle", 1, Inventory({
|
||||||
|
"minecraft:stone": 100,
|
||||||
|
"minecraft:redstone": 1,
|
||||||
|
"minecraft:iron_ingot": 10,
|
||||||
|
"minecraft:oak_wood": 2,
|
||||||
|
"minecraft:glass": 7
|
||||||
|
}), lambda item, quantity: print(f"Using {quantity} {item}"), lambda recipe, item, runs: print(f"Crafting {runs}x{recipe.quantity} {item}"))
|
||||||
|
|
||||||
|
print(final)
|
||||||
|
|
||||||
|
def compute_item_graph(recipes_general):
|
||||||
|
recipes_forward_graph = defaultdict(set) # edge u→v exists where u is used to craft v
|
||||||
|
recipes_backward_graph = defaultdict(set) # edge u→v exists where v's recipe contains u
|
||||||
|
for src, recipes in recipes_general.items():
|
||||||
|
for recipe in recipes:
|
||||||
|
for input in Counter(recipe.slots).keys():
|
||||||
|
if input is not None:
|
||||||
|
recipes_forward_graph[input].add(src)
|
||||||
|
recipes_backward_graph[src].add(input)
|
||||||
|
|
||||||
|
return recipes_forward_graph, recipes_backward_graph
|
||||||
|
|
||||||
|
def item_graph(recipe_forward_graph, name):
|
||||||
|
dot = graphviz.Digraph("items", format="png", body=["\tpad=0.5\n"])
|
||||||
|
dot.attr("graph", nodesep="1", ranksep="1")
|
||||||
|
nodes = set()
|
||||||
|
|
||||||
|
def mk_node(item):
|
||||||
|
if not item in nodes:
|
||||||
|
dot.node(item.replace(":", "_"), "", image=f"minecraft_images/{item}.png", imagescale="true", shape="plaintext")
|
||||||
|
nodes.add(item)
|
||||||
|
|
||||||
|
for input, outputs in recipe_forward_graph.items():
|
||||||
|
mk_node(input)
|
||||||
|
for output in outputs:
|
||||||
|
dot.edge(input.replace(":", "_"), output.replace(":", "_"))
|
||||||
|
mk_node(output)
|
||||||
|
|
||||||
|
dot.render(filename=name)
|
||||||
|
|
||||||
|
recipes_general = defaultdict(list)
|
||||||
|
|
||||||
|
for src, recipe in recipes.items():
|
||||||
|
recipes_general[src].append(recipe)
|
||||||
|
|
||||||
|
item_graph(compute_item_graph(recipes_general)[0], "basic")
|
||||||
|
|
||||||
|
# Add multiple recipes for things to make the ILP solver work harder.
|
||||||
|
|
||||||
|
def recipe_short_extra(output, qty, inputs, processing=False):
|
||||||
|
recipes_general[short_names_inv[output]].append(Recipe([short_names_inv.get(slot, slot) for slot in inputs.split()], qty, processing))
|
||||||
|
|
||||||
|
recipe_short_extra("bp", 4, "bw")
|
||||||
|
recipe_short_extra("bc", 1, "bp bp bp bp - bp bp bp bp")
|
||||||
|
recipe_short_extra("tu", 1, "ii ii ii ii cc ii ii bc ii")
|
||||||
|
recipe_short_extra("gb", 1, "gi gi gi gi gi gi gi gi gi")
|
||||||
|
recipe_short("ta", 1, "gi gi gi gi ca gi gi bc gi")
|
||||||
|
recipe_short_extra("ta", 1, "gi gb gi gi tu gi - gi -")
|
||||||
|
recipe_short_extra("ta", 1, "gi gb gi gi tu gi - gi -")
|
||||||
|
for count in [1, 8]:
|
||||||
|
recipe_short_extra("gl", count, "ch" + " sa" * count, processing=True)
|
||||||
|
recipe_short_extra("gl", count, "co" + " sa" * count, processing=True)
|
||||||
|
recipe_short_extra("ii", count, "ch" + " io" * count, processing=True)
|
||||||
|
recipe_short_extra("ii", count, "co" + " io" * count, processing=True)
|
||||||
|
recipe_short_extra("gi", count, "ch" + " go" * count, processing=True)
|
||||||
|
recipe_short_extra("gi", count, "co" + " go" * count, processing=True)
|
||||||
|
recipe_short_extra("ch", count, "co" + " ow" * count, processing=True)
|
||||||
|
recipe_short_extra("ch", count, "co" + " bw" * count, processing=True)
|
||||||
|
|
||||||
|
recipes_forward_graph, recipes_backward_graph = compute_item_graph(recipes_general)
|
||||||
|
|
||||||
|
item_graph(recipes_forward_graph, "complex")
|
||||||
|
|
||||||
|
def topo_sort_inputs(item: str) -> Generator[str]:
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
# DFS to find root nodes in relevant segment (no incoming edges → no recipes)
|
||||||
|
def dfs(item):
|
||||||
|
if item in seen or not item: return
|
||||||
|
seen.add(item)
|
||||||
|
|
||||||
|
for input in recipes_backward_graph[item]:
|
||||||
|
dfs(input)
|
||||||
|
|
||||||
|
dfs(item)
|
||||||
|
roots = deque(item for item in seen if len(recipes_general[item]) == 0)
|
||||||
|
|
||||||
|
# Kahn's algorithm (approximately)
|
||||||
|
# Count incoming edges
|
||||||
|
counts = { item: len(inputs) for item, inputs in recipes_backward_graph.items() if item in seen }
|
||||||
|
|
||||||
|
while roots:
|
||||||
|
item = roots.popleft()
|
||||||
|
yield item
|
||||||
|
for out_edge in recipes_forward_graph[item]:
|
||||||
|
# Filter out items not in current operation's subtree
|
||||||
|
if (count := counts.get(out_edge)) != None:
|
||||||
|
counts[out_edge] -= 1
|
||||||
|
# It's now safe to use this item since its dependencies are all in output
|
||||||
|
if counts[out_edge] == 0:
|
||||||
|
roots.append(out_edge)
|
||||||
|
|
||||||
|
def solve_ilp(item: str, quantity: int, inventory: Inventory, use_callback, craft_callback):
|
||||||
|
sequence = list(topo_sort_inputs(item))
|
||||||
|
|
||||||
|
recipe_steps = []
|
||||||
|
|
||||||
|
# Rewrite (involved) recipes as production/consumption numbers.
|
||||||
|
items = { sitem: [] for sitem in sequence }
|
||||||
|
|
||||||
|
for item in sequence:
|
||||||
|
for recipe in recipes_general[item]:
|
||||||
|
step_changes = { sitem: 0 for sitem in sequence }
|
||||||
|
for citem, cquantity in Counter(recipe.slots).items():
|
||||||
|
if citem is not None:
|
||||||
|
step_changes[citem] = -cquantity
|
||||||
|
step_changes[item] = recipe.quantity
|
||||||
|
|
||||||
|
recipe_steps.append((item, recipe))
|
||||||
|
|
||||||
|
for sitem, coef in step_changes.items():
|
||||||
|
items[sitem].append(coef)
|
||||||
|
|
||||||
|
objective = np.ones(len(recipe_steps))
|
||||||
|
# The amount of each item we produce/consume is linearly dependent on how many times each recipe is executed.
|
||||||
|
# This matrix is that linear transform.
|
||||||
|
# Solver wants upper bounds so flip signs.
|
||||||
|
production_matrix = -np.stack([np.array(coefs) for item, coefs in items.items()])
|
||||||
|
# production_matrix @ x is the vector of item consumption, so we upper-bound that with inventory item counts
|
||||||
|
# and require that we produce the required output (negative net consumption)
|
||||||
|
item_constraint_vector = np.array([ -quantity + inventory[i] if i == item else inventory[i] for i in sequence ])
|
||||||
|
|
||||||
|
soln = opt.linprog(objective, integrality=np.ones_like(objective), A_ub=production_matrix, b_ub=item_constraint_vector)
|
||||||
|
|
||||||
|
match soln.status:
|
||||||
|
case 0:
|
||||||
|
print("OK")
|
||||||
|
# soln.x is now the number of times to execute each recipe_step
|
||||||
|
item_consumption = production_matrix @ soln.x
|
||||||
|
for item_name, consumption in zip(sequence, item_consumption):
|
||||||
|
consumption = int(consumption)
|
||||||
|
if consumption > 0:
|
||||||
|
use_callback(item_name, consumption)
|
||||||
|
inventory = inventory.take(item_name, consumption)
|
||||||
|
for (recipe_output, recipe_spec), execution_count in zip(recipe_steps, soln.x):
|
||||||
|
execution_count = int(execution_count)
|
||||||
|
if execution_count > 0:
|
||||||
|
craft_callback(recipe_spec, recipe_output, execution_count)
|
||||||
|
return inventory
|
||||||
|
case 1:
|
||||||
|
print("iteration limit reached")
|
||||||
|
raise NoRecipe
|
||||||
|
case 2:
|
||||||
|
print("infeasible")
|
||||||
|
raise NoRecipe
|
||||||
|
|
||||||
|
print(solve_ilp("computercraft:turtle_advanced", 1, Inventory({
|
||||||
|
"minecraft:stone": 100,
|
||||||
|
"minecraft:redstone": 1,
|
||||||
|
"minecraft:iron_ingot": 10,
|
||||||
|
"minecraft:gold_ore": 16,
|
||||||
|
"minecraft:oak_wood": 3,
|
||||||
|
"minecraft:birch_wood": 8,
|
||||||
|
"minecraft:glass": 7,
|
||||||
|
"minecraft:coal": 1,
|
||||||
|
"minecraft:sand": 16,
|
||||||
|
}), lambda item, quantity: print(f"Using {quantity} {item}"), lambda recipe, item, runs: print(f"Crafting {runs}x{recipe.quantity} {item}")))
|
25
bigram.py
Normal file
25
bigram.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
import math
|
||||||
|
|
||||||
|
out = Image.new("RGB", (256, 256))
|
||||||
|
|
||||||
|
ctr = defaultdict(lambda: 0)
|
||||||
|
|
||||||
|
BS = 2<<18
|
||||||
|
|
||||||
|
with open(sys.argv[2], "rb") as f:
|
||||||
|
last = b""
|
||||||
|
while xs := f.read(BS):
|
||||||
|
for a, b in zip(last + xs, last + xs[1:]):
|
||||||
|
ctr[a, b] += 1
|
||||||
|
last = bytes([xs[-1]])
|
||||||
|
|
||||||
|
ctrl = { k: math.log(v) for k, v in ctr.items() }
|
||||||
|
maxv = max(ctrl.values())
|
||||||
|
for x, y in ctrl.items():
|
||||||
|
s = int(y / maxv * 255)
|
||||||
|
out.putpixel((x[0], x[1]), (0, s, 0))
|
||||||
|
|
||||||
|
out.save(sys.argv[1])
|
@ -42,7 +42,7 @@ session = requests.Session()
|
|||||||
|
|
||||||
DETECTPORTAL_URL = "http://detectportal.firefox.com/canonical.html"
|
DETECTPORTAL_URL = "http://detectportal.firefox.com/canonical.html"
|
||||||
DETECTPORTAL_CONTENT = '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>'
|
DETECTPORTAL_CONTENT = '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>'
|
||||||
PRIORITY_KEYWORDS = {"registr", "login", "signup", "signin"}
|
PRIORITY_KEYWORDS = {"regist", "login", "signup", "signin"}
|
||||||
CONFIRM_SUFFIXES = {"2", "repeat", "confirm", "_repeat", "_confirm"}
|
CONFIRM_SUFFIXES = {"2", "repeat", "confirm", "_repeat", "_confirm"}
|
||||||
EMAIL_BASE = "0t.lt"
|
EMAIL_BASE = "0t.lt"
|
||||||
|
|
||||||
@ -85,11 +85,20 @@ def handle_response(response):
|
|||||||
queue_ext = []
|
queue_ext = []
|
||||||
for link in soup.find_all("a"):
|
for link in soup.find_all("a"):
|
||||||
if href := link.get("href"):
|
if href := link.get("href"):
|
||||||
href = urllib.parse.urljoin(response.url, href)
|
|
||||||
if is_priority(href):
|
if is_priority(href):
|
||||||
queue_ext.insert(0, href)
|
queue_ext.insert(0, href)
|
||||||
else:
|
else:
|
||||||
queue_ext.append(href)
|
queue_ext.append(href)
|
||||||
|
"""
|
||||||
|
for script in soup.find_all("script"):
|
||||||
|
if src := script.get("src"):
|
||||||
|
queue_ext.append(src)
|
||||||
|
"""
|
||||||
|
for meta in soup.find_all("meta"):
|
||||||
|
if meta.get("http-equiv", "").lower() == "refresh":
|
||||||
|
if content := meta.get("content"):
|
||||||
|
if mat := re.match(r"\d+;URL='(.*)'", content):
|
||||||
|
queue_ext.append(mat.group(1))
|
||||||
|
|
||||||
for form in soup.find_all("form"):
|
for form in soup.find_all("form"):
|
||||||
fields = {}
|
fields = {}
|
||||||
@ -152,6 +161,7 @@ def handle_response(response):
|
|||||||
response = session.post(action, data=fields)
|
response = session.post(action, data=fields)
|
||||||
handle_response(response)
|
handle_response(response)
|
||||||
|
|
||||||
|
queue_ext = [ urllib.parse.urljoin(response.url, q) for q in queue_ext ]
|
||||||
queue.extend(x for x in queue_ext if x not in tried)
|
queue.extend(x for x in queue_ext if x not in tried)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
23
compare_things.py
Normal file
23
compare_things.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
|
||||||
|
with open("nouns.json", "r") as f:
|
||||||
|
nouns = set(json.load(f))
|
||||||
|
|
||||||
|
with open("comparisons.jsonl", "a") as f:
|
||||||
|
def writeline(obj):
|
||||||
|
f.write(json.dumps(obj, separators=(",", ":")) + "\n")
|
||||||
|
|
||||||
|
for noun in nouns:
|
||||||
|
other_noun = random.choice(list(nouns - {noun}))
|
||||||
|
print(noun, "/",other_noun)
|
||||||
|
pref = input("a/b/e/x/y: ")
|
||||||
|
writeline({"a": noun, "b": other_noun, "pref": pref})
|
||||||
|
if pref == "x":
|
||||||
|
writeline({"a": noun, "b": other_noun, "pref": "x"})
|
||||||
|
nouns.remove(noun)
|
||||||
|
elif pref == "y":
|
||||||
|
writeline({"a": other_noun, "b": noun, "pref": "y"})
|
||||||
|
nouns.remove(other_noun)
|
||||||
|
|
||||||
|
f.flush()
|
53
extract_nouns.py
Normal file
53
extract_nouns.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import openai
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shelve
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
|
||||||
|
openai.api_key = os.environ["OPENAI_API_KEY"]
|
||||||
|
|
||||||
|
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
||||||
|
|
||||||
|
def chunks(text, size):
|
||||||
|
out = [""]
|
||||||
|
for line in text.split("\n"):
|
||||||
|
out[-1] += line + "\n"
|
||||||
|
if len(out[-1]) > size:
|
||||||
|
out.append("")
|
||||||
|
return [ x.removesuffix("\n") for x in out if x ]
|
||||||
|
|
||||||
|
def extract_nouns(text):
|
||||||
|
completion = client.chat.completions.create(
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
messages=[
|
||||||
|
{"role": "user", "content": f"""Extract all unique simple noun phrases from this document and put them in a JSON array in the singular:
|
||||||
|
```
|
||||||
|
{text}
|
||||||
|
```"""}],
|
||||||
|
response_format={"type": "json_object"},
|
||||||
|
max_tokens=16384,
|
||||||
|
temperature=0.2 # should be 0 but repetition issues at 0
|
||||||
|
)
|
||||||
|
result = json.loads(completion.choices[0].message.content)
|
||||||
|
return result[next(iter(result.keys()))]
|
||||||
|
|
||||||
|
with open("../website/strings.json", "r") as f:
|
||||||
|
strings = json.load(f)
|
||||||
|
|
||||||
|
nouns = set()
|
||||||
|
with shelve.open("nouns_cache.db") as db:
|
||||||
|
for bigstring in strings:
|
||||||
|
for string in chunks(bigstring, 8192):
|
||||||
|
if nouns: print(random.choices(list(nouns), k=10))
|
||||||
|
if string in db:
|
||||||
|
nouns.update(db[string])
|
||||||
|
else:
|
||||||
|
print("reading:", string[:100])
|
||||||
|
s_nouns = extract_nouns(string)
|
||||||
|
nouns.update(s_nouns)
|
||||||
|
print(len(s_nouns), "/", len(nouns))
|
||||||
|
db[string] = s_nouns
|
||||||
|
|
||||||
|
with open("nouns.json", "w") as f:
|
||||||
|
json.dump(list(nouns), f)
|
@ -1,9 +1,9 @@
|
|||||||
import os, sys, subprocess, datetime
|
import os, sys, subprocess, datetime
|
||||||
|
|
||||||
dt_threshold = datetime.datetime(2023, 6, 16).timestamp()
|
dt_threshold = datetime.datetime(2024, 10, 22).timestamp()
|
||||||
|
|
||||||
_, indir, outdir = sys.argv
|
_, indir, outdir = sys.argv
|
||||||
for x in os.listdir(indir):
|
for x in sorted(os.listdir(indir)):
|
||||||
inpath = os.path.join(indir, x)
|
inpath = os.path.join(indir, x)
|
||||||
if os.stat(inpath).st_mtime > dt_threshold:
|
if os.stat(inpath).st_mtime > dt_threshold:
|
||||||
if subprocess.run(("feh", inpath)).returncode == 0:
|
if subprocess.run(("feh", inpath)).returncode == 0:
|
||||||
|
18
parametric_mesh.py
Normal file
18
parametric_mesh.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from solid2 import *
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
THICKNESS = 3
|
||||||
|
XYSIZE = 58
|
||||||
|
HOLE_SIZE = 4
|
||||||
|
LINE_SIZE = 0.5
|
||||||
|
|
||||||
|
LINE_SPACING = HOLE_SIZE + LINE_SIZE
|
||||||
|
XYSIZE = (XYSIZE // LINE_SPACING) * LINE_SPACING + LINE_SIZE
|
||||||
|
print(XYSIZE)
|
||||||
|
|
||||||
|
xlines = [ cube(LINE_SIZE, XYSIZE, THICKNESS).translate(x, 0, 0) for x in np.arange(0, XYSIZE, LINE_SPACING) ]
|
||||||
|
ylines = [ cube(XYSIZE, LINE_SIZE, THICKNESS).translate(0, y, 0) for y in np.arange(0, XYSIZE, LINE_SPACING) ]
|
||||||
|
|
||||||
|
model = union()(*xlines).union()(*ylines)
|
||||||
|
|
||||||
|
model.save_as_scad()
|
@ -99,7 +99,10 @@ def clean_html(html):
|
|||||||
remove_unknown_tags=True,
|
remove_unknown_tags=True,
|
||||||
safe_attrs_only=True
|
safe_attrs_only=True
|
||||||
)
|
)
|
||||||
return cleaner.clean_html(feedparser.sanitizer._sanitize_html(html.replace("<!doctype html>", ""), "utf-8", "text/html"))
|
try:
|
||||||
|
return cleaner.clean_html(feedparser.sanitizer._sanitize_html(html.replace("<!doctype html>", ""), "utf-8", "text/html"))
|
||||||
|
except:
|
||||||
|
return "HTML parse error"
|
||||||
|
|
||||||
def email_to_html(emsg, debug_info=False):
|
def email_to_html(emsg, debug_info=False):
|
||||||
if isinstance(emsg, Message):
|
if isinstance(emsg, Message):
|
||||||
|
299
wfc.html
299
wfc.html
@ -1,40 +1,140 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<canvas id="canv"></canvas>
|
<style>
|
||||||
<input type="file" id="file">
|
canvas {
|
||||||
<button id="step">Step</button>
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#colors .color {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<canvas id="output"></canvas>
|
||||||
|
<canvas id="pattern"></canvas>
|
||||||
|
<div id="controls">
|
||||||
|
<button id="step">Step</button>
|
||||||
|
<button id="run">Run</button>
|
||||||
|
<button id="reset">Reset</button>
|
||||||
|
<input type="checkbox" id="hard-borders" checked><label for="hard-borders">Hard borders</label>
|
||||||
|
<input type="checkbox" id="ignore-directions" checked><label for="ignore-directions">Ignore directions</label>
|
||||||
|
<input type="checkbox" id="diagonals" checked><label for="diagonals">Diagonals</label>
|
||||||
|
</div>
|
||||||
|
<div id="colors"></div>
|
||||||
<div id="out"></div>
|
<div id="out"></div>
|
||||||
<script>
|
<script>
|
||||||
const write = line => {
|
const write = line => {
|
||||||
const out = document.querySelector("#out")
|
const out = document.querySelector("#out")
|
||||||
|
out.innerHTML = ""
|
||||||
out.appendChild(document.createTextNode(line))
|
out.appendChild(document.createTextNode(line))
|
||||||
out.appendChild(document.createElement("br"))
|
out.appendChild(document.createElement("br"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const PX = 16
|
const PATTERN_W = 6
|
||||||
const W = 16
|
const W = 16
|
||||||
const RANGE = 10
|
const PX = 24
|
||||||
const ctx = document.querySelector("canvas").getContext("2d")
|
const PATTERN_PX = 64
|
||||||
ctx.canvas.width = PX * W
|
|
||||||
ctx.canvas.height = PX * W
|
|
||||||
|
|
||||||
let grid = Array(W * W).fill(null).map(x => ({ value: null, options: new Set(Array(RANGE).fill(null).map((_, ix) => ix)) }))
|
|
||||||
const map = ([x, y]) => x + y * W
|
const map = ([x, y]) => x + y * W
|
||||||
const unmap = a => [a % W, Math.floor(a / W)]
|
const unmap = a => [a % W, Math.floor(a / W)]
|
||||||
const vsum = ([a, b], [c, d]) => [a + c, b + d]
|
const vsum = ([a, b], [c, d]) => [a + c, b + d]
|
||||||
const adj = [[0, 1], [0, -1], [1, 0], [-1, 0]]
|
const inRange = ([x, y]) => x >= 0 && x < W && y >= 0 && y < W
|
||||||
const inRange = p => p >= 0 && p < grid.length
|
|
||||||
const modclamp = x => x < 0 ? 10 + x : x % 10
|
const modclamp = x => x < 0 ? 10 + x : x % 10
|
||||||
|
|
||||||
|
let currentConstraintSet = {}
|
||||||
|
let colorsInUse = new Set()
|
||||||
|
|
||||||
|
let ignoreDirections = false
|
||||||
|
const readIgnoreDirections = () => ignoreDirections = document.querySelector("#ignore-directions").checked
|
||||||
|
readIgnoreDirections()
|
||||||
|
document.querySelector("#ignore-directions").addEventListener("change", readIgnoreDirections)
|
||||||
|
let hardBorders = false
|
||||||
|
const readHardBorders = () => hardBorders = document.querySelector("#hard-borders").checked
|
||||||
|
readHardBorders()
|
||||||
|
document.querySelector("#hard-borders").addEventListener("change", readHardBorders)
|
||||||
|
|
||||||
|
const getAdjacent = diagonals => diagonals ? [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]] : [[0, 1], [0, -1], [1, 0], [-1, 0]]
|
||||||
|
|
||||||
|
let adj = null
|
||||||
|
|
||||||
|
const ctx = document.querySelector("canvas#output").getContext("2d")
|
||||||
|
ctx.canvas.width = PX * W
|
||||||
|
ctx.canvas.height = PX * W
|
||||||
|
|
||||||
|
let grid = Array(W * W).fill(null).map(x => ({ value: null, options: new Set() }))
|
||||||
|
|
||||||
|
const joinConstraintKey = adjs => {
|
||||||
|
if (ignoreDirections) adjs.sort()
|
||||||
|
return adjs.join(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
const regenerateOptionsAt = (coord) => {
|
||||||
|
const data = grid[map(coord)]
|
||||||
|
const slots = []
|
||||||
|
for (const a of adj) {
|
||||||
|
const n = vsum(a, coord)
|
||||||
|
if (inRange(n)) {
|
||||||
|
const value = grid[map(n)].value
|
||||||
|
slots.push(value === null ? undefined : value)
|
||||||
|
} else {
|
||||||
|
slots.push(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slots.every(x => x === undefined)) {
|
||||||
|
data.options = colorsInUse // no constraints apply - shortcut
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const out = new Set()
|
||||||
|
let hasMatch = false
|
||||||
|
// N-way Cartesian product of slots
|
||||||
|
const go = (slots, acc) => {
|
||||||
|
if (slots.length === 0) {
|
||||||
|
for (const prod of acc) {
|
||||||
|
const ckey = joinConstraintKey(prod)
|
||||||
|
if (currentConstraintSet[ckey]) {
|
||||||
|
for (const val of currentConstraintSet[ckey]) {
|
||||||
|
out.add(val)
|
||||||
|
}
|
||||||
|
hasMatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const [fst, ...rest] = slots
|
||||||
|
if (fst === undefined) {
|
||||||
|
return go(rest, Array.from(colorsInUse).concat(hardBorders ? [] : [null]).flatMap(x => acc.map(xs => [x].concat(xs))))
|
||||||
|
} else {
|
||||||
|
return go(rest, acc.map(xs => [fst].concat(xs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go(slots, [[]])
|
||||||
|
data.options = out
|
||||||
|
}
|
||||||
|
|
||||||
|
const regenerateOptions = () => {
|
||||||
|
for (let x = 0; x < W; x++) {
|
||||||
|
for (let y = 0; y < W; y++) {
|
||||||
|
regenerateOptionsAt([x, y])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readAdjacent = () => {
|
||||||
|
adj = getAdjacent(document.querySelector("#diagonals").checked)
|
||||||
|
regenerateOptions()
|
||||||
|
}
|
||||||
|
readAdjacent()
|
||||||
|
document.querySelector("#diagonals").addEventListener("change", readAdjacent)
|
||||||
|
|
||||||
const updatePos = (pos, value) => {
|
const updatePos = (pos, value) => {
|
||||||
grid[map(pos)].value = value
|
grid[map(pos)].value = value
|
||||||
console.log(value)
|
|
||||||
grid[map(pos)].options = null
|
grid[map(pos)].options = null
|
||||||
for (const a of adj) {
|
for (const a of adj) {
|
||||||
const n = map(vsum(a, pos))
|
const n = vsum(a, pos)
|
||||||
if (inRange(n) && grid[n].value === null) {
|
if (inRange(n) && grid[map(n)].value === null && grid[map(n)].options !== null) {
|
||||||
for (const offset of [-1, 0, 1, 2, -2, 3, -3]) {
|
regenerateOptionsAt(n)
|
||||||
grid[n].options.delete(modclamp(offset + value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,56 +149,181 @@
|
|||||||
bestPos.push(unmap(index))
|
bestPos.push(unmap(index))
|
||||||
}
|
}
|
||||||
return [bestQty, bestPos]
|
return [bestQty, bestPos]
|
||||||
}, [RANGE, []])
|
}, [colorsInUse.size + 1, []])
|
||||||
|
|
||||||
const render = grid => {
|
const render = grid => {
|
||||||
for (let x = 0; x < W; x++) {
|
for (let x = 0; x < W; x++) {
|
||||||
for (let y = 0; y < W; y++) {
|
for (let y = 0; y < W; y++) {
|
||||||
const data = grid[map([x, y])]
|
const data = grid[map([x, y])]
|
||||||
const level = data.options && Math.floor(data.options.size / RANGE * 255).toString(16).padStart(2, "0")
|
const level = data.options && Math.floor((data.options.size + 1) / (colorsInUse.size + 1) * 255).toString(16).padStart(2, "0") || "00"
|
||||||
ctx.fillStyle = data.value !== null ? `#0000${Math.floor(data.value / RANGE * 255).toString(16).padStart(2, "0")}` : `#${level}${level}${level}`
|
ctx.fillStyle = data.value !== null ? data.value : `#${level}${level}${level}`
|
||||||
ctx.fillRect(x * PX, y * PX, PX, PX)
|
ctx.fillRect(x * PX, y * PX, PX, PX)
|
||||||
|
if (data.value === null) {
|
||||||
|
ctx.strokeStyle = "#000000"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(x * PX, y * PX)
|
||||||
|
ctx.lineTo((x + 1) * PX, (y + 1) * PX)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.strokeStyle = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let recentColors = ["#ffffff", "#000000", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff"]
|
||||||
|
const recentColorsDiv = document.querySelector("#colors")
|
||||||
|
let currentColor = recentColors[0]
|
||||||
|
|
||||||
|
const pushColor = color => {
|
||||||
|
recentColors = [color].concat(recentColors.filter(x => x !== color))
|
||||||
|
if (recentColors.length > 10) {
|
||||||
|
recentColors.shift()
|
||||||
|
}
|
||||||
|
currentColor = color
|
||||||
|
updateRecentColors()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRecentColors = () => {
|
||||||
|
recentColorsDiv.innerHTML = ""
|
||||||
|
for (const color of recentColors) {
|
||||||
|
recentColorsDiv.appendChild(document.createElement("div"))
|
||||||
|
recentColorsDiv.lastChild.classList.add("color")
|
||||||
|
recentColorsDiv.lastChild.style.backgroundColor = color
|
||||||
|
recentColorsDiv.lastChild.addEventListener("click", () => {
|
||||||
|
pushColor(color)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCanvasMouse = ev => {
|
||||||
|
if ((ev.buttons & 1) !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [x, y] = [Math.floor(ev.offsetX / PX), Math.floor(ev.offsetY / PX)]
|
||||||
|
if (x < 0 || x >= W || y < 0 || y >= W) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const coord = [x, y]
|
||||||
|
const data = grid[map(coord)]
|
||||||
|
if (data.value === null) {
|
||||||
|
pushColor(currentColor)
|
||||||
|
updatePos(coord, currentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.canvas.addEventListener("click", handleCanvasMouse)
|
||||||
|
ctx.canvas.addEventListener("mousemove", handleCanvasMouse)
|
||||||
|
|
||||||
const pick = arr => arr[Math.floor(Math.random() * arr.length)]
|
const pick = arr => arr[Math.floor(Math.random() * arr.length)]
|
||||||
|
|
||||||
render(grid)
|
render(grid)
|
||||||
|
|
||||||
step.onclick = () => {
|
step.onclick = () => {
|
||||||
const [qty, pos] = findBestCandidates(grid)
|
const [qty, pos] = findBestCandidates(grid)
|
||||||
if (qty === 0) {
|
if (pos.length === 0) {
|
||||||
write("contradiction")
|
write("Done.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
write(`${qty} options on ${pos.length} tiles`)
|
if (qty === 0) {
|
||||||
|
write("Contradiction.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
write(`${qty} options on ${pos.length} tiles.`)
|
||||||
if (qty === 1) {
|
if (qty === 1) {
|
||||||
write("resolving")
|
write(`Resolving ${pos.length} tiles.`)
|
||||||
for (const p of pos) {
|
for (const p of pos) {
|
||||||
const newValue = Array.from(grid[map(p)].options)[0]
|
const newValue = Array.from(grid[map(p)].options)[0]
|
||||||
console.log(newValue)
|
|
||||||
updatePos(p, newValue)
|
updatePos(p, newValue)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const p = pick(pos)
|
const p = pick(pos)
|
||||||
console.log(p, map(p), grid[map(p)])
|
|
||||||
const newValue = pick(Array.from(grid[map(p)].options))
|
const newValue = pick(Array.from(grid[map(p)].options))
|
||||||
console.log(newValue)
|
|
||||||
updatePos(p, newValue)
|
updatePos(p, newValue)
|
||||||
}
|
}
|
||||||
render(grid)
|
render(grid)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.file.oninput = ev => {
|
let timer
|
||||||
const file = e.target.files[0]
|
const runButton = document.querySelector("#run")
|
||||||
const url = URL.createObjectURL(file)
|
runButton.addEventListener("click", () => {
|
||||||
const img = new Image()
|
if (runButton.innerHTML === "Run") {
|
||||||
|
runButton.innerHTML = "Stop"
|
||||||
img.onload = function() {
|
timer = setInterval(step.onclick, 100)
|
||||||
URL.revokeObjectURL(img.src)
|
} else {
|
||||||
c.getContext("2d").drawImage(img, 0, 0)
|
runButton.innerHTML = "Run"
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
grid = Array(W * W).fill(null).map(x => ({ value: null, options: new Set() }))
|
||||||
|
clearInterval(timer)
|
||||||
|
runButton.innerHTML = "Run"
|
||||||
|
render(grid)
|
||||||
|
regenerateOptions()
|
||||||
|
}
|
||||||
|
document.querySelector("#reset").addEventListener("click", reset)
|
||||||
|
|
||||||
|
const patternEditor = () => {
|
||||||
|
const map = ([x, y]) => x + y * PATTERN_W
|
||||||
|
const pattern = document.querySelector("canvas#pattern")
|
||||||
|
const ctx = pattern.getContext("2d")
|
||||||
|
|
||||||
|
let grid = Array(PATTERN_W * PATTERN_W).fill(null).map(x => ({ value: currentColor }))
|
||||||
|
const inRange = ([x, y]) => x >= 0 && x < PATTERN_W && y >= 0 && y < PATTERN_W
|
||||||
|
|
||||||
|
pattern.width = PATTERN_PX * PATTERN_W
|
||||||
|
pattern.height = PATTERN_PX * PATTERN_W
|
||||||
|
|
||||||
|
const constraintKeyFor = coord => {
|
||||||
|
let constraintKey = []
|
||||||
|
for (const a of adj) {
|
||||||
|
const n = vsum(a, coord)
|
||||||
|
if (inRange(n)) {
|
||||||
|
constraintKey.push(grid[map(n)].value)
|
||||||
|
} else {
|
||||||
|
constraintKey.push(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return joinConstraintKey(constraintKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
img.src = url
|
const recomputeConstraintSet = () => {
|
||||||
|
currentConstraintSet = new Set()
|
||||||
|
colorsInUse = new Set()
|
||||||
|
for (let x = 0; x < PATTERN_W; x++) {
|
||||||
|
for (let y = 0; y < PATTERN_W; y++) {
|
||||||
|
const coord = [x, y]
|
||||||
|
const data = grid[map(coord)]
|
||||||
|
const constraintKey = constraintKeyFor(coord)
|
||||||
|
currentConstraintSet[constraintKey] ??= new Set()
|
||||||
|
currentConstraintSet[constraintKey].add(data.value)
|
||||||
|
colorsInUse.add(data.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regenerateOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePatternMouse = ev => {
|
||||||
|
if ((ev.buttons & 1) !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [x, y] = [Math.floor(ev.offsetX / PATTERN_PX), Math.floor(ev.offsetY / PATTERN_PX)]
|
||||||
|
if (x < 0 || x >= PATTERN_W || y < 0 || y >= PATTERN_W) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.fillStyle = currentColor
|
||||||
|
ctx.fillRect(PATTERN_PX * x, PATTERN_PX * y, PATTERN_PX, PATTERN_PX)
|
||||||
|
grid[map([x, y])].value = currentColor
|
||||||
|
recomputeConstraintSet()
|
||||||
|
}
|
||||||
|
pattern.addEventListener("click", handlePatternMouse)
|
||||||
|
pattern.addEventListener("mousemove", handlePatternMouse)
|
||||||
|
|
||||||
|
recomputeConstraintSet()
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
updateRecentColors()
|
||||||
|
patternEditor()
|
||||||
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user