Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 60 additions & 36 deletions app/lib/icemake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type ComponentGraphNode = {

export type ExecutionStep = {
componentIndex: number;
stack: Flavor[];
stack: Flavor[] | null; // コーンが食べられたらnull
branchTaken?: boolean;
};

Expand All @@ -17,6 +17,7 @@ export type IcemakeResult = {
traces: { color: ConeColor; steps: ExecutionStep[] }[];
};

// 1つのコーンに積めるアイスの最大数(UI上の想定に合わせた安全上限)
const MAX_ICE_STACK_SIZE = 10;

export function icemake(
Expand All @@ -28,6 +29,7 @@ export function icemake(
const stageData = STAGES[stage];
if (!stageData) return { result: {}, traces: [] };

// グラフが渡されたときは、各色ごとに実行トレースを取りながら結果を計算する
if (graph && firstComponentId !== undefined) {
const traces = colors.map((color) => {
const steps = runGraphExecution(
Expand All @@ -38,16 +40,18 @@ export function icemake(
);
return { color, steps };
});
// 最終ステップのstackをその色の完成結果として採用する
const result = Object.fromEntries(
traces.map(({ color, steps }) => [
color,
steps.length > 0 ? steps[steps.length - 1].stack : [],
steps.length > 0 ? (steps[steps.length - 1].stack ?? []) : [],
]),
);
return { result, traces };
}

return {
// グラフ未実行時はステージ定義のミッションを既定値として返す
result: Object.fromEntries(
colors.map((color) => [color, stageData.mission[color] ?? []]),
),
Expand All @@ -61,8 +65,10 @@ function runGraphExecution(
graph: Record<number, ComponentGraphNode>,
firstComponentId: number,
): ExecutionStep[] {
const stack: Flavor[] = [];
// コーンが食べられたらnull
let stack: Flavor[] | null = [];
const steps: ExecutionStep[] = [];
// 同じノード + 同じstack状態に再訪したら無限ループとみなして停止する
const visited: Map<number, Set<string>> = new Map();
let currentId: number | null = firstComponentId;

Expand All @@ -80,58 +86,76 @@ function runGraphExecution(
if (!componentVisited || componentVisited.has(stackKey)) break;
componentVisited.add(stackKey);

if (!stack) break;

switch (component.type) {
case "push":
if (stack.length < MAX_ICE_STACK_SIZE) stack.push(component.flavor);
break;
case "pop":
if (
stack.length > 0 &&
(!component.flavor || stack[stack.length - 1] === component.flavor)
if (stack.length === 0) stack = null;
else if (
!component.flavor ||
stack[stack.length - 1] === component.flavor
)
stack.pop();
break;
case "if":
break;
}

const children: ComponentGraphNode["childrenIds"] = node.childrenIds;

let branchTaken: boolean | undefined;
if (children != null && typeof children !== "number") {
let condition = false;
if (component.type === "if") {
const cond: ConeColor | Flavor | Flavor[] | number =
component.condition;
if (typeof cond === "string") {
if (coneColors.includes(cond as ConeColor))
condition = color === cond;
else if (flavors.includes(cond as Flavor))
condition = stack.length > 0 && stack[stack.length - 1] === cond;
} else if (Array.isArray(cond)) {
for (let i = 0; i <= stack.length - cond.length; i++) {
if (
stack.slice(i, i + cond.length).every((f, j) => f === cond[j])
) {
condition = true;
break;
if (!stack) {
steps.push({
componentIndex: currentId,
stack: null,
branchTaken: undefined,
});
} else {
const children: ComponentGraphNode["childrenIds"] = node.childrenIds;

let branchTaken: boolean | undefined;
if (children != null && typeof children !== "number") {
let condition = false;
// ifノードの条件を、色 / 先頭フレーバー / 部分配列一致 / 個数で評価する
if (component.type === "if") {
const cond: ConeColor | Flavor | Flavor[] | number =
component.condition;
if (typeof cond === "string") {
if (coneColors.includes(cond as ConeColor))
condition = color === cond;
else if (flavors.includes(cond as Flavor))
condition = stack.length > 0 && stack[stack.length - 1] === cond;
} else if (Array.isArray(cond)) {
for (let i = 0; i <= stack.length - cond.length; i++) {
if (
stack.slice(i, i + cond.length).every((f, j) => f === cond[j])
) {
condition = true;
break;
}
}
} else if (typeof cond === "number") {
condition = stack.length >= cond;
}
} else if (typeof cond === "number") {
condition = stack.length >= cond;
}
branchTaken = condition;
}
branchTaken = condition;
}

steps.push({ componentIndex: currentId, stack: [...stack], branchTaken });
steps.push({
componentIndex: currentId,
stack: stack === null ? null : [...stack],
branchTaken,
});

if (children == null) break;
if (children == null) break;

if (typeof children === "number") {
currentId = children;
} else {
currentId = branchTaken ? children.true : children.false;
if (typeof children === "number") {
// 直線接続
currentId = children;
} else {
// 分岐接続(true/false)
currentId = branchTaken ? children.true : children.false;
}
}
}

Expand Down
Loading