Whoo got unit tests that are failing...

This commit is contained in:
Lani Aung
2025-06-07 15:26:51 -07:00
parent 5e9039efad
commit 44053555ec
15 changed files with 2472 additions and 114 deletions
+103
View File
@@ -0,0 +1,103 @@
import HSL from "./hsl"
abstract class Scheme {
protected primary: HSL
protected constructor(initial: number) {
this.primary = HSL.fromHex(initial)
}
colors: number[]
}
export class Analogous extends Scheme {
constructor(initial: number) {
super(initial)
const left1 = new HSL(this.primary.hue - 60, this.primary.saturation, this.primary.lightness)
const left2 = new HSL(this.primary.hue - 30, this.primary.saturation, this.primary.lightness)
const right1 = new HSL(this.primary.hue + 30, this.primary.saturation, this.primary.lightness)
const right2 = new HSL(this.primary.hue + 60, this.primary.saturation, this.primary.lightness)
this.colors = [
left1.toHex(),
left2.toHex(),
initial,
right1.toHex(),
right2.toHex()
]
}
}
export class Monochromatic extends Scheme {
constructor(initial: number) {
super(initial)
const delta = this.primary.lightness / 6
this.colors = [
initial,
new HSL(this.primary.hue, this.primary.saturation, this.primary.lightness - delta).toHex(),
new HSL(this.primary.hue, this.primary.saturation, this.primary.lightness - delta * 2).toHex(),
new HSL(this.primary.hue, this.primary.saturation, this.primary.lightness - delta * 3).toHex(),
new HSL(this.primary.hue, this.primary.saturation, this.primary.lightness - delta * 4).toHex()
]
}
}
export class SplitComplementary extends Scheme {
constructor(initial: number) {
super(initial)
const complement = new HSL((this.primary.hue + 180) % 360, this.primary.saturation, this.primary.lightness)
this.colors = [
initial,
new HSL(complement.hue - 30, complement.saturation, complement.lightness).toHex(),
new HSL(complement.hue + 30, complement.saturation, complement.lightness).toHex()
]
}
}
export class Triadic extends Scheme {
constructor(initial: number) {
super(initial)
this.colors = [
initial,
new HSL(this.primary.hue - 120, this.primary.saturation, this.primary.lightness).toHex(),
new HSL(this.primary.hue + 120, this.primary.saturation, this.primary.lightness).toHex()
]
}
}
export class Tetradic extends Scheme {
constructor(initial: number) {
super(initial)
const second = new HSL(this.primary.hue + 45, this.primary.saturation, this.primary.lightness)
const complement = new HSL(this.primary.hue + 180, this.primary.saturation, this.primary.lightness)
const secondComplement = new HSL(second.hue + 180, this.primary.saturation, this.primary.lightness)
this.colors = [
initial,
second.toHex(),
complement.toHex(),
secondComplement.toHex()
]
}
}
export class Square extends Scheme {
constructor(initial: number) {
super(initial)
const second = new HSL(this.primary.hue + 90, this.primary.saturation, this.primary.lightness)
const third = new HSL(this.primary.hue + 180, this.primary.saturation, this.primary.lightness)
const fourth = new HSL(second.hue + 270, this.primary.saturation, this.primary.lightness)
this.colors = [
initial,
second.toHex(),
third.toHex(),
fourth.toHex()
]
}
}
+10 -6
View File
@@ -2,15 +2,15 @@ import SandPainter from "./sand-painter"
import State from "./state"
export default class Crack {
private x: number = 0
private y: number = 0
private angle: number = 0
x: number = 0
y: number = 0
angle: number = 0
readonly state: State
private painter: SandPainter
private state: State
constructor(state: State, x: number, y: number, angle: number) {
this.painter = new SandPainter(state.getRandomColor())
this.state = state
this.painter = new SandPainter(this)
this.start(x, y, angle)
}
@@ -25,9 +25,13 @@ export default class Crack {
this.x += .42 * Math.cos(this.angle * Math.PI / 180)
this.y += .42 * Math.sin(this.angle * Math.PI / 180)
const fuzzX = this.x + (Math.random() * .66 - .33)
const fuzzY = this.y + (Math.random() * .66 - .33)
const context = this.state.canvas.getContext("2d")
context.fillStyle = "#000"
context.fillRect(this.x, this.y, 1, 1)
context.fillRect(fuzzX, fuzzY, 1, 1)
this.painter.render()
const x = Math.floor(this.x)
const y = Math.floor(this.y)
+40
View File
@@ -0,0 +1,40 @@
import RGB from "./rgb"
export default class HSL {
hue: number
saturation: number
lightness: number
constructor(hue: number, saturation: number, lightness: number) {
this.hue = (hue + 360) % 360
this.saturation = saturation
this.lightness = lightness
}
// https://en.wikipedia.org/wiki/RGB_color_model
public static fromRgb(rgb: RGB) {
const redValue = rgb.red / 255,
greenValue = rgb.green / 255,
blueValue = rgb.blue / 255,
lightness = (redValue + greenValue + blueValue) / 3,
saturation = 1 - (3 / (redValue + greenValue + blueValue)) * Math.min(redValue, greenValue, blueValue)
const dividend = (redValue - greenValue) + (redValue - blueValue)
const divisor = 2 * Math.sqrt((redValue - greenValue) ^ 2 + (redValue - blueValue) * (greenValue - blueValue))
let hue = (1 / Math.cos(dividend / divisor))
if (greenValue > blueValue) hue = 360 - hue
return new HSL(hue, saturation, lightness)
}
public static fromHex(color: number): HSL {
const red = color >> 16
const green = (color - (red << 16)) >> 8
const blue = color - (red << 16) - (green << 8)
return HSL.fromRgb(new RGB(red, green, blue))
}
public toHex(): number {
return RGB.fromHsl(this).toHex()
}
}
+6 -37
View File
@@ -1,32 +1,6 @@
export class HSL {
hue: number
saturation: number
lightness: number
import HSL from "./hsl"
constructor(hue: number, saturation: number, lightness: number) {
this.hue = hue
this.saturation = saturation
this.lightness = lightness
}
// https://en.wikipedia.org/wiki/RGB_color_model
public static fromRgb(rgb: RGB) {
const redValue = rgb.red / 255,
greenValue = rgb.green / 255,
blueValue = rgb.blue / 255,
lightness = (redValue + greenValue + blueValue) / 3,
saturation = 1 - (3 / (redValue + greenValue + blueValue)) * Math.min(redValue, greenValue, blueValue)
const dividend = (redValue - greenValue) + (redValue - blueValue)
const divisor = 2 * Math.sqrt((redValue - greenValue) ^ 2 + (redValue - blueValue) * (greenValue - blueValue))
let hue = (1 / Math.cos(dividend / divisor))
if (greenValue > blueValue) hue = 360 - hue
return new HSL(hue, saturation, lightness)
}
}
export class RGB {
export default class RGB {
red: number
green: number
blue: number
@@ -37,6 +11,10 @@ export class RGB {
this.blue = blue
}
toHex(): number {
return this.red << 16 + this.green << 8 + this.blue
}
// https://en.wikipedia.org/wiki/HSL_and_HSV
public static fromHsl(hsl: HSL): RGB {
let red = 0,
@@ -71,13 +49,4 @@ export class RGB {
return new RGB((red + match) * 255, (green + match) * 255, (blue + match) * 255)
}
}
export enum Schemes {
Analogous,
Monochromatic,
SplitComplementary,
Triadic,
Tetradic,
Square
}
+16 -6
View File
@@ -1,16 +1,20 @@
import State from "./state"
import Crack from "./crack"
export default class SandPainter {
private crack: Crack
private color: number
private gain: number
constructor(color: number) {
this.color = color
constructor(crack: Crack) {
this.crack = crack
this.color = this.crack.state.getRandomColor()
this.gain = Math.random() / 10
}
render(x: number, y: number, angle: number, state: State) {
render() {
const state = this.crack.state, x = this.crack.x, y = this.crack.y, angle = this.crack.angle
let open = true, endX = x, endY = y
while (open) {
endX += .81 * Math.sin(angle * Math.PI / 180)
endY -= .81 * Math.cos(angle * Math.PI / 180)
@@ -24,14 +28,20 @@ export default class SandPainter {
}
}
this.gain += (Math.random() / 10 - .05)
if (this.gain < 0) this.gain = 0
if (this.gain > 1) this.gain = 1
const grains = 64
const hex = this.color.toString(16)
const w = this.gain / (grains - 1) // some kind of multiplier
const context = state.canvas.getContext("2d")
for (let i = 0; i < grains; i++) {
const alpha = (.1 - i / (grains * 10)) * 255
const alpha = Math.floor((.1 - i / (grains * 10)) * 255)
const paintX = x + (endX - x) * Math.sin(Math.sin(i * w))
const paintY = y + (endY - y) * Math.sin(Math.sin(i * w))
context.fillStyle = `#${hex.padStart(6, "0")}${alpha.toString(16).padStart(2, "0")}`
context.fillRect(paintX, paintY, 1, 1)
}
}
}
+3 -2
View File
@@ -2,7 +2,7 @@ import Crack from "./crack"
export default class State {
readonly canvas: HTMLCanvasElement
readonly colors: number[]
colors: number[]
readonly maxCracks: number
readonly grid: number[][]
seeds: {x: number, y: number}[]
@@ -19,7 +19,8 @@ export default class State {
this.init()
}
init() {
init(colors?: number[]) {
if (colors) this.colors = colors
this.cracks.length = 0
this.grid.length = 0
this.seeds.length = 0
+20 -1
View File
@@ -1,5 +1,6 @@
// TS attempt of http://www.complexification.net/gallery/machines/substrate/index.php by laniaung.me
import State from "./state"
import {Analogous, Monochromatic, SplitComplementary, Square, Tetradic, Triadic} from "./color-schemes"
export default class Substrate {
private readonly state: State
@@ -23,7 +24,25 @@ export default class Substrate {
requestAnimationFrame(draw)
}
setInterval(() => me.state.init(), 120 * 1000)
setInterval(() => me.state.init(me.getRandomColorScheme()), 30 * 1000)
draw()
}
getRandomColorScheme(): number[] {
const color = Math.floor(Math.random() * Math.pow(256, 3))
switch (Math.floor(Math.random() * 6)) {
case 1:
return new Monochromatic(color).colors
case 2:
return new SplitComplementary(color).colors
case 3:
return new Triadic(color).colors
case 4:
return new Tetradic(color).colors
case 5:
return new Square(color).colors
default:
return new Analogous(color).colors
}
}
}