Compare commits
No commits in common. "f277a71087c7cd90ce1d2b548cb9dc2152e19da5" and "a633cb7252329a1b9be0775e6671d89b025f7153" have entirely different histories.
f277a71087
...
a633cb7252
8 changed files with 119 additions and 99 deletions
|
@ -80,17 +80,15 @@ class DisplayHandler {
|
||||||
async spin() {
|
async spin() {
|
||||||
while (true) {
|
while (true) {
|
||||||
await wait(100);
|
await wait(100);
|
||||||
const [width, height] = await DisplayHandler.get_res();
|
const [width, height] = await this.get_res();
|
||||||
this.outputs.send({ kind: "request_display", content: { width, height } });
|
this.outputs.send({ kind: "request_display", content: { width, height } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async get_res() {
|
async get_res() {
|
||||||
const output = await run("stty", "size");
|
const width = parseInt(await run("tput", "cols")) / 2 - 2; // note : tiles are 2 char wide
|
||||||
const [raw_height, raw_width] = output.split(" ");
|
const height = parseInt(await run("tput", "lines")) - 2;
|
||||||
const width = Math.floor(parseInt(raw_width) / 2) - 2; // note : tiles are 2 char wide
|
return [width, height];
|
||||||
const height = parseInt(raw_height) - 2;
|
|
||||||
return [width, height] as const;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(raw: string) {
|
refresh(raw: string) {
|
||||||
|
@ -125,13 +123,3 @@ class ServerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.main) await main();
|
if (import.meta.main) await main();
|
||||||
|
|
||||||
Deno.test("res_check", async () => {
|
|
||||||
const ENABLED = false; // note : toggle before running test to know current resolution.
|
|
||||||
while (ENABLED) {
|
|
||||||
await wait(100);
|
|
||||||
const res = await DisplayHandler.get_res();
|
|
||||||
console.clear();
|
|
||||||
console.log("res", res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ export function wait(ms: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run(cmd: string, ...args: string[]) {
|
export async function run(cmd: string, ...args: string[]) {
|
||||||
const command = new Deno.Command(cmd, { args, stdin: "inherit", stdout: "piped" });
|
const command = new Deno.Command(cmd, { args, stdout: "piped" });
|
||||||
const output = await command.output();
|
const output = await command.output();
|
||||||
if (!output.success) throw new Error();
|
if (!output.success) throw new Error();
|
||||||
return new TextDecoder().decode(output.stdout);
|
return new TextDecoder().decode(output.stdout);
|
||||||
|
|
|
@ -1,25 +1,6 @@
|
||||||
import { v2 } from "../../common/utils.ts";
|
|
||||||
import { range, Vec2 } from "../../common/utils.ts";
|
|
||||||
import { Engine } from "../engine.ts";
|
|
||||||
import { query_in_rect } from "./world.ts";
|
|
||||||
|
|
||||||
export class CompDisplay {
|
export class CompDisplay {
|
||||||
display;
|
display;
|
||||||
constructor(display: string) {
|
constructor(display: string) {
|
||||||
this.display = display;
|
this.display = display;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sys_render_world(center: Vec2, size: Vec2) {
|
|
||||||
return (engine: Engine) => {
|
|
||||||
const radius = size.scale(0.5);
|
|
||||||
const result = Array.from(range(0, size.y())).map(() => Array.from(range(0, size.x())).map(() => " "));
|
|
||||||
const min = center.sub(radius);
|
|
||||||
const max = center.add(radius).sub(v2(1, 1));
|
|
||||||
for (const [pos, display] of engine.query_all(query_in_rect(min, max).with(CompDisplay))) {
|
|
||||||
const local_pos = pos.position.sub(min);
|
|
||||||
result[local_pos.y()][local_pos.x()] = display.display;
|
|
||||||
}
|
|
||||||
return result.map((line) => line.join("")).toReversed().join("\n");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
import { spiral } from "../../common/utils.ts";
|
||||||
import { chunks, enumerate, log_from, spiral, Vec2 } from "../../common/utils.ts";
|
import { chunks, enumerate, log_from, Vec2 } from "../../common/utils.ts";
|
||||||
import { Engine, Entity, Query } from "../engine.ts";
|
import { Engine, Entity, Query } from "../engine.ts";
|
||||||
import { CompDisplay } from "./display.ts";
|
import { CompDisplay } from "./display.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
@ -53,19 +53,12 @@ function* displacement_steps(position: Vec2, displacement: Vec2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Deno.test("test_displacement", () => {
|
|
||||||
assertEquals(
|
|
||||||
[...displacement_steps(new Vec2(1, 1), new Vec2(4, 6))],
|
|
||||||
[new Vec2(2, 2), new Vec2(3, 3), new Vec2(4, 4), new Vec2(5, 5), new Vec2(5, 6), new Vec2(5, 7)],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function query_at(pos: Vec2) {
|
export function query_at(pos: Vec2) {
|
||||||
return Query.with(CompPos).filter(([c]) => c.position.overlaps(pos));
|
return Query.filter(CompPos, (c) => c.position.overlaps(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function query_in_rect(min: Vec2, max: Vec2) {
|
export function query_in_rect(min: Vec2, max: Vec2) {
|
||||||
return Query.with(CompPos).filter(([c]) => c.position.inside(min, max));
|
return Query.filter(CompPos, (c) => c.position.inside(min, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompStructure {}
|
class CompStructure {}
|
||||||
|
|
117
server/engine.ts
117
server/engine.ts
|
@ -1,4 +1,9 @@
|
||||||
import { Awaitable, ClassMap, Constructible, first, log_from, Prototyped, wait } from "../common/utils.ts";
|
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
||||||
|
import { Awaitable, log_from, Prototyped, Vec2 } from "../common/utils.ts";
|
||||||
|
import { ClassMap } from "../common/utils.ts";
|
||||||
|
import { wait } from "../common/utils.ts";
|
||||||
|
import { Constructible } from "../common/utils.ts";
|
||||||
|
import { first } from "../common/utils.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
export class Entity {
|
export class Entity {
|
||||||
|
@ -60,11 +65,11 @@ export class Engine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*query_all<O extends QueryRes>(query: Query<O>) {
|
*query_all(query: Query) {
|
||||||
yield* query.run(this.entities.values());
|
yield* query.run(this.entities.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
query_one<O extends QueryRes>(query: Query<O>) {
|
query_one(query: Query) {
|
||||||
return first(this.query_all(query));
|
return first(this.query_all(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,52 +90,72 @@ export class Engine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryRes<T = unknown> = readonly T[];
|
function* displacement_steps(position: Vec2, displacement: Vec2) {
|
||||||
export class Query<O extends QueryRes> {
|
let pos = position;
|
||||||
extractor;
|
let left = displacement;
|
||||||
|
while (left.len() >= 1) {
|
||||||
|
if (left.x() >= 1) {
|
||||||
|
pos = pos.add(new Vec2(1, 0));
|
||||||
|
left = left.sub(new Vec2(1, 0));
|
||||||
|
}
|
||||||
|
if (left.x() <= -1) {
|
||||||
|
pos = pos.add(new Vec2(-1, 0));
|
||||||
|
left = left.sub(new Vec2(-1, 0));
|
||||||
|
}
|
||||||
|
if (left.y() >= 1) {
|
||||||
|
pos = pos.add(new Vec2(0, 1));
|
||||||
|
left = left.sub(new Vec2(0, 1));
|
||||||
|
}
|
||||||
|
if (left.y() <= -1) {
|
||||||
|
pos = pos.add(new Vec2(0, -1));
|
||||||
|
left = left.sub(new Vec2(0, -1));
|
||||||
|
}
|
||||||
|
yield pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(extractor: (entity: Entity) => O | null) {
|
Deno.test("test_displacement", () => {
|
||||||
this.extractor = extractor;
|
assertEquals(
|
||||||
|
[...displacement_steps(new Vec2(1, 1), new Vec2(4, 6))],
|
||||||
|
[new Vec2(2, 2), new Vec2(3, 3), new Vec2(4, 4), new Vec2(5, 5), new Vec2(5, 6), new Vec2(5, 7)],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export class Query {
|
||||||
|
private test;
|
||||||
|
|
||||||
|
constructor(test: (entity: Entity) => boolean) {
|
||||||
|
this.test = test;
|
||||||
|
}
|
||||||
|
|
||||||
|
and(query: Query) {
|
||||||
|
return new Query((entity) => this.test(entity) && query.test(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
static with(...components: Constructible[]) {
|
||||||
|
return new Query((entity) => {
|
||||||
|
for (const comp of components) if (!entity.components.has(comp)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
with(...components: Constructible[]) {
|
||||||
|
return this.and(Query.with(...components));
|
||||||
|
}
|
||||||
|
|
||||||
|
static filter<I>(component: Constructible<I>, filter: (comp: I, entity: Entity) => boolean) {
|
||||||
|
return new Query((entity) => {
|
||||||
|
const comp = entity.get(component);
|
||||||
|
if (comp === null) return false;
|
||||||
|
return filter(comp, entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filter<I>(component: Constructible<I>, filter: (comp: I, entity: Entity) => boolean) {
|
||||||
|
return this.and(Query.filter(component, filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
*run(entities: Iterable<Entity>) {
|
*run(entities: Iterable<Entity>) {
|
||||||
for (const entity of entities) {
|
for (const entity of entities) if (this.test(entity)) yield entity;
|
||||||
const extracted = this.extractor(entity);
|
|
||||||
if (extracted !== null) yield extracted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
and<Q extends QueryRes>(other: Query<Q>) {
|
|
||||||
const this_extractor = this.extractor;
|
|
||||||
return new Query((entity) => {
|
|
||||||
const this_extracted = this_extractor(entity);
|
|
||||||
if (this_extracted === null) return null;
|
|
||||||
const other_extracted = other.extractor(entity);
|
|
||||||
if (other_extracted === null) return null;
|
|
||||||
return [...this_extracted, ...other_extracted] as const;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static with<I>(comp: Constructible<I>) {
|
|
||||||
function extract(entity: Entity) {
|
|
||||||
const extracted = entity.get(comp);
|
|
||||||
if (extracted === null) return null;
|
|
||||||
return [extracted] as const;
|
|
||||||
}
|
|
||||||
return new Query(extract);
|
|
||||||
}
|
|
||||||
|
|
||||||
with<I>(comp: Constructible<I>) {
|
|
||||||
return this.and(Query.with(comp));
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(predicate: (comps: O) => boolean) {
|
|
||||||
const this_extract = this.extractor;
|
|
||||||
return new Query((entity) => {
|
|
||||||
const extracted = this_extract(entity);
|
|
||||||
if (extracted === null) return null;
|
|
||||||
if (predicate(extracted)) return extracted;
|
|
||||||
else return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { mts } from "../../common/mod.ts";
|
import { mts } from "../../common/mod.ts";
|
||||||
|
import { range } from "../../common/utils.ts";
|
||||||
import { log_from, v2, Vec2 } from "../../common/utils.ts";
|
import { log_from, v2, Vec2 } from "../../common/utils.ts";
|
||||||
import { CompDisplay, sys_render_world } from "../components/display.ts";
|
import { CompDisplay } from "../components/display.ts";
|
||||||
import { CompPos, sys_find_free_pos } from "../components/world.ts";
|
import { sys_find_free_pos } from "../components/world.ts";
|
||||||
|
import { CompPos, query_in_rect } from "../components/world.ts";
|
||||||
import { Engine, Entity } from "../engine.ts";
|
import { Engine, Entity } from "../engine.ts";
|
||||||
import { ClientInterface } from "../network.ts";
|
import { ClientInterface } from "../network.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
@ -58,6 +60,20 @@ export class Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sys_render_world(center: Vec2, size: Vec2) {
|
||||||
|
return (engine: Engine) => {
|
||||||
|
const radius = size.scale(0.5);
|
||||||
|
const result = Array.from(range(0, size.y())).map(() => Array.from(range(0, size.x())).map(() => " "));
|
||||||
|
const min = center.sub(radius);
|
||||||
|
const max = center.add(radius);
|
||||||
|
for (const entity of engine.query_all(query_in_rect(min, max).with(CompDisplay))) {
|
||||||
|
const local_pos = entity.get_force(CompPos).position.sub(min);
|
||||||
|
result[local_pos.y()][local_pos.x()] = entity.get_force(CompDisplay).display;
|
||||||
|
}
|
||||||
|
return result.map((line) => line.join("")).toReversed().join("\n");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function sys_spawn_player(position: Vec2) {
|
export function sys_spawn_player(position: Vec2) {
|
||||||
return (engine: Engine) => {
|
return (engine: Engine) => {
|
||||||
return engine.spawn((entity) =>
|
return engine.spawn((entity) =>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#!/bin/env -S deno run --allow-net --allow-read
|
#!/bin/env -S deno run --allow-net --allow-read
|
||||||
|
|
||||||
import { log_from, v2 } from "../common/utils.ts";
|
import { log_from, v2, wait } from "../common/utils.ts";
|
||||||
import { Engine } from "./engine.ts";
|
import { Engine } from "./engine.ts";
|
||||||
import { Session } from "./entities/player.ts";
|
import { Session } from "./entities/player.ts";
|
||||||
import { sys_spawn_structure } from "./components/world.ts";
|
import { sys_spawn_obstacle, sys_spawn_structure } from "./components/world.ts";
|
||||||
import { Gateway } from "./network.ts";
|
import { Gateway } from "./network.ts";
|
||||||
import { sys_spawn_enemy } from "./entities/enemy.ts";
|
import { sys_spawn_enemy } from "./entities/enemy.ts";
|
||||||
|
import { spiral } from "../common/utils.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -15,6 +16,14 @@ async function main() {
|
||||||
engine.run(await sys_spawn_structure("../data/structures/houses.txt", v2(2, 2)));
|
engine.run(await sys_spawn_structure("../data/structures/houses.txt", v2(2, 2)));
|
||||||
engine.run(sys_spawn_enemy(v2(1, 1)));
|
engine.run(sys_spawn_enemy(v2(1, 1)));
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await wait(5_000);
|
||||||
|
for (const pos of spiral(v2(-8, -8))) {
|
||||||
|
await wait(500);
|
||||||
|
engine.run(sys_spawn_obstacle(pos, "@@"));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const port = 9999;
|
const port = 9999;
|
||||||
const gateway = new Gateway(port);
|
const gateway = new Gateway(port);
|
||||||
log("Awaiting connection.");
|
log("Awaiting connection.");
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
#!/bin/env -S deno run --allow-net
|
#!/bin/env -S deno run --allow-net
|
||||||
|
|
||||||
import { MsgToClient, MsgToServer, mts } from "../common/mod.ts";
|
import { MsgToClient, MsgToServer, mts } from "../common/mod.ts";
|
||||||
import { channel, log_from, parsed_stream, Receiver, Sender, serialized_stream } from "../common/utils.ts";
|
import {
|
||||||
|
channel,
|
||||||
|
launch_caught,
|
||||||
|
log_from,
|
||||||
|
parsed_stream,
|
||||||
|
Receiver,
|
||||||
|
Sender,
|
||||||
|
serialized_stream,
|
||||||
|
} from "../common/utils.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
export class Gateway {
|
export class Gateway {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue