Created
July 4, 2025 23:54
-
-
Save tgreiser/d4d624a0300f1f1ba6acb2ad7b8086c0 to your computer and use it in GitHub Desktop.
fxhash open form with kontra
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>open-form</title> | |
| <meta charset="utf-8" /> | |
| <script src="./fxhash.min.js"></script> | |
| <script src="./node_modules/kontra/kontra.min.js"></script> | |
| <style name="style"> | |
| body, | |
| html { | |
| margin: 0; | |
| padding: 0; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| #app { | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app"></div> | |
| <script src="./index.js"></script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * To install: | |
| * npm install kontra | |
| * | |
| * See: https://straker.github.io/kontra/getting-started | |
| */ | |
| // list of hashes in the lineageof the iteration | |
| console.log("lineage:", $fx.lineage) | |
| // the depth in the lineage, ie the number of ancestors | |
| console.log("depth:", $fx.depth) | |
| // previously, you would be using $fx.rand() to get a random number [0; 1] | |
| // console.log("previously, $fx.rand():", $fx.rand()) | |
| // now, you can use $fx.rand( depth ) to get a random number using the seed at | |
| // the given depth | |
| console.log("$fx.randAt(0)", $fx.randAt(0)) | |
| // General ideas to work with open-form | |
| // - Apply randomness in sequence for each depth, so that you keep existing | |
| // properties of ancestors | |
| // - You may want to first define the features of your piece, and mutate these | |
| // features for each depth | |
| // - You also have the option to apply the same algorithm in sequence for each | |
| // depth if that fits your project. | |
| // - More generally, there are no hard rules :) | |
| // You have access to a list of hashes/randomizers for each depth, you are | |
| // free to leverage these in any way that makes sense for your art. have fun! | |
| // This is an example where the features are defined first, and the depth | |
| // is used to mutate these features. In this case the features are defined as | |
| // an array of numbers, but it can be anything :) | |
| function getFeatures(depth) { | |
| const options = { | |
| color: ["red", "green", "blue", "orange", "greenyellow", "darkviolet"], | |
| size: ["small", "medium", "big"], | |
| } | |
| // we use depth 0 to define the first features | |
| const features = { | |
| color: options.color[Math.floor($fx.randAt(0) * options.color.length)], | |
| size: options.size[Math.floor($fx.randAt(0) * options.size.length)], | |
| complexity: 0, | |
| } | |
| // we iterate through each depth to mutate some features | |
| for (let i = 0; i <= depth; i++) { | |
| // randomly mutate color or size | |
| if ($fx.randAt(i) < 0.5) { | |
| features.color = | |
| options.color[Math.floor($fx.randAt(i) * options.color.length)] | |
| } else { | |
| features.size = | |
| options.size[Math.floor($fx.randAt(i) * options.size.length)] | |
| } | |
| // we can also mutate a number for instance | |
| features.complexity += $fx.randAt(i) * 0.1 | |
| console.log(`Features at depth ${i}:`, { ...features }) | |
| } | |
| return features | |
| } | |
| function sizeToNumber(size) { | |
| switch (size) { | |
| case "small": return 100 | |
| case "medium": return 200 | |
| case "big": return 300 | |
| } | |
| } | |
| function getSprite(features, canvas) { | |
| // Set base positions once using fx(hash) randomness | |
| const baseX = $fx.randAt(0) * canvas.width * 0.5 + 0.25 * canvas.width | |
| const baseY = $fx.randAt(0) * canvas.height * 0.25 + 0.125 * canvas.height | |
| let sprite = new kontra.Sprite({ | |
| x: baseX, | |
| y: baseY, | |
| width: sizeToNumber(features.size) * $fx.randAt(0), | |
| height: sizeToNumber(features.size) * $fx.randAt(0), | |
| color: features.color, | |
| }) | |
| return sprite | |
| } | |
| // Create a canvas element and add it to the app container | |
| const appContainer = document.getElementById("app") | |
| const canvas = document.createElement('canvas') | |
| canvas.width = 640 | |
| canvas.height = 640 | |
| canvas.style.width = '100%' | |
| canvas.style.height = '100%' | |
| appContainer.appendChild(canvas) | |
| // Initialize kontra with the canvas | |
| kontra.init(canvas) | |
| let sprites = [] | |
| let seeds = [] | |
| for (let iX = 0; iX <= $fx.depth; iX++) { | |
| sprites.push(getSprite(getFeatures(iX), canvas)) | |
| seeds.push($fx.randAt(iX)) | |
| } | |
| // Create a simple game loop for drawing | |
| const loop = new kontra.GameLoop({ | |
| update: () => { | |
| // Update logic here if needed | |
| }, | |
| render: () => { | |
| // Clear the canvas | |
| const ctx = canvas.getContext('2d') | |
| ctx.clearRect(0, 0, canvas.width, canvas.height) | |
| sprites.forEach((sprite, iX) => { | |
| // modulate by sin for bouncing effect - much slower animation | |
| sprite.y = sprite.y + Math.sin(Date.now() * 0.01 * seeds[iX]) * 5 | |
| sprite.render() | |
| }) | |
| } | |
| }) | |
| loop.start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment