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