diff --git a/src/samples/slay-the-spire-like/grid-inventory/index.ts b/src/samples/slay-the-spire-like/grid-inventory/index.ts index 936bd7f..d926b78 100644 --- a/src/samples/slay-the-spire-like/grid-inventory/index.ts +++ b/src/samples/slay-the-spire-like/grid-inventory/index.ts @@ -1,4 +1,4 @@ -export type { CellCoordinate, GridInventory, InventoryItem, PlacementResult } from './types'; +export type { CellCoordinate, CellKey, GridInventory, InventoryItem, MutationResult, PlacementResult } from './types'; export { createGridInventory, flipItem, diff --git a/src/samples/slay-the-spire-like/grid-inventory/transform.ts b/src/samples/slay-the-spire-like/grid-inventory/transform.ts index 624c894..d29094f 100644 --- a/src/samples/slay-the-spire-like/grid-inventory/transform.ts +++ b/src/samples/slay-the-spire-like/grid-inventory/transform.ts @@ -8,36 +8,36 @@ import { rotateTransform, transformShape, } from '../utils/shape-collision'; -import type { GridInventory, InventoryItem, PlacementResult } from './types'; +import type { CellKey, GridInventory, InventoryItem, MutationResult, PlacementResult } from './types'; /** * Creates a new empty grid inventory. * Note: When used inside `.produce()`, call this before returning the draft. */ -export function createGridInventory(width: number, height: number): GridInventory { +export function createGridInventory>(width: number, height: number): GridInventory { return { width, height, - items: new Map(), - occupiedCells: new Set(), + items: new Map>(), + occupiedCells: new Set(), }; } /** * Builds a Set of occupied cell keys from a shape and transform. */ -function getShapeCellKeys(shape: ParsedShape, transform: Transform2D): Set { +function getShapeCellKeys(shape: ParsedShape, transform: Transform2D): Set { const cells = transformShape(shape, transform); - return new Set(cells.map(c => `${c.x},${c.y}`)); + return new Set(cells.map(c => `${c.x},${c.y}` as CellKey)); } /** * Validates whether an item can be placed at the given transform. * Checks bounds and collision with all other items. */ -export function validatePlacement( - inventory: GridInventory, - shape: InventoryItem['shape'], +export function validatePlacement>( + inventory: GridInventory, + shape: InventoryItem['shape'], transform: Transform2D ): PlacementResult { if (!checkBounds(shape, transform, inventory.width, inventory.height)) { @@ -56,7 +56,7 @@ export function validatePlacement( * **Mutates directly** — call inside a `.produce()` callback. * Does not validate; call `validatePlacement` first. */ -export function placeItem(inventory: GridInventory, item: InventoryItem): void { +export function placeItem>(inventory: GridInventory, item: InventoryItem): void { const cells = getShapeCellKeys(item.shape, item.transform); for (const cellKey of cells) { inventory.occupiedCells.add(cellKey); @@ -68,7 +68,7 @@ export function placeItem(inventory: GridInventory, item: InventoryItem): void { * Removes an item from the grid by its ID. * **Mutates directly** — call inside a `.produce()` callback. */ -export function removeItem(inventory: GridInventory, itemId: string): void { +export function removeItem>(inventory: GridInventory, itemId: string): void { const item = inventory.items.get(itemId); if (!item) return; @@ -84,11 +84,11 @@ export function removeItem(inventory: GridInventory, itemId: string): void { * **Mutates directly** — call inside a `.produce()` callback. * Validates before applying; returns result indicating success. */ -export function moveItem( - inventory: GridInventory, +export function moveItem>( + inventory: GridInventory, itemId: string, newTransform: Transform2D -): { success: true } | { success: false; reason: string } { +): MutationResult { const item = inventory.items.get(itemId); if (!item) { return { success: false, reason: '物品不存在' }; @@ -127,11 +127,11 @@ export function moveItem( * **Mutates directly** — call inside a `.produce()` callback. * Validates before applying; returns result indicating success. */ -export function rotateItem( - inventory: GridInventory, +export function rotateItem>( + inventory: GridInventory, itemId: string, degrees: number -): { success: true } | { success: false; reason: string } { +): MutationResult { const item = inventory.items.get(itemId); if (!item) { return { success: false, reason: '物品不存在' }; @@ -146,11 +146,11 @@ export function rotateItem( * **Mutates directly** — call inside a `.produce()` callback. * Validates before applying; returns result indicating success. */ -export function flipItem( - inventory: GridInventory, +export function flipItem>( + inventory: GridInventory, itemId: string, axis: 'x' | 'y' -): { success: true } | { success: false; reason: string } { +): MutationResult { const item = inventory.items.get(itemId); if (!item) { return { success: false, reason: '物品不存在' }; @@ -166,19 +166,19 @@ export function flipItem( /** * Returns a copy of the occupied cells set. */ -export function getOccupiedCellSet(inventory: GridInventory): Set { +export function getOccupiedCellSet>(inventory: GridInventory): Set { return new Set(inventory.occupiedCells); } /** * Finds the item occupying the given cell, if any. */ -export function getItemAtCell( - inventory: GridInventory, +export function getItemAtCell>( + inventory: GridInventory, x: number, y: number -): InventoryItem | undefined { - const cellKey = `${x},${y}`; +): InventoryItem | undefined { + const cellKey = `${x},${y}` as CellKey; if (!inventory.occupiedCells.has(cellKey)) { return undefined; } @@ -197,30 +197,31 @@ export function getItemAtCell( * Gets all items adjacent to the given item (orthogonally, not diagonally). * Returns a Map of itemId -> item for deduplication. */ -export function getAdjacentItems( - inventory: GridInventory, +export function getAdjacentItems = Record>( + inventory: GridInventory, itemId: string -): Map { +): Map> { const item = inventory.items.get(itemId); if (!item) { return new Map(); } const ownCells = getShapeCellKeys(item.shape, item.transform); - const adjacent = new Map(); + const adjacent = new Map>(); for (const cellKey of ownCells) { const [cx, cy] = cellKey.split(',').map(Number); - const neighbors = [ - `${cx + 1},${cy}`, - `${cx - 1},${cy}`, - `${cx},${cy + 1}`, - `${cx},${cy - 1}`, + const neighbors: CellKey[] = [ + `${cx + 1},${cy}` as CellKey, + `${cx - 1},${cy}` as CellKey, + `${cx},${cy + 1}` as CellKey, + `${cx},${cy - 1}` as CellKey, ]; for (const neighborKey of neighbors) { if (inventory.occupiedCells.has(neighborKey) && !ownCells.has(neighborKey)) { - const neighborItem = getItemAtCell(inventory, ...neighborKey.split(',').map(Number) as [number, number]); + const [nx, ny] = neighborKey.split(',').map(Number); + const neighborItem = getItemAtCell(inventory, nx, ny); if (neighborItem) { adjacent.set(neighborItem.id, neighborItem); } diff --git a/src/samples/slay-the-spire-like/grid-inventory/types.ts b/src/samples/slay-the-spire-like/grid-inventory/types.ts index ff0866a..f022b9e 100644 --- a/src/samples/slay-the-spire-like/grid-inventory/types.ts +++ b/src/samples/slay-the-spire-like/grid-inventory/types.ts @@ -1,6 +1,11 @@ import type { ParsedShape } from '../utils/parse-shape'; import type { Transform2D } from '../utils/shape-collision'; +/** + * String key representing a grid cell in "x,y" format. + */ +export type CellKey = `${number},${number}`; + /** * Simple 2D coordinate for grid cells. */ @@ -11,8 +16,9 @@ export interface CellCoordinate { /** * An item placed on the grid inventory. + * @template TMeta - Optional metadata type for game-specific data */ -export interface InventoryItem { +export interface InventoryItem> { /** Unique item identifier */ id: string; /** Reference to the item's shape definition */ @@ -20,7 +26,7 @@ export interface InventoryItem { /** Current transformation (position, rotation, flips) */ transform: Transform2D; /** Optional metadata for game-specific data */ - meta?: Record; + meta?: TMeta; } /** @@ -28,17 +34,23 @@ export interface InventoryItem { */ export type PlacementResult = { valid: true } | { valid: false; reason: string }; +/** + * Result of a mutation operation (move, rotate, flip). + */ +export type MutationResult = { success: true } | { success: false; reason: string }; + /** * Grid inventory state. * Designed to be mutated directly inside a `mutative .produce()` callback. + * @template TMeta - Optional metadata type for items */ -export interface GridInventory { +export interface GridInventory> { /** Board width in cells */ width: number; /** Board height in cells */ height: number; /** Map of itemId -> InventoryItem for all placed items */ - items: Map; + items: Map>; /** Set of occupied cells in "x,y" format for O(1) collision lookups */ - occupiedCells: Set; + occupiedCells: Set; }