commit 011ea8bdeb1c0ec013b1405689636915b2f58784 Author: Matthieu Jolimaitre Date: Wed May 28 00:20:03 2025 +0200 Init. diff --git a/README b/README new file mode 100644 index 0000000..b7b7dea --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +# Fisher + +Fish simulation as a TCP service. + +## TODO + +- [ ] Colors. +- [ ] Don't redraw if buffer unchanged. +- [ ] Don't overwrite fishes with space. +- [ ] Layer labels above fishes. +- [ ] Post new fishes. diff --git a/assets/fishes/albert.txt b/assets/fishes/albert.txt new file mode 100644 index 0000000..18f36c0 --- /dev/null +++ b/assets/fishes/albert.txt @@ -0,0 +1,2 @@ + _ +<•_)<( diff --git a/assets/fishes/bernard.txt b/assets/fishes/bernard.txt new file mode 100644 index 0000000..13c02ab --- /dev/null +++ b/assets/fishes/bernard.txt @@ -0,0 +1,2 @@ + ____ _ +/o'_/\( diff --git a/assets/fishes/charles.txt b/assets/fishes/charles.txt new file mode 100644 index 0000000..7169dde --- /dev/null +++ b/assets/fishes/charles.txt @@ -0,0 +1,2 @@ + ___ , +(.o.)<, diff --git a/assets/fishes/daniel.txt b/assets/fishes/daniel.txt new file mode 100644 index 0000000..7063479 --- /dev/null +++ b/assets/fishes/daniel.txt @@ -0,0 +1,2 @@ + _ +(_\/( diff --git a/assets/fishes/emile.txt b/assets/fishes/emile.txt new file mode 100644 index 0000000..7a35889 --- /dev/null +++ b/assets/fishes/emile.txt @@ -0,0 +1,2 @@ + ___ +/•v_><[ diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..9f4c051 --- /dev/null +++ b/deno.json @@ -0,0 +1,7 @@ +{ + "fmt": { + "useTabs": true, + "lineWidth": 120, + "semiColons": false + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..60b136f --- /dev/null +++ b/deno.lock @@ -0,0 +1,78 @@ +{ + "version": "4", + "specifiers": { + "npm:@spissvinkel/simplex-noise@1.0.1": "1.0.1" + }, + "npm": { + "@spissvinkel/simplex-noise@1.0.1": { + "integrity": "sha512-Ni1aqFlajxWFxkp2WuCspat6wmrsRZmDv1hgt90hm8J0XkRByd1bzlJFIKw9kC0q5MlOH5GyVrHVR/ZVvhOzqg==" + } + }, + "remote": { + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/bytes/concat.ts": "86161274b5546a02bdb3154652418efe7af8c9310e8d54107a68aaa148e0f5ed", + "https://deno.land/std@0.224.0/bytes/copy.ts": "08d85062240a7223e6ec4e2af193ad1a50c59a43f0d86ac3a7b16f3e0d77c028", + "https://deno.land/std@0.224.0/io/_common.ts": "36705cdb4dfcd338d6131bca1b16e48a4d5bf0d1dada6ce397268e88c17a5835", + "https://deno.land/std@0.224.0/io/_constants.ts": "3c7ad4695832e6e4a32e35f218c70376b62bc78621ef069a4a0a3d55739f8856", + "https://deno.land/std@0.224.0/io/buf_reader.ts": "aa6d589e567c964c8ba1f582648f3feac45e88ab2e3d2cc2c9f84fd73c05d051", + "https://deno.land/std@0.224.0/io/buf_writer.ts": "3fab3fbeae7a6ed1672207b640b6781a2369890878f8a7bb35f95d364dd6dae6", + "https://deno.land/std@0.224.0/io/buffer.ts": "4d1f805f350433e418002accec798bc6c33ce18f614afa65f987c202d7b2234e", + "https://deno.land/std@0.224.0/io/copy.ts": "63c6a4acf71fb1e89f5e47a7b3b2972f9d2c56dd645560975ead72db7eb23f61", + "https://deno.land/std@0.224.0/io/copy_n.ts": "1bad1525b1f78737cb4c11084e2f7cf22359210fea89e2f0e784bd9a4b221fca", + "https://deno.land/std@0.224.0/io/iterate_reader.ts": "1e5e4fea22d8965afb7df4ee9ab9adda0a0fc581adbea31bc2f2d25453f8a6e9", + "https://deno.land/std@0.224.0/io/limited_reader.ts": "bbe3f7dafe9dd076d0c92d88b1b94e91ecad4825997064f44c2a767737ad2526", + "https://deno.land/std@0.224.0/io/mod.ts": "f0a3f9d419394cec27099ba15ab0c8100da1e70928f95ebe646caaf667c6870c", + "https://deno.land/std@0.224.0/io/multi_reader.ts": "dd8f06d50adec0e1befb92a1d354fcf28733a4b1669b23bf534ace161ce61b1c", + "https://deno.land/std@0.224.0/io/read_all.ts": "876c1cb20adea15349c72afc86cecd3573335845ae778967aefb5e55fe5a8a4a", + "https://deno.land/std@0.224.0/io/read_delim.ts": "d26cfed53b0c2c127ec3453ccd65f474ecc36a331209e246448885434afffc4e", + "https://deno.land/std@0.224.0/io/read_int.ts": "2412f106103a271f5d770b2a61c97b13eebcd18de414a0a092ed82dfcb875903", + "https://deno.land/std@0.224.0/io/read_lines.ts": "17b96f87dbebc5edb976b3d83dc7713bddb994e9c8cb688012d6c6c26803fb9e", + "https://deno.land/std@0.224.0/io/read_long.ts": "d7cd367ab5c1263014c32a76afa0f0584c3bf3a3d860963c7368a6825094df46", + "https://deno.land/std@0.224.0/io/read_range.ts": "2ddfedbfff44e4ea8d60c0463cddb2b1860293d932d6a72a0fb78bdf412538fc", + "https://deno.land/std@0.224.0/io/read_short.ts": "f6dff570e685ade917dcb5188e8ecf0b701d6581b0cd186f08e6efe7f5ce33f7", + "https://deno.land/std@0.224.0/io/read_string_delim.ts": "f6beafa8969e6d9d6d7d679f846cd6595d8b6e99091a20a0aecbd50eb30a391e", + "https://deno.land/std@0.224.0/io/reader_from_stream_reader.ts": "a75bbc93f39df8b0e372cc1fbdc416a7cbf2a39fc4c09ddb057f1241100191c5", + "https://deno.land/std@0.224.0/io/slice_long_to_bytes.ts": "bc59a7aaac64845371dbd44debf3e864ae7b7e453127751d96e30adb29fb633b", + "https://deno.land/std@0.224.0/io/string_reader.ts": "279e9ea72e0ed7af6a9cb6da84f4148af93df849d308c31f124ca21f16d09cf2", + "https://deno.land/std@0.224.0/io/string_writer.ts": "923954c2038a622b84c294b94a3a322565fa0d67e8b4c62942b154fc1ad3bb9b", + "https://deno.land/std@0.224.0/io/to_readable_stream.ts": "ed03a44a1ec1cc55a85a857acf6cac472035298f6f3b6207ea209f93b4aefb39", + "https://deno.land/std@0.224.0/io/to_writable_stream.ts": "ef422e0425963c8a1e0481674e66c3023da50f0acbe5ef51ec9789efc3c1e2ed", + "https://deno.land/std@0.224.0/io/types.ts": "acecb3074c730b5ff487ba4fe9ce51e67bd982aa07c95e5f5679b7b2f24ad129", + "https://deno.land/std@0.224.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038", + "https://deno.land/x/noise@v1.1.0/_utils.ts": "09d276f9f2b76b7ed91d0553dfef63af2df911ef252ea831850a3e9d595b3e0c", + "https://deno.land/x/noise@v1.1.0/fractal/_math.ts": "f85d46e7e9781f54f4c6d7f360e8fff52516a0d13933fb550b14993a48cdb3af", + "https://deno.land/x/noise@v1.1.0/fractal/cubic.ts": "08da8567c1b04ba27eb789d3c1933e3ed8d5e58af1d03a278e558191e94954e1", + "https://deno.land/x/noise@v1.1.0/fractal/cylindrical.ts": "f0c324889481f2493f9787bee4059ff4da7b8127797846bbf297a0d10d675a3b", + "https://deno.land/x/noise@v1.1.0/fractal/linear.ts": "7957faa7c16289fae40d53a79a2d287bcb9eee936d0022f82d2e67fb132bb510", + "https://deno.land/x/noise@v1.1.0/fractal/mod.ts": "8a204173bd4eaf71052b61eda3c7eb9be626b983ae3885f659316594e3094596", + "https://deno.land/x/noise@v1.1.0/fractal/options.ts": "48a8c835637855a7fe77a16d38db6c4bd9b5f48912d456d1f45ba1e37e7bdc55", + "https://deno.land/x/noise@v1.1.0/fractal/rectangular.ts": "b11c760f1d0d999461e5ea1cf4d1cd8b08eb819c7029cee3bf9b09330ae79376", + "https://deno.land/x/noise@v1.1.0/fractal/spherical.ts": "a2bdd079f155594259c541238c39b2dde7b74c5a3d0f74449bed579c668bada4", + "https://deno.land/x/noise@v1.1.0/math.ts": "d93ab4c1aa2c45635db3936b0cc4c612e21cdfa7165140bb138d25b4003e0466", + "https://deno.land/x/noise@v1.1.0/mod.ts": "488e92a50b28b5209c8e6c93e736df2f4542adb90f193de1a756093548191dce", + "https://deno.land/x/noise@v1.1.0/noise.ts": "2e937c759f55681fecb17ccf88e08b358df99459115bd9f26a02313b7cf6fb38", + "https://deno.land/x/noise@v1.1.0/open_simplex/2d.ts": "01930f7f1a585c10eed02d6abf548e00ade756ff29f57439c669ad6d67070a43", + "https://deno.land/x/noise@v1.1.0/open_simplex/3d.ts": "610ff2533419ae4252b78584df341617c420a56d3cc663bcf5e32e50babb5899", + "https://deno.land/x/noise@v1.1.0/open_simplex/4d.ts": "7566693f82a35da0643ab4f404e0eca142b990065d60b38f998a842b485912f3", + "https://deno.land/x/noise@v1.1.0/open_simplex/_3d_constants.ts": "7f89ce76d03edc51bd150fbab9036ce439eda76c6ee73b738b16d6a485760b4a", + "https://deno.land/x/noise@v1.1.0/open_simplex/_4d_constants.ts": "fa218f32de12dc8311e0d7bf32cdbe438df2b45058a215340e4527dde714cee4", + "https://deno.land/x/noise@v1.1.0/open_simplex/mod.ts": "084791c4e4e44c9389cfcc6667e4078560349b999f998813a9d309522789021b", + "https://deno.land/x/noise@v1.1.0/options.ts": "6048592a492b84724af37aee34a199d0cd1887ad39edec16bcb27bf5cf2869f9", + "https://deno.land/x/noise@v1.1.0/perlin/2d.ts": "c7a4273d4e6d9ff669aa764c2eca7b990f7e760848cd56fbeceb459f45837d91", + "https://deno.land/x/noise@v1.1.0/perlin/3d.ts": "4575cbcbc3b2c0d368d215e51c3646e97b9ed0c9d8875c9f860d770b673b0533", + "https://deno.land/x/noise@v1.1.0/perlin/4d.ts": "8e878efd0edfb31d3872f33652fcb478b6433fd210630cf4d80e91ef4f219113", + "https://deno.land/x/noise@v1.1.0/perlin/mod.ts": "30a2ce75e4bf9ae74fb08e9de6b4e767f62500cf532508a0fb4dc9a09e4165c8", + "https://deno.land/x/noise@v1.1.0/perlin/options.ts": "af59a2db880352261a57208fae136700bb5ed331439e6da2b49e02a56ea7f6a2", + "https://deno.land/x/noise@v1.1.0/simplex/2d.ts": "d6cdcf77280c55c968af591b0a41bcb42a9495270e84e29fce7d568114d3b82f", + "https://deno.land/x/noise@v1.1.0/simplex/3d.ts": "e8663685cf32b2da4b400cf9504a883a1e77eb95ffff1565570ea3c48486ea16", + "https://deno.land/x/noise@v1.1.0/simplex/4d.ts": "57349d61e0461c509da85c468bd03962fc6dd75c50491b5bbafb918242190b80", + "https://deno.land/x/noise@v1.1.0/simplex/mod.ts": "084791c4e4e44c9389cfcc6667e4078560349b999f998813a9d309522789021b", + "https://deno.land/x/noise@v1.1.0/value/1d.ts": "240b0bde9623ab2bfb41caf9591c60fe3db9dff386fa23651a321ed712acf6b3", + "https://deno.land/x/noise@v1.1.0/value/2d.ts": "f8cef1670aa1559353437c720394519324aa0e6d033c51edabb833af89efd227", + "https://deno.land/x/noise@v1.1.0/value/3d.ts": "878ae8722db4a92386d140b3654246b837f635354fca591a4f5b6611d65cc24b", + "https://deno.land/x/noise@v1.1.0/value/4d.ts": "2c655fc2b4e47459dcd945e5ba1e91dd16fbf6a1638587feda5d6bb46ce21910", + "https://deno.land/x/noise@v1.1.0/value/mod.ts": "0590eb561a46e6495bbe91501db0cdec75756f4080ce64096017bdcfb7cfaf24", + "https://deno.land/x/noise@v1.1.0/value/options.ts": "6b3a0206247410b2f6a5540aedc66819c0b6a92ea53a4856be2da917e8ad6e0c" + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100755 index 0000000..739d854 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,133 @@ +#!/usr/bin/env -S deno run -A + +import { writeAll } from "https://deno.land/std@0.224.0/io/mod.ts" +import { mkSimplexNoise, SimplexNoise } from "npm:@spissvinkel/simplex-noise@1.0.1" + +async function main() { + const connections = Listener.listen(8080) + const fishes = await Fishes.load("./assets/fishes") + for await (const moved of fishes.movements()) { + console.log(moved.name, "at", moved.position) + const displayed = esc_clear() + fishes.fishes.map(display).join("") + "\n" + connections.send(displayed) + } +} + +class Listener { + private constructor( + private connections: Set, + ) {} + + public static listen(port: number) { + const connections = new Set() + ;(async () => { + for await (const conn of Deno.listen({ transport: "tcp", port })) connections.add(conn) + })() + return new Listener(connections) + } + + public async send(text: string) { + const encoded = new TextEncoder().encode(text) + const promises = new Array>() + for (const conn of this.connections) { + const promise = writeAll(conn, encoded).catch((_) => this.connections.delete(conn)) + promises.push(promise) + } + await Promise.all(promises) + } +} + +class Fishes { + private constructor( + public fishes: Fish[], + ) {} + + public static async load(path: string, max = 999) { + const fishes = new Array() + for await (const entry of Deno.readDir(path)) { + if (fishes.length >= max) break + if (!entry.isFile) continue + if (!entry.name.endsWith(".txt")) continue + const name = entry.name.slice(0, entry.name.length - ".txt".length) + const file_path = `${path}/${entry.name}` + const content = await Deno.readTextFile(file_path) + const shape = content.split("\n").filter((l) => l.length > 0) + const size: [number, number] = [shape.map((l) => l.length).reduce((a, b) => Math.max(a, b)), shape.length] + fishes.push(new Fish(name, size, shape, [Math.random() * 20, Math.random() * 20])) + } + console.log("Loaded", fishes.length, "fishes.") + return new Fishes(fishes) + } + + public async *movements() { + const noise = mkSimplexNoise(Math.random) + while (true) { + const picked = pick(this.fishes) + picked.move_into(noise, [[0, 0], [80, 25]]) + yield picked + await new Promise((res) => setTimeout(res, 200)) + } + } +} + +class Fish { + public constructor( + public name: string, + public size: [x: number, y: number], + public shape: string[], + public position: [number, number] = [0, 0], + public age = 0, + ) {} + + public move_into(noise: SimplexNoise, bounds: [[number, number], [number, number]]) { + const base = [...this.name].map((c) => c.charCodeAt(0)).reduce((a, b) => a + b) + const velocityX = noise.noise2D(this.age % 100, base) + const velocityY = noise.noise2D(this.age % 100, base + 100) + this.position[0] += velocityX + this.position[1] += velocityY + this.age += 1 + if (this.position[0] < bounds[0][0]) this.position[0] = bounds[0][0] + if (this.position[1] < bounds[0][1]) this.position[1] = bounds[0][1] + if (this.position[0] > bounds[1][0]) this.position[0] = bounds[1][0] + if (this.position[1] > bounds[1][1]) this.position[1] = bounds[1][1] + } +} + +/** + * ``` + * _ + * / + * ##### + * ##### + * ##### + * ``` + * + * @param fish Fish to display. + */ +function display(fish: Fish) { + const tick_pad = " ".repeat(fish.size[0]) + // TODO : add position and color + return [ + tick_pad + " _ " + fish.name, + tick_pad + "/", + ...fish.shape, + ] + .map((l, i) => esc_goto([fish.position[0], fish.position[1] + i]) + l) + .join("") + "\n" +} + +function pick(arr: T[], random = Math.random) { + const index = Math.floor(random() * arr.length) + return arr[index] +} + +function esc_goto(pos: [number, number]) { + const [x, y] = pos.map(Math.floor) + return "\u001B[" + y + ";" + x + "H" +} + +function esc_clear() { + return "\u001B[3J" +} + +if (import.meta.main) await main()