refactor: mass refactoring
This commit is contained in:
parent
d0d051f547
commit
6740584fc8
|
|
@ -1,312 +0,0 @@
|
|||
import { signal, Signal, computed } from '@preact/signals-core';
|
||||
import type { Part } from './Part';
|
||||
import type { Placement } from './Placement';
|
||||
import type { Region } from './Region';
|
||||
|
||||
/**
|
||||
* 游戏状态
|
||||
*/
|
||||
export interface GameStateData {
|
||||
id: string;
|
||||
name: string;
|
||||
phase?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏状态类
|
||||
* 统一管理所有 Parts, Regions, Placements
|
||||
*/
|
||||
export class GameState {
|
||||
/** 游戏基本信息 */
|
||||
data: Signal<GameStateData>;
|
||||
|
||||
/** Parts 存储 */
|
||||
parts: Signal<Map<string, Part>>;
|
||||
|
||||
/** Regions 存储 */
|
||||
regions: Signal<Map<string, Region>>;
|
||||
|
||||
/** Placements 存储 */
|
||||
placements: Signal<Map<string, Placement>>;
|
||||
|
||||
constructor(gameData: GameStateData) {
|
||||
this.data = signal(gameData);
|
||||
this.parts = signal(new Map());
|
||||
this.regions = signal(new Map());
|
||||
this.placements = signal(new Map());
|
||||
}
|
||||
|
||||
// ========== Part 相关方法 ==========
|
||||
|
||||
/**
|
||||
* 添加 Part
|
||||
*/
|
||||
addPart(part: Part): void {
|
||||
const parts = new Map(this.parts.value);
|
||||
parts.set(part.id, part);
|
||||
this.parts.value = parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Part
|
||||
*/
|
||||
getPart(partId: string): Part | undefined {
|
||||
return this.parts.value.get(partId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Part
|
||||
*/
|
||||
removePart(partId: string): void {
|
||||
const parts = new Map(this.parts.value);
|
||||
parts.delete(partId);
|
||||
this.parts.value = parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Part
|
||||
*/
|
||||
updatePart<T extends Part>(partId: string, updates: Partial<T>): void {
|
||||
const part = this.parts.value.get(partId);
|
||||
if (part) {
|
||||
const updated = { ...part, ...updates } as T;
|
||||
const parts = new Map(this.parts.value);
|
||||
parts.set(partId, updated);
|
||||
this.parts.value = parts;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Region 相关方法 ==========
|
||||
|
||||
/**
|
||||
* 添加 Region
|
||||
*/
|
||||
addRegion(region: Region): void {
|
||||
const regions = new Map(this.regions.value);
|
||||
regions.set(region.id, region);
|
||||
this.regions.value = regions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Region
|
||||
*/
|
||||
getRegion(regionId: string): Region | undefined {
|
||||
return this.regions.value.get(regionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Region
|
||||
*/
|
||||
removeRegion(regionId: string): void {
|
||||
const regions = new Map(this.regions.value);
|
||||
regions.delete(regionId);
|
||||
this.regions.value = regions;
|
||||
}
|
||||
|
||||
// ========== Placement 相关方法 ==========
|
||||
|
||||
/**
|
||||
* 添加 Placement
|
||||
*/
|
||||
addPlacement(placement: Placement): void {
|
||||
const placements = new Map(this.placements.value);
|
||||
placements.set(placement.id, placement);
|
||||
this.placements.value = placements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Placement
|
||||
*/
|
||||
getPlacement(placementId: string): Placement | undefined {
|
||||
return this.placements.value.get(placementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Placement
|
||||
*/
|
||||
removePlacement(placementId: string): void {
|
||||
const placement = this.placements.value.get(placementId);
|
||||
if (placement) {
|
||||
// 从 Region 中移除
|
||||
const region = this.regions.value.get(placement.regionId);
|
||||
if (region) {
|
||||
const current = region.placements.value;
|
||||
const index = current.indexOf(placementId);
|
||||
if (index !== -1) {
|
||||
const updated = [...current];
|
||||
updated.splice(index, 1);
|
||||
region.placements.value = updated;
|
||||
}
|
||||
|
||||
// 如果是 keyed region,清理 slot
|
||||
if (region.type === 'keyed' && region.slots) {
|
||||
const slots = new Map(region.slots.value);
|
||||
for (const [key, value] of slots.entries()) {
|
||||
if (value === placementId) {
|
||||
slots.set(key, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
region.slots.value = slots;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const placements = new Map(this.placements.value);
|
||||
placements.delete(placementId);
|
||||
this.placements.value = placements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Placement
|
||||
*/
|
||||
updatePlacement(placementId: string, updates: Partial<Placement>): void {
|
||||
const placement = this.placements.value.get(placementId);
|
||||
if (placement) {
|
||||
const updated = { ...placement, ...updates };
|
||||
const placements = new Map(this.placements.value);
|
||||
placements.set(placementId, updated);
|
||||
this.placements.value = placements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Placement 的 Part 引用
|
||||
*/
|
||||
updatePlacementPart(placementId: string, part: Part | null): void {
|
||||
const placement = this.placements.value.get(placementId);
|
||||
if (placement) {
|
||||
const updated = { ...placement, part };
|
||||
const placements = new Map(this.placements.value);
|
||||
placements.set(placementId, updated);
|
||||
this.placements.value = placements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动 Placement 到另一个 Region
|
||||
*/
|
||||
movePlacement(placementId: string, targetRegionId: string, key?: string): void {
|
||||
const placement = this.placements.value.get(placementId);
|
||||
if (!placement) {
|
||||
throw new Error(`Placement ${placementId} not found`);
|
||||
}
|
||||
|
||||
const sourceRegion = this.regions.value.get(placement.regionId);
|
||||
const targetRegion = this.regions.value.get(targetRegionId);
|
||||
|
||||
if (!targetRegion) {
|
||||
throw new Error(`Region ${targetRegionId} not found`);
|
||||
}
|
||||
|
||||
// 从源 Region 移除
|
||||
if (sourceRegion) {
|
||||
const current = sourceRegion.placements.value;
|
||||
const index = current.indexOf(placementId);
|
||||
if (index !== -1) {
|
||||
const updated = [...current];
|
||||
updated.splice(index, 1);
|
||||
sourceRegion.placements.value = updated;
|
||||
}
|
||||
|
||||
// 清理源 keyed region 的 slot
|
||||
if (sourceRegion.type === 'keyed' && sourceRegion.slots) {
|
||||
const slots = new Map(sourceRegion.slots.value);
|
||||
for (const [k, value] of slots.entries()) {
|
||||
if (value === placementId) {
|
||||
slots.set(k, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sourceRegion.slots.value = slots;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到目标 Region
|
||||
if (targetRegion.type === 'keyed') {
|
||||
if (key === undefined) {
|
||||
throw new Error('Key is required for keyed regions');
|
||||
}
|
||||
if (targetRegion.slots) {
|
||||
const slots = new Map(targetRegion.slots.value);
|
||||
slots.set(key, placementId);
|
||||
targetRegion.slots.value = slots;
|
||||
}
|
||||
}
|
||||
|
||||
const targetPlacements = [...targetRegion.placements.value, placementId];
|
||||
targetRegion.placements.value = targetPlacements;
|
||||
|
||||
// 更新 Placement 的 regionId
|
||||
const updated = { ...placement, regionId: targetRegionId };
|
||||
if (key !== undefined) {
|
||||
updated.metadata = { ...updated.metadata, key };
|
||||
}
|
||||
const placements = new Map(this.placements.value);
|
||||
placements.set(placementId, updated);
|
||||
this.placements.value = placements;
|
||||
}
|
||||
|
||||
// ========== 计算属性 ==========
|
||||
|
||||
/**
|
||||
* 获取 Region 中的所有 Placements
|
||||
*/
|
||||
getPlacementsInRegion(regionId: string): Placement[] {
|
||||
const region = this.regions.value.get(regionId);
|
||||
if (!region) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const placementIds = region.placements.value;
|
||||
return placementIds
|
||||
.map((id) => this.placements.value.get(id))
|
||||
.filter((p): p is Placement => p !== undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Part 的所有 Placements
|
||||
*/
|
||||
getPlacementsOfPart(partId: string): Placement[] {
|
||||
const allPlacements = Array.from(this.placements.value.values());
|
||||
return allPlacements.filter((p) => p.partId === partId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建计算信号:获取 Region 中的 Placement 数量
|
||||
*/
|
||||
createPlacementCountSignal(regionId: string): Signal<number> {
|
||||
const region = this.regions.value.get(regionId);
|
||||
if (!region) {
|
||||
return signal(0);
|
||||
}
|
||||
return computed(() => region.placements.value.length);
|
||||
}
|
||||
|
||||
// ========== 游戏状态管理 ==========
|
||||
|
||||
/**
|
||||
* 更新游戏阶段
|
||||
*/
|
||||
setPhase(phase: string): void {
|
||||
this.data.value = { ...this.data.value, phase };
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游戏元数据
|
||||
*/
|
||||
updateMetadata(updates: Record<string, unknown>): void {
|
||||
this.data.value = {
|
||||
...this.data.value,
|
||||
metadata: { ...this.data.value.metadata, ...updates },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建游戏状态
|
||||
*/
|
||||
export function createGameState(data: GameStateData): GameState {
|
||||
return new GameState(data);
|
||||
}
|
||||
103
src/core/Part.ts
103
src/core/Part.ts
|
|
@ -1,103 +0,0 @@
|
|||
import { signal } from '@preact/signals-core';
|
||||
|
||||
/**
|
||||
* Part 类型枚举
|
||||
*/
|
||||
export enum PartType {
|
||||
Meeple = 'meeple',
|
||||
Card = 'card',
|
||||
Tile = 'tile',
|
||||
}
|
||||
|
||||
/**
|
||||
* Part 的基础属性
|
||||
*/
|
||||
export interface PartBase {
|
||||
id: string;
|
||||
type: PartType;
|
||||
name?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meeple 特有属性
|
||||
*/
|
||||
export interface MeeplePart extends PartBase {
|
||||
type: PartType.Meeple;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Card 特有属性
|
||||
*/
|
||||
export interface CardPart extends PartBase {
|
||||
type: PartType.Card;
|
||||
suit?: string;
|
||||
value?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tile 特有属性
|
||||
*/
|
||||
export interface TilePart extends PartBase {
|
||||
type: PartType.Tile;
|
||||
pattern?: string;
|
||||
rotation?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part 联合类型
|
||||
*/
|
||||
export type Part = MeeplePart | CardPart | TilePart;
|
||||
|
||||
/**
|
||||
* Part 信号类型
|
||||
*/
|
||||
export type PartSignal = ReturnType<typeof signal<Part>>;
|
||||
|
||||
/**
|
||||
* 创建 Part
|
||||
*/
|
||||
export function createPart<T extends Part>(part: T): T {
|
||||
return part;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Meeple Part
|
||||
*/
|
||||
export function createMeeple(id: string, color: string, options?: { name?: string; metadata?: Record<string, unknown> }): MeeplePart {
|
||||
return {
|
||||
id,
|
||||
type: PartType.Meeple,
|
||||
color,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Card Part
|
||||
*/
|
||||
export function createCard(
|
||||
id: string,
|
||||
options?: { suit?: string; value?: number | string; name?: string; metadata?: Record<string, unknown> }
|
||||
): CardPart {
|
||||
return {
|
||||
id,
|
||||
type: PartType.Card,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Tile Part
|
||||
*/
|
||||
export function createTile(
|
||||
id: string,
|
||||
options?: { pattern?: string; rotation?: number; name?: string; metadata?: Record<string, unknown> }
|
||||
): TilePart {
|
||||
return {
|
||||
id,
|
||||
type: PartType.Tile,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import { signal, Signal } from '@preact/signals-core';
|
||||
import type { Part } from './Part';
|
||||
|
||||
/**
|
||||
* Placement 的位置信息
|
||||
*/
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placement 属性
|
||||
*/
|
||||
export interface PlacementProperties {
|
||||
id: string;
|
||||
partId: string;
|
||||
regionId: string;
|
||||
position?: Position;
|
||||
rotation?: number;
|
||||
faceUp?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placement 类型
|
||||
*/
|
||||
export interface Placement extends PlacementProperties {
|
||||
part: Part | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placement 信号类型
|
||||
*/
|
||||
export type PlacementSignal = Signal<Placement>;
|
||||
|
||||
/**
|
||||
* 创建 Placement
|
||||
*/
|
||||
export function createPlacement(properties: {
|
||||
id: string;
|
||||
partId: string;
|
||||
regionId: string;
|
||||
part: Part;
|
||||
position?: Position;
|
||||
rotation?: number;
|
||||
faceUp?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
}): Placement {
|
||||
return {
|
||||
id: properties.id,
|
||||
partId: properties.partId,
|
||||
regionId: properties.regionId,
|
||||
part: properties.part,
|
||||
position: properties.position,
|
||||
rotation: properties.rotation ?? 0,
|
||||
faceUp: properties.faceUp ?? true,
|
||||
metadata: properties.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Placement 的 Part 引用
|
||||
*/
|
||||
export function updatePlacementPart(placement: Placement, part: Part | null): void {
|
||||
placement.part = part;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Placement 的位置
|
||||
*/
|
||||
export function updatePlacementPosition(placement: Placement, position: Position): void {
|
||||
placement.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Placement 的旋转角度
|
||||
*/
|
||||
export function updatePlacementRotation(placement: Placement, rotation: number): void {
|
||||
placement.rotation = rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻转 Placement
|
||||
*/
|
||||
export function flipPlacement(placement: Placement): void {
|
||||
placement.faceUp = !placement.faceUp;
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
import { signal, Signal } from '@preact/signals-core';
|
||||
import type { Placement } from './Placement';
|
||||
|
||||
/**
|
||||
* Region 类型
|
||||
*/
|
||||
export enum RegionType {
|
||||
/**
|
||||
* Keyed Region - 子元素通过 key 索引
|
||||
* 适用于:玩家手牌、版图格子等有固定位置的区域
|
||||
*/
|
||||
Keyed = 'keyed',
|
||||
/**
|
||||
* Unkeyed Region - 子元素按顺序排列
|
||||
* 适用于:牌库、弃牌堆等堆叠区域
|
||||
*/
|
||||
Unkeyed = 'unkeyed',
|
||||
}
|
||||
|
||||
/**
|
||||
* Region 属性
|
||||
*/
|
||||
export interface RegionProperties {
|
||||
id: string;
|
||||
type: RegionType;
|
||||
name?: string;
|
||||
capacity?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyed Region 的槽位
|
||||
*/
|
||||
export interface Slot {
|
||||
key: string;
|
||||
placementId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Region 类型
|
||||
*/
|
||||
export interface Region extends RegionProperties {
|
||||
placements: Signal<string[]>; // Placement ID 列表
|
||||
slots?: Signal<Map<string, string | null>>; // Keyed Region 专用:key -> placementId
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Region
|
||||
*/
|
||||
export function createRegion(properties: RegionProperties): Region {
|
||||
const region: Region = {
|
||||
...properties,
|
||||
placements: signal<string[]>([]),
|
||||
};
|
||||
|
||||
if (properties.type === RegionType.Keyed) {
|
||||
region.slots = signal<Map<string, string | null>>(new Map());
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 Placement ID 到 Region (unkeyed)
|
||||
*/
|
||||
export function addPlacementToRegion(region: Region, placementId: string): void {
|
||||
if (region.type === RegionType.Keyed) {
|
||||
throw new Error('Cannot use addPlacementToRegion on a keyed region. Use setSlot instead.');
|
||||
}
|
||||
|
||||
const current = region.placements.value;
|
||||
if (region.capacity !== undefined && current.length >= region.capacity) {
|
||||
throw new Error(`Region ${region.id} has reached its capacity of ${region.capacity}`);
|
||||
}
|
||||
region.placements.value = [...current, placementId];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Region 移除 Placement ID
|
||||
*/
|
||||
export function removePlacementFromRegion(region: Region, placementId: string): void {
|
||||
const current = region.placements.value;
|
||||
const index = current.indexOf(placementId);
|
||||
if (index !== -1) {
|
||||
const updated = [...current];
|
||||
updated.splice(index, 1);
|
||||
region.placements.value = updated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Keyed Region 的槽位
|
||||
*/
|
||||
export function setSlot(region: Region, key: string, placementId: string | null): void {
|
||||
if (region.type !== RegionType.Keyed || !region.slots) {
|
||||
throw new Error('Cannot use setSlot on an unkeyed region.');
|
||||
}
|
||||
|
||||
const slots = new Map(region.slots.value);
|
||||
|
||||
// 如果是放置新 placement,需要更新 placements 列表
|
||||
if (placementId !== null) {
|
||||
const currentPlacements = region.placements.value;
|
||||
if (!currentPlacements.includes(placementId)) {
|
||||
region.placements.value = [...currentPlacements, placementId];
|
||||
}
|
||||
}
|
||||
|
||||
slots.set(key, placementId);
|
||||
region.slots.value = slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Keyed Region 的槽位
|
||||
*/
|
||||
export function getSlot(region: Region, key: string): string | null {
|
||||
if (region.type !== RegionType.Keyed || !region.slots) {
|
||||
throw new Error('Cannot use getSlot on an unkeyed region.');
|
||||
}
|
||||
return region.slots.value.get(key) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 Region
|
||||
*/
|
||||
export function clearRegion(region: Region): void {
|
||||
region.placements.value = [];
|
||||
if (region.slots) {
|
||||
region.slots.value = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Region 中 Placement 的数量
|
||||
*/
|
||||
export function getPlacementCount(region: Region): number {
|
||||
return region.placements.value.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Region 是否为空
|
||||
*/
|
||||
export function isRegionEmpty(region: Region): boolean {
|
||||
return region.placements.value.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Region 是否已满
|
||||
*/
|
||||
export function isRegionFull(region: Region): boolean {
|
||||
if (region.capacity === undefined) {
|
||||
return false;
|
||||
}
|
||||
return region.placements.value.length >= region.capacity;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import {createModel, Signal, signal} from '@preact/signals-core';
|
||||
import {createEntityCollection} from "../utils/entity";
|
||||
import {Part} from "./part";
|
||||
import {Region} from "./region";
|
||||
|
||||
export type Context = {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export const GameContext = createModel((root: Context) => {
|
||||
const parts = createEntityCollection<Part>();
|
||||
const regions = createEntityCollection<Region>();
|
||||
const contexts = signal([signal(root)]);
|
||||
function pushContext(context: Context) {
|
||||
const ctxSignal = signal(context);
|
||||
contexts.value = [...contexts.value, ctxSignal];
|
||||
return context;
|
||||
}
|
||||
function popContext() {
|
||||
contexts.value = contexts.value.slice(0, -1);
|
||||
}
|
||||
function latestContext<T extends Context>(type: T['type']){
|
||||
for(let i = contexts.value.length - 1; i >= 0; i--){
|
||||
if(contexts.value[i].value.type === type){
|
||||
return contexts.value[i] as Signal<T>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
parts,
|
||||
regions,
|
||||
contexts,
|
||||
pushContext,
|
||||
popContext,
|
||||
latestContext,
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import {Entity, EntityAccessor} from "../utils/entity";
|
||||
import {Region} from "./region";
|
||||
import {RNG} from "../utils/rng";
|
||||
|
||||
export type Part = Entity & {
|
||||
// cards have 2 sides, dices have multiple, tokens have 1
|
||||
sides: number;
|
||||
|
||||
// mostly rotations, if relevant
|
||||
alignments?: string[];
|
||||
|
||||
// current side
|
||||
side: number;
|
||||
// current alignment
|
||||
alignment?: string;
|
||||
|
||||
// current region
|
||||
region: EntityAccessor<Region>;
|
||||
// current position in region
|
||||
position: number[];
|
||||
}
|
||||
|
||||
export function flip(part: Part) {
|
||||
part.side = (part.side + 1) % part.sides;
|
||||
}
|
||||
|
||||
export function flipTo(part: Part, side: number) {
|
||||
part.side = side;
|
||||
}
|
||||
|
||||
export function roll(part: Part, rng: RNG) {
|
||||
part.side = rng.nextInt(part.sides);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import {Entity, EntityAccessor} from "../utils/entity";
|
||||
import {Part} from "./part";
|
||||
import {RNG} from "../utils/rng";
|
||||
|
||||
export type Region = Entity & {
|
||||
// aligning axes of the region
|
||||
axes: RegionAxis[];
|
||||
|
||||
// current children; expect no overlapped positions
|
||||
children: EntityAccessor<Part>[];
|
||||
}
|
||||
|
||||
export type RegionAxis = {
|
||||
name: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
align?: 'start' | 'end' | 'center';
|
||||
}
|
||||
|
||||
/**
|
||||
* for each axis, try to remove gaps in positions.
|
||||
* - if min exists and align is start, and there are parts at (for example) min+2 and min+4, then move them to min and min+1
|
||||
* - if max exists and align is end, and there are parts at (for example) max-2 and max-4, then move them to max-1 and max-3
|
||||
* - for center, move parts to the center, possibly creating parts placed at 0.5 positions
|
||||
* - sort children so that they're in ascending order on each axes.
|
||||
* @param region
|
||||
*/
|
||||
export function applyAlign(region: Region){
|
||||
for (const axis of region.axes) {
|
||||
// TODO implement this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shuffle on each axis. for each axis, try to swap position.
|
||||
* @param region
|
||||
* @param rng
|
||||
*/
|
||||
export function shuffle(region: Region, rng: RNG){
|
||||
// TODO implement this
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import {Context} from "./context";
|
||||
import {Command} from "../utils/command";
|
||||
import {effect} from "@preact/signals-core";
|
||||
|
||||
export type RuleContext<T> = Context & {
|
||||
actions: Command[];
|
||||
handledActions: number;
|
||||
invocations: RuleContext<unknown>[];
|
||||
resolution?: T;
|
||||
}
|
||||
|
||||
function invokeRuleContext<T>(pushContext: (context: Context) => void, type: string, rule: Generator<string, T, Command>){
|
||||
const ctx: RuleContext<T> = {
|
||||
type,
|
||||
actions: [],
|
||||
handledActions: 0,
|
||||
invocations: [],
|
||||
resolution: undefined,
|
||||
}
|
||||
const dispose = effect(() => {
|
||||
if(ctx.resolution) {
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
pushContext(rule);
|
||||
}
|
||||
|
||||
function* rule(){
|
||||
const play: Command = yield 'play';
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export type Command = {
|
||||
name: string;
|
||||
flags: Record<string, true>;
|
||||
options: Record<string, string>;
|
||||
params: string[];
|
||||
}
|
||||
|
||||
// TODO implement this
|
||||
export function parseCommand(input: string): Command {
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import {Signal, signal} from "@preact/signals-core";
|
||||
|
||||
export type Entity = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type EntityAccessor<T extends Entity> = {
|
||||
id: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export function createEntityCollection<T extends Entity>() {
|
||||
const collection = signal({} as Record<string, Signal<T>>);
|
||||
const remove = (...ids: string[]) => {
|
||||
collection.value = Object.fromEntries(
|
||||
Object.entries(collection.value).filter(([id]) => !ids.includes(id)),
|
||||
);
|
||||
};
|
||||
|
||||
const add = (...entities: T[]) => {
|
||||
collection.value = {
|
||||
...collection.value,
|
||||
...Object.fromEntries(entities.map((entity) => [entity.id, signal(entity)])),
|
||||
};
|
||||
};
|
||||
|
||||
const get = (id: string) => {
|
||||
return {
|
||||
id,
|
||||
get value(){
|
||||
return collection.value[id]?.value;
|
||||
},
|
||||
set value(value: T){
|
||||
const signal = collection.value[id];
|
||||
if(signal)signal.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
collection,
|
||||
remove,
|
||||
add,
|
||||
get
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export interface RNG {
|
||||
/** 设置随机数种子 */
|
||||
(seed: number): void;
|
||||
|
||||
/** 获取一个[0,1)随机数 */
|
||||
next(max?: number): number;
|
||||
|
||||
/** 获取一个[0,max)随机整数 */
|
||||
nextInt(max: number): number;
|
||||
}
|
||||
|
||||
// TODO: create a RNG implementation with the alea library
|
||||
Loading…
Reference in New Issue