DFFMZSJOCLTBA3IGRYS2ZIJNO2MC3VLBBU42QL5DFY2SMCNFXPIAC
OL5CSNTOYADQ6NOJS5YRWY4QIAWEQRGCJGBHNSRSMJD2I43COJ7AC
Z3E7XJOW6NSBDYRDKSGTOAEJSPATAUX4JUFCL4DIL3372GL4K52QC
HUH4SI4HXIP72KQSJP2I4ELHX5KUQZM7FFGKZZGJ33DF7E3JHMYQC
ZGS4FTTFBXTF2SHYTPAJJBWEUVWVYXPSJVEFI5NYJWTW273B4NHAC
TQ57VE45BHV7MOZ6GKTYZEAMAOTXLPQ3ROCWJ2FUCITQWOYVMUIAC
DLEEYV4V7X337ZJJM775DPARMCMXMLOBXGSBCWDMZBHYKSQTGZCQC
ZHOSSPNKGFIKSFPDXCGLMSYMMX2J433VU2BUUWBKUH7TOLQUBSPQC
return Object.keys(this._values).map(key => this._values[key]);
}
// hashes only the keys - use HashableHashMap if you know that the value type here is also hashable
hashKeyset(): string {
const hashes: number[] = Object.keys(this._values).map(s => hashCode(s));
let code: number = hashes.reduce((pv, cv) => pv + cv);
return code.toString();
}
}
// Hash a string to a number. source: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
function hashCode(s: string): number {
let h = 0;
for (let i = 0; i < s.length; i++) {
h = Math.imul(31, h) + s.charCodeAt(i) | 0;
}
return h;
}
export class HashableHashMap<K extends { hash(): string }, V extends { hash(): string }> extends HashMap<K, V> {
hash(): string {
const hashes: number[] = Object.entries(this._values).map(([s, v]) => hashCode(s) + hashCode(v.hash()));
let code: number = hashes.reduce((pv, cv) => pv + cv);
return code.toString();
}
}
/**
* Same as HashMap, but actually stores the keys used to key the hashmap, instead of just their hashes.
* Allows iteration over the full key-value pair set.
*/
export class KeyedHashMap<K extends { hash(): string }, V>{
private _values: { [key: string]: [K, V] } = {};
put(key: K, value: V) {
this._values[key.hash()] = [key, value];
}
remove(key: K): void {
delete this._values[key.hash()];
}
get(key: K): V {
return this._values[key.hash()][1];
}
contains(key: K): boolean {
// V may be an undefined type
return this.get(key) !== undefined && key.hash() in this._values;
}
keys(): K[] {
return Object.keys(this._values).map(key => this._values[key][0]);
}
entries(): ([K, V])[] {
import { HashMap, HashSet } from "../lib/util/data_structures/hash";
import { ChunkGenConstants, PointNodeRef } from "../data/GameState";
import { HashMap, HashSet, KeyedHashMap } from "../lib/util/data_structures/hash";
import { ChunkGen, ChunkGenConstants, ChunkRef, PointNodeRef } from "../data/GameState";
import { RenderedPointNode } from "./RenderedPointNode";
// constructor(chunk: Chunk, onNodeFocus: (selection: PointNodeRef) => void, texture?: Pixi.Texture) {
constructor(args: { onNodeFocus: (selection: PointNodeRef) => void, nodeTexture: Pixi.Texture }) {
public renderedPointNodes: KeyedHashMap<PointNodeRef, RenderedPointNode>;
// public renderedNodes: HashMap<Vector2, Pixi.Graphics | Pixi.Sprite> = new HashMap();
// render the thing
// args here will never change, and changing this will NOT force a rerender
constructor(args: {
pointNodeTexture: Pixi.Texture,
selfChunkRef: ChunkRef, // where am i in parent
chunkGen: ChunkGen, // what is in me
stateUpdaterQueue: [Function],
ticker: Pixi.Ticker
}) {
for (let node of chunk.nodes) {
let g: Pixi.Sprite = new Pixi.Sprite(args.nodeTexture);
g.anchor.x = 0.5;
g.anchor.y = 0.5;
g.x = node.x * RenderedChunkConstants.SPACING_PX;
g.y = node.y * RenderedChunkConstants.SPACING_PX;
g.hitArea = new Pixi.Rectangle(
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
RenderedChunkConstants.NODE_HITAREA_PX,
RenderedChunkConstants.NODE_HITAREA_PX,
)
this.renderedNodes.put(node, g);
g.interactive = true;
this.selfChunkRef = args.selfChunkRef;
this.renderedPointNodes = new KeyedHashMap();
if (this.chunk.allocatedNodes.get(node)) {
g.tint = 0x00aaff;
} else if (this.chunk.selectedNodes.get(node)) {
g.tint = 0xBBBBBB;
}
g.addListener("pointerdown", () => {
onNodeFocus(new PointNodeRef({
z: 0, // TODO(bowei): fix
chunkCoord: this.chunk.location,
pointNodeCoord: node,
pointNodeId: 0, // TODO(bowei): fix
}));
console.log(`clicked chunk ${this.chunk.location.x} ${this.chunk.location.y} node ${node.x}, ${node.y}`);
// args.chunkGen.pointNodes
for (let [pointNodeCoord, pointNodeGen] of args.chunkGen.pointNodes.entries()) {
const pointNodeRef = new PointNodeRef({
z: this.selfChunkRef.z,
chunkCoord: this.selfChunkRef.chunkCoord,
pointNodeCoord: pointNodeCoord,
pointNodeId: pointNodeGen.id
})
// if nothing is selected
if (this.chunk.selectedNodes.values().length == 0) {
// select it
this.chunk.selectedNodes.put(node);
g.tint = 0xBBBBBB;
// g.alpha = 0.5;
} else if (this.chunk.selectedNodes.get(node)) {
// i was already selected, let's allocate it
this.chunk.selectedNodes.remove(node);
// try to allocate, only allow if we are connected to something already allocated
let neighbors = [node.addX(1), node.addY(1), node.addY(-1), node.addX(-1)];
let allowed = false;
for (let neighbor of neighbors) {
if (this.chunk.allocatedNodes.get(neighbor)) {
allowed = true;
break;
}
}
if (allowed) {
this.chunk.allocatedNodes.put(node);
g.tint = 0x00aaff;
} else {
g.tint = 0xFFFFFF;
window.alert('not allowed to allocate that one!');
}
// g.alpha = 0.5;
} else {
// unselect what was previously selected
for (let selected of this.chunk.selectedNodes.values()) {
this.renderedNodes.get(selected).tint = 0xFFFFFF;
this.chunk.selectedNodes.remove(selected);
}
this.chunk.selectedNodes.put(node);
g.tint = 0xBBBBBB;
}
});
this.container.addChild(g);
let renderedPointNode = new RenderedPointNode({
selfPointNodeRef: pointNodeRef,
...args
})
this.renderedPointNodes.put(pointNodeRef, renderedPointNode);
this.container.addChild(renderedPointNode.sprite);
// renderedPointNode.setCoord(pointNodeRef); renderedPointNode.setCoord();
renderedPointNode.sprite.x = pointNodeCoord.x * RenderedChunkConstants.SPACING_PX;
renderedPointNode.sprite.y = pointNodeCoord.y * RenderedChunkConstants.SPACING_PX;
// this.container.x = this.chunk.location.x * RenderedChunk.CHUNK_SPACING_PX;
// this.container.y = this.chunk.location.y * RenderedChunk.CHUNK_SPACING_PX;
}
public setLocation(chunk: ChunkRef = this.selfChunkRef): this {
this.container.x = chunk.chunkCoord.x * RenderedChunkConstants.CHUNK_SPACING_PX;
this.container.y = chunk.chunkCoord.y * RenderedChunkConstants.CHUNK_SPACING_PX;
return this
}
public hash(): string {
return this.chunk.hash();
public rerender(props: {
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}) {
for (let child of this.renderedPointNodes.values()) {
child.rerender({
isSelected: props.selectedPointNode?.pointNodeId == child.selfPointNodeRef.pointNodeId,
isAllocated: props.allocatedPointNodeSubset.contains(child.selfPointNodeRef),
})
}
// for (let node of chunk.nodes) {
// let g: Pixi.Sprite = new Pixi.Sprite(args.nodeTexture);
// g.anchor.x = 0.5;
// g.anchor.y = 0.5;
// g.x = node.x * RenderedChunkConstants.SPACING_PX;
// g.y = node.y * RenderedChunkConstants.SPACING_PX;
// g.hitArea = new Pixi.Rectangle(
// - RenderedChunkConstants.NODE_HITAREA_PX / 2,
// - RenderedChunkConstants.NODE_HITAREA_PX / 2,
// RenderedChunkConstants.NODE_HITAREA_PX,
// RenderedChunkConstants.NODE_HITAREA_PX,
// )
// this.renderedNodes.put(node, g);
// g.interactive = true;
//
// if (this.chunk.allocatedNodes.get(node)) {
// g.tint = 0x00aaff;
// } else if (this.chunk.selectedNodes.get(node)) {
// g.tint = 0xBBBBBB;
// }
// g.addListener("pointerdown", () => {
// onNodeFocus(new PointNodeRef({
// z: 0, // TODO(bowei): fix
// chunkCoord: this.chunk.location,
// pointNodeCoord: node,
// pointNodeId: 0, // TODO(bowei): fix
// }));
// console.log(`clicked chunk ${this.chunk.location.x} ${this.chunk.location.y} node ${node.x}, ${node.y}`);
//
// // if nothing is selected
// if (this.chunk.selectedNodes.values().length == 0) {
// // select it
// this.chunk.selectedNodes.put(node);
// g.tint = 0xBBBBBB;
// // g.alpha = 0.5;
// } else if (this.chunk.selectedNodes.get(node)) {
// // i was already selected, let's allocate it
// this.chunk.selectedNodes.remove(node);
// // try to allocate, only allow if we are connected to something already allocated
// let neighbors = [node.addX(1), node.addY(1), node.addY(-1), node.addX(-1)];
// let allowed = false;
// for (let neighbor of neighbors) {
// if (this.chunk.allocatedNodes.get(neighbor)) {
// allowed = true;
// break;
// }
// }
// if (allowed) {
// this.chunk.allocatedNodes.put(node);
// g.tint = 0x00aaff;
// } else {
// g.tint = 0xFFFFFF;
// window.alert('not allowed to allocate that one!');
// }
// // g.alpha = 0.5;
// } else {
// // unselect what was previously selected
// for (let selected of this.chunk.selectedNodes.values()) {
// this.renderedNodes.get(selected).tint = 0xFFFFFF;
// this.chunk.selectedNodes.remove(selected);
// }
// this.chunk.selectedNodes.put(node);
// g.tint = 0xBBBBBB;
// }
// });
// this.container.addChild(g);
// }
//
// this.container.x = this.chunk.location.x * RenderedChunk.CHUNK_SPACING_PX;
// this.container.y = this.chunk.location.y * RenderedChunk.CHUNK_SPACING_PX;
// }
//
// public hash(): string {
// return this.chunk.hash();
// }
public animate(delta: number) {
public setLocation(node: PointNodeRef = this.selfPointNodeRef) : this {
this.sprite.x = node.pointNodeCoord.x * RenderedChunkConstants.SPACING_PX;
this.sprite.y = node.pointNodeCoord.y * RenderedChunkConstants.SPACING_PX;
return this;
}
public animate(delta: number) : this {
// public isSelected(gameState: DeepReadonly<GameState>): boolean {
// return gameState.playerUI.selectedPointNode?.pointNodeId == this.selfPointNodeRef.pointNodeId;
// }
//
// public isAllocated(gameState: DeepReadonly<GameState>): boolean {
// return gameState.playerSave.allocatedPointNodeSet.get(this.selfPointNodeRef)
// }
public isSelected(gameState: DeepReadonly<GameState>): boolean {
return gameState.playerUI.selectedPointNode?.pointNodeId == this.selfPointNodeRef.pointNodeId;
}
public isAllocated(gameState: DeepReadonly<GameState>): boolean {
return gameState.playerSave.allocatedPointNodeSet.get(this.selfPointNodeRef)
}
import * as Pixi from "pixi.js";
import { ChunkRef, PointNodeRef, ZLevelGen } from "../data/GameState";
import { KeyedHashMap, HashSet } from "../lib/util/data_structures/hash";
import { Vector2 } from "../lib/util/geometry/vector2";
import { squirrel3 } from "../lib/util/random";
import { RenderedChunk, RenderedChunkConstants } from "./RenderedChunk";
export class RenderedZLevel {
public container: Pixi.Container;
public z: number;
public renderedChunks: KeyedHashMap<ChunkRef, RenderedChunk> = new KeyedHashMap();
constructor(args: {
pointNodeTexture: Pixi.Texture,
z: number,
zLevelGen: ZLevelGen, // what is in me
stateUpdaterQueue: [Function],
ticker: Pixi.Ticker
}) {
// constructor(zLevel: ZLevel, onNodeFocus: (selection: PointNodeRef) => void, texture?: Pixi.Texture) {
this.z = args.z;
this.container = new Pixi.Container();
for (let [chunkCoord, chunkGen] of args.zLevelGen.chunks.entries()) {
const chunkRef = new ChunkRef({
z: this.z,
chunkCoord,
chunkId: chunkGen.id,
});
const renderedChunk = new RenderedChunk({
selfChunkRef: chunkRef,
chunkGen,
...args
});
this.renderedChunks.put(chunkRef, renderedChunk);
this.container.addChild(renderedChunk.container);
renderedChunk.container.x = chunkCoord.x * RenderedChunkConstants.CHUNK_SPACING_PX;
renderedChunk.container.y = chunkCoord.y * RenderedChunkConstants.CHUNK_SPACING_PX;
}
}
public animate(delta: number): this {
return this;
}
public rerender(props: {
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}) {
for (let [chunkRef, child] of this.renderedChunks.entries()) {
let relevantToChunk = new HashSet(
props.allocatedPointNodeSubset.values()
.filter((pointNodeRef) => {
return pointNodeRef.chunkCoord.x === chunkRef.chunkCoord.x &&
pointNodeRef.chunkCoord.y === chunkRef.chunkCoord.y;
})
);
child.rerender({
selectedPointNode: props.selectedPointNode,
allocatedPointNodeSubset: relevantToChunk
})
}
}
}