Designing a Generic Slot‑Card Assembly Model for Experimental Simulations
The article discusses three approaches to binding experimental components, critiques their limitations, and proposes a reusable slot‑card model implemented in TypeScript, complete with base classes, Card, Slot, and Engine code to manage assembly and updates.
In experimental development, many scenarios require binding two pieces of equipment (or parts) together and later unbinding them, such as wires to terminals, plugs to sockets, lenses to holders, or bottle caps to bottles.
Solution 1 keeps a separate data set for each pair of items, e.g., an array on a terminal to store bound wire ends and a variable on a wire end to store its terminal. This works for a single case but does not scale to other disciplines.
Solution 2 uses physics‑engine joints to create the binding. While convenient when a physics engine is already in use, it adds unnecessary overhead when physics is not needed.
Solution 3 introduces a generic slot‑card model:
Slot (槽): a slot can hold one or more cards.
Card (卡): a card can be inserted into only one slot.
Both slot and card have matching type masks to ensure compatibility.
When a card is placed in a slot, it follows the slot’s movement.
The following TypeScript code implements the model.
export class AssembleBase {
public assembleData: IAssembleAble;
public disabled: boolean = false;
public group: number = -1;
protected engine: AssembleEngine;
constructor(engine: AssembleEngine, assembleData: IAssembleAble) {
this.assembleData = assembleData;
this.engine = engine;
}
public destroy(): void {
this.assembleData = null;
this.engine = null;
}
public canAdd(assemble: AssembleBase): boolean {
return true;
}
}
export interface IAssembleAble {} export class Card extends AssembleBase {
public slot: Slot;
public userData: ICardUserData = {};
constructor(engine: AssembleEngine, assembleData: IAssembleAble, group: number = -1) {
super(engine, assembleData);
this.group = group;
this.engine.addCard(this);
}
public destroy(): void {
this.free();
this.slot = null;
this.engine.removeCard(this);
this.userData = null;
super.destroy();
}
public isFree(): boolean {
return !this.slot;
}
public free(): void {
if (this.slot) {
this.slot.removeCard(this);
}
}
public canAdd(slot: Slot): boolean {
return !this.disabled && this.isFree();
}
}
export interface ICardUserData {} export class Slot extends AssembleBase {
public userData: ISlotUserData = {};
protected cards: Card[] = [];
protected maxCards: number = 1;
constructor(engine: AssembleEngine, assembleData: IAssembleAble, group: number = -1, maxCards: number = 1) {
super(engine, assembleData);
this.group = group;
this.maxCards = maxCards;
this.engine.addSlot(this);
}
public destroy(): void {
this.engine.removeSlot(this);
this.cards.forEach((card: Card) => {
card.slot = null;
});
this.cards = null;
this.userData = null;
super.destroy();
}
public isEmpty(): boolean {
return this.cards.length === 0;
}
public isFull(): boolean {
return this.cards.length >= this.maxCards;
}
public canAdd(card: Card): boolean {
return !this.disabled && !this.hasCard(card) && !this.isFull() && (this.group & card.group) !== 0;
}
public hasCard(card: Card): boolean {
return card.slot === this;
}
public addCard(card: Card): void {
if (card.slot) {
card.slot.removeCard(card);
}
this.cards.push(card);
card.slot = this;
}
public removeCard(card: Card): void {
const ind: number = this.cards.indexOf(card);
if (ind !== -1) {
this.cards.splice(ind, 1);
card.slot = null;
}
}
}
export interface ISlotUserData {} export class AssembleEngine {
protected cardArr: Card[] = [];
protected slotArr: Slot[] = [];
constructor() {}
public destroy(): void {
this.cardArr = null;
this.slotArr = null;
}
public addCard(card: Card): void {
ArrayUtil.add(this.cardArr, card);
}
public removeCard(card: Card): void {
ArrayUtil.remove(this.cardArr, card);
}
public addSlot(slot: Slot): void {
ArrayUtil.add(this.slotArr, slot);
}
public removeSlot(slot: Slot): void {
ArrayUtil.remove(this.slotArr, slot);
}
public update(dt: number): void {
this.cardArr.forEach((card: Card) => {
// card follows its slot
});
}
}The overall structure consists of Card, Slot, and Engine classes; adding collision detection and card‑following logic would introduce a Calculater component. The design mirrors common patterns in physics engines (Shape, Body, World) and graph‑based electric engines (Vertex, Edge, Graph), illustrating that programs are essentially data structures plus algorithms.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
New Oriental Technology
Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
