feat: sts-like-viewer
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
import Phaser from 'phaser';
|
||||
import {
|
||||
createGridInventory,
|
||||
placeItem,
|
||||
getAdjacentItems,
|
||||
parseShapeString,
|
||||
heroItemFighter1Data,
|
||||
type GridInventory,
|
||||
type InventoryItem,
|
||||
} from 'boardgame-core/samples/slay-the-spire-like';
|
||||
import { ReactiveScene } from 'boardgame-phaser';
|
||||
|
||||
export class GridViewerScene extends ReactiveScene {
|
||||
private inventory: GridInventory;
|
||||
private readonly CELL_SIZE = 60;
|
||||
private readonly GRID_WIDTH = 6;
|
||||
private readonly GRID_HEIGHT = 4;
|
||||
private gridOffsetX = 0;
|
||||
private gridOffsetY = 0;
|
||||
|
||||
constructor() {
|
||||
super('GridViewerScene');
|
||||
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
|
||||
}
|
||||
|
||||
create(): void {
|
||||
super.create();
|
||||
const { width, height } = this.scale;
|
||||
this.gridOffsetX = (width - this.GRID_WIDTH * this.CELL_SIZE) / 2;
|
||||
this.gridOffsetY = (height - this.GRID_HEIGHT * this.CELL_SIZE) / 2 + 20;
|
||||
|
||||
// Place some sample items
|
||||
this.placeSampleItems();
|
||||
|
||||
// Draw grid
|
||||
this.drawGrid();
|
||||
|
||||
// Draw items
|
||||
this.drawItems();
|
||||
|
||||
// Title
|
||||
this.add.text(width / 2, 30, 'Grid Inventory Viewer (4x6)', {
|
||||
fontSize: '24px',
|
||||
color: '#ffffff',
|
||||
fontStyle: 'bold',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Controls
|
||||
this.createControls();
|
||||
|
||||
// Info text
|
||||
this.add.text(width / 2, height - 40, 'Hover over cells to see item details', {
|
||||
fontSize: '14px',
|
||||
color: '#aaaaaa',
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
private placeSampleItems(): void {
|
||||
// Place a few items from heroItemFighter1Data
|
||||
const sampleItems = [
|
||||
{ index: 0, x: 0, y: 0 }, // 剑
|
||||
{ index: 3, x: 3, y: 0 }, // 短刀
|
||||
{ index: 6, x: 0, y: 2 }, // 盾
|
||||
{ index: 12, x: 3, y: 2 }, // 披风
|
||||
];
|
||||
|
||||
for (const { index, x, y } of sampleItems) {
|
||||
const data = heroItemFighter1Data[index];
|
||||
const shape = parseShapeString(data.shape);
|
||||
const item: InventoryItem = {
|
||||
id: `item-${index}`,
|
||||
shape,
|
||||
transform: { offset: { x, y }, rotation: 0, flipX: false, flipY: false },
|
||||
meta: { name: data.name, data },
|
||||
};
|
||||
placeItem(this.inventory, item);
|
||||
}
|
||||
}
|
||||
|
||||
private drawGrid(): void {
|
||||
const graphics = this.add.graphics();
|
||||
|
||||
// Draw cells
|
||||
for (let y = 0; y < this.GRID_HEIGHT; y++) {
|
||||
for (let x = 0; x < this.GRID_WIDTH; x++) {
|
||||
const px = this.gridOffsetX + x * this.CELL_SIZE;
|
||||
const py = this.gridOffsetY + y * this.CELL_SIZE;
|
||||
|
||||
const isOccupied = this.inventory.occupiedCells.has(`${x},${y}`);
|
||||
const color = isOccupied ? 0x334455 : 0x222233;
|
||||
|
||||
graphics.fillStyle(color);
|
||||
graphics.fillRect(px + 1, py + 1, this.CELL_SIZE - 2, this.CELL_SIZE - 2);
|
||||
graphics.lineStyle(1, 0x555577);
|
||||
graphics.strokeRect(px, py, this.CELL_SIZE, this.CELL_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private drawItems(): void {
|
||||
for (const [itemId, item] of this.inventory.items) {
|
||||
const shape = item.shape;
|
||||
const transform = item.transform;
|
||||
|
||||
// Get occupied cells
|
||||
const cells: { x: number; y: number }[] = [];
|
||||
for (let y = 0; y < shape.height; y++) {
|
||||
for (let x = 0; x < shape.width; x++) {
|
||||
if (shape.grid[y]?.[x]) {
|
||||
const finalX = x + transform.offset.x;
|
||||
const finalY = y + transform.offset.y;
|
||||
cells.push({ x: finalX, y: finalY });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw item cells with a unique color per item
|
||||
const itemColor = this.getItemColor(itemId);
|
||||
const graphics = this.add.graphics();
|
||||
|
||||
for (const cell of cells) {
|
||||
const px = this.gridOffsetX + cell.x * this.CELL_SIZE;
|
||||
const py = this.gridOffsetY + cell.y * this.CELL_SIZE;
|
||||
|
||||
graphics.fillStyle(itemColor);
|
||||
graphics.fillRect(px + 2, py + 2, this.CELL_SIZE - 4, this.CELL_SIZE - 4);
|
||||
}
|
||||
|
||||
// Item name label (at first cell)
|
||||
if (cells.length > 0) {
|
||||
const firstCell = cells[0];
|
||||
const px = this.gridOffsetX + firstCell.x * this.CELL_SIZE;
|
||||
const py = this.gridOffsetY + firstCell.y * this.CELL_SIZE;
|
||||
const itemName = (item.meta?.name as string) ?? item.id;
|
||||
|
||||
this.add.text(px + this.CELL_SIZE / 2, py + this.CELL_SIZE / 2, itemName, {
|
||||
fontSize: '11px',
|
||||
color: '#ffffff',
|
||||
fontStyle: 'bold',
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
// Adjacent items info
|
||||
const adjacent = getAdjacentItems(this.inventory, itemId);
|
||||
if (adjacent.size > 0) {
|
||||
const adjacentNames = Array.from(adjacent.values()).map(i => (i.meta?.name as string) ?? i.id).join(', ');
|
||||
const firstCell = cells[0];
|
||||
const px = this.gridOffsetX + firstCell.x * this.CELL_SIZE;
|
||||
const py = this.gridOffsetY + firstCell.y * this.CELL_SIZE - 20;
|
||||
|
||||
this.add.text(px + this.CELL_SIZE / 2, py, `邻接: ${adjacentNames}`, {
|
||||
fontSize: '10px',
|
||||
color: '#ffff88',
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getItemColor(itemId: string): number {
|
||||
// Generate a consistent color based on item ID
|
||||
const hash = itemId.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
||||
const colors = [0x4488cc, 0xcc8844, 0x44cc88, 0xcc4488, 0x8844cc, 0x44cccc];
|
||||
return colors[hash % colors.length];
|
||||
}
|
||||
|
||||
private createControls(): void {
|
||||
const { width, height } = this.scale;
|
||||
|
||||
// Back button
|
||||
this.createButton('返回菜单', 100, 40, () => {
|
||||
this.scene.start('IndexScene');
|
||||
});
|
||||
|
||||
// Clear button
|
||||
this.createButton('清空', width - 260, 40, () => {
|
||||
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
|
||||
this.scene.restart();
|
||||
});
|
||||
|
||||
// Random fill button
|
||||
this.createButton('随机填充', width - 130, 40, () => {
|
||||
this.randomFill();
|
||||
this.scene.restart();
|
||||
});
|
||||
}
|
||||
|
||||
private randomFill(): void {
|
||||
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
|
||||
|
||||
// Try to place random items
|
||||
let itemIndex = 0;
|
||||
for (let y = 0; y < this.GRID_HEIGHT && itemIndex < heroItemFighter1Data.length; y++) {
|
||||
for (let x = 0; x < this.GRID_WIDTH && itemIndex < heroItemFighter1Data.length; x++) {
|
||||
const data = heroItemFighter1Data[itemIndex];
|
||||
const shape = parseShapeString(data.shape);
|
||||
|
||||
// Check if placement is valid
|
||||
const occupiedCells = new Set<string>();
|
||||
for (let sy = 0; sy < shape.height; sy++) {
|
||||
for (let sx = 0; sx < shape.width; sx++) {
|
||||
if (shape.grid[sy]?.[sx]) {
|
||||
occupiedCells.add(`${sx + x},${sy + y}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple check if any cell is out of bounds or occupied
|
||||
let valid = true;
|
||||
for (const cell of occupiedCells) {
|
||||
const [cx, cy] = cell.split(',').map(Number);
|
||||
if (cx >= this.GRID_WIDTH || cy >= this.GRID_HEIGHT || this.inventory.occupiedCells.has(cell)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
const item: InventoryItem = {
|
||||
id: `item-${itemIndex}`,
|
||||
shape,
|
||||
transform: { offset: { x, y }, rotation: 0, flipX: false, flipY: false },
|
||||
meta: { name: data.name, data },
|
||||
};
|
||||
placeItem(this.inventory, item);
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createButton(label: string, x: number, y: number, onClick: () => void): void {
|
||||
const buttonWidth = 120;
|
||||
const buttonHeight = 36;
|
||||
|
||||
const bg = this.add.rectangle(x, y, buttonWidth, buttonHeight, 0x444466)
|
||||
.setStrokeStyle(2, 0x7777aa)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
|
||||
const text = this.add.text(x, y, label, {
|
||||
fontSize: '16px',
|
||||
color: '#ffffff',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
bg.on('pointerover', () => {
|
||||
bg.setFillStyle(0x555588);
|
||||
text.setScale(1.05);
|
||||
});
|
||||
|
||||
bg.on('pointerout', () => {
|
||||
bg.setFillStyle(0x444466);
|
||||
text.setScale(1);
|
||||
});
|
||||
|
||||
bg.on('pointerdown', onClick);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user