This commit is contained in:
gpt-engineer-app[bot] 2026-01-20 08:27:03 +00:00
parent db6bd5e7e8
commit 8c88ce26c0

View file

@ -5,14 +5,12 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
import { Shield, Target, RotateCcw, Info } from "lucide-react"; import { Shield, Target, RotateCcw, Info } from "lucide-react";
// Extended good rectangle: 10 columns × 12 rows (following Fig. 10) // Extended good rectangle: 10 columns × 12 rows (following Fig. 10)
// The border consists of: // Border: columns 0 and 9, rows 0/1 (top) and 10/11 (bottom)
// - Columns 0 and 9 (left and right edges)
// - Rows 0, 1 (top two rows) and rows 10, 11 (bottom two rows)
// This forms the double-layer border cycle C
const COLS = 10; const COLS = 10;
const ROWS = 12; const ROWS = 12;
type Position = { x: number; y: number }; type Position = { x: number; y: number };
type Movement = { from: Position; to: Position; type: 'interior' | 'toInterior' | 'toBorder' | 'pathShift' };
const posKey = (p: Position): string => `${p.x},${p.y}`; const posKey = (p: Position): string => `${p.x},${p.y}`;
const parseKey = (key: string): Position => { const parseKey = (key: string): Position => {
@ -20,34 +18,25 @@ const parseKey = (key: string): Position => {
return { x, y }; return { x, y };
}; };
const isValidPos = (x: number, y: number): boolean => { const isValidPos = (x: number, y: number): boolean =>
return x >= 0 && x < COLS && y >= 0 && y < ROWS; x >= 0 && x < COLS && y >= 0 && y < ROWS;
};
// Hexagonal grid adjacency (brick/offset pattern as in Fig. 10) // Hexagonal adjacency (brick pattern)
// Even rows: neighbors at relative positions
// Odd rows: offset by 0.5 to the right visually
const getHexNeighbors = (x: number, y: number): Position[] => { const getHexNeighbors = (x: number, y: number): Position[] => {
const neighbors: Position[] = []; const neighbors: Position[] = [];
// Left and right (same for all rows)
if (isValidPos(x - 1, y)) neighbors.push({ x: x - 1, y }); if (isValidPos(x - 1, y)) neighbors.push({ x: x - 1, y });
if (isValidPos(x + 1, y)) neighbors.push({ x: x + 1, y }); if (isValidPos(x + 1, y)) neighbors.push({ x: x + 1, y });
const isEvenRow = y % 2 === 0; const isEvenRow = y % 2 === 0;
if (isEvenRow) { if (isEvenRow) {
// Even row: upper neighbors at x-1 and x
if (isValidPos(x - 1, y - 1)) neighbors.push({ x: x - 1, y: y - 1 }); if (isValidPos(x - 1, y - 1)) neighbors.push({ x: x - 1, y: y - 1 });
if (isValidPos(x, y - 1)) neighbors.push({ x: x, y: y - 1 }); if (isValidPos(x, y - 1)) neighbors.push({ x: x, y: y - 1 });
// Lower neighbors at x-1 and x
if (isValidPos(x - 1, y + 1)) neighbors.push({ x: x - 1, y: y + 1 }); if (isValidPos(x - 1, y + 1)) neighbors.push({ x: x - 1, y: y + 1 });
if (isValidPos(x, y + 1)) neighbors.push({ x: x, y: y + 1 }); if (isValidPos(x, y + 1)) neighbors.push({ x: x, y: y + 1 });
} else { } else {
// Odd row: upper neighbors at x and x+1
if (isValidPos(x, y - 1)) neighbors.push({ x: x, y: y - 1 }); if (isValidPos(x, y - 1)) neighbors.push({ x: x, y: y - 1 });
if (isValidPos(x + 1, y - 1)) neighbors.push({ x: x + 1, y: y - 1 }); if (isValidPos(x + 1, y - 1)) neighbors.push({ x: x + 1, y: y - 1 });
// Lower neighbors at x and x+1
if (isValidPos(x, y + 1)) neighbors.push({ x: x, y: y + 1 }); if (isValidPos(x, y + 1)) neighbors.push({ x: x, y: y + 1 });
if (isValidPos(x + 1, y + 1)) neighbors.push({ x: x + 1, y: y + 1 }); if (isValidPos(x + 1, y + 1)) neighbors.push({ x: x + 1, y: y + 1 });
} }
@ -55,51 +44,22 @@ const getHexNeighbors = (x: number, y: number): Position[] => {
return neighbors; return neighbors;
}; };
// Border definition from Fig. 10: // Border: left/right columns and top/bottom double rows
// The border of the extended good rectangle consists of:
// - x = 0 or x = COLS-1 (left/right columns)
// - y = 0 or y = 1 (top two rows)
// - y = ROWS-2 or y = ROWS-1 (bottom two rows)
const isBorder = (x: number, y: number): boolean => { const isBorder = (x: number, y: number): boolean => {
if (x === 0 || x === COLS - 1) return true; if (x === 0 || x === COLS - 1) return true;
if (y === 0 || y === 1 || y === ROWS - 2 || y === ROWS - 1) return true; if (y === 0 || y === 1 || y === ROWS - 2 || y === ROWS - 1) return true;
return false; return false;
}; };
// Interior: vertices not on the border const isInterior = (x: number, y: number): boolean =>
const isInterior = (x: number, y: number): boolean => { x > 0 && x < COLS - 1 && y > 1 && y < ROWS - 2;
return x > 0 && x < COLS - 1 && y > 1 && y < ROWS - 2;
};
// Get ordered border cycle C (clockwise around the rectangle) // Get the initial guard configuration U₃
// This matches the structure in Fig. 10 where the green paths run along the border // All border vertices guarded + interior guards in dominating pattern
const getBorderCycle = (): Position[] => {
const cycle: Position[] = [];
// Top edge (y=0, left to right)
for (let x = 0; x < COLS; x++) cycle.push({ x, y: 0 });
// Right edge (x=COLS-1, top to bottom, skip first)
for (let y = 1; y < ROWS; y++) cycle.push({ x: COLS - 1, y });
// Bottom edge (y=ROWS-1, right to left, skip first)
for (let x = COLS - 2; x >= 0; x--) cycle.push({ x, y: ROWS - 1 });
// Left edge (x=0, bottom to top, skip first and last)
for (let y = ROWS - 2; y > 0; y--) cycle.push({ x: 0, y });
// Second layer (y=1 and y=ROWS-2)
for (let x = 1; x < COLS - 1; x++) cycle.push({ x, y: 1 });
for (let x = 1; x < COLS - 1; x++) cycle.push({ x, y: ROWS - 2 });
return cycle;
};
// Initial guard placement following U₃ from the paper:
// 1. ALL border vertices are guarded (both layers)
// 2. Interior guards placed according to S₃ pattern (eternal dominating set of T₃)
// From the proof: interior pattern uses density ~7/15
const generateInitialGuards = (): Set<string> => { const generateInitialGuards = (): Set<string> => {
const guards = new Set<string>(); const guards = new Set<string>();
// All border vertices are guarded (forming the protected cycle C) // All border vertices
for (let x = 0; x < COLS; x++) { for (let x = 0; x < COLS; x++) {
for (let y = 0; y < ROWS; y++) { for (let y = 0; y < ROWS; y++) {
if (isBorder(x, y)) { if (isBorder(x, y)) {
@ -108,14 +68,10 @@ const generateInitialGuards = (): Set<string> => {
} }
} }
// Interior guards following the S₃ pattern from Theorem 6 // Interior guards: dominating set pattern from S₃
// The pattern: (0,0) ∈ S, and if (x,y) ∈ S then (x+2,y+2), (x+3,y-1), etc. // Place at positions where (x + 2*y) % 4 === 0
// Simplified: place guards to create a dominating set in the interior
// Using the pattern from Fig. 10 where interior guards are at specific positions
for (let x = 1; x < COLS - 1; x++) { for (let x = 1; x < COLS - 1; x++) {
for (let y = 2; y < ROWS - 2; y++) { for (let y = 2; y < ROWS - 2; y++) {
// Pattern based on the hexagonal tiling domination
// Every vertex needs to be within distance 1 of a guard
if ((x + 2 * y) % 4 === 0) { if ((x + 2 * y) % 4 === 0) {
guards.add(posKey({ x, y })); guards.add(posKey({ x, y }));
} }
@ -125,13 +81,24 @@ const generateInitialGuards = (): Set<string> => {
return guards; return guards;
}; };
// Check if vertex is dominated // Get the target pattern for interior (what we want to restore to)
const getTargetInteriorPattern = (): Set<string> => {
const pattern = new Set<string>();
for (let x = 1; x < COLS - 1; x++) {
for (let y = 2; y < ROWS - 2; y++) {
if ((x + 2 * y) % 4 === 0) {
pattern.add(posKey({ x, y }));
}
}
}
return pattern;
};
const isDominated = (x: number, y: number, guards: Set<string>): boolean => { const isDominated = (x: number, y: number, guards: Set<string>): boolean => {
if (guards.has(posKey({ x, y }))) return true; if (guards.has(posKey({ x, y }))) return true;
return getHexNeighbors(x, y).some(p => guards.has(posKey(p))); return getHexNeighbors(x, y).some(p => guards.has(posKey(p)));
}; };
// Check if all vertices are dominated
const isFullyDominated = (guards: Set<string>): boolean => { const isFullyDominated = (guards: Set<string>): boolean => {
for (let x = 0; x < COLS; x++) { for (let x = 0; x < COLS; x++) {
for (let y = 0; y < ROWS; y++) { for (let y = 0; y < ROWS; y++) {
@ -141,133 +108,203 @@ const isFullyDominated = (guards: Set<string>): boolean => {
return true; return true;
}; };
// Find index in border cycle // Get ordered border cycle C (for path finding)
const findInCycle = (cycle: Position[], pos: Position): number => { const getBorderCycleOrdered = (): Position[] => {
return cycle.findIndex(p => p.x === pos.x && p.y === pos.y); const cycle: Position[] = [];
// Outer ring clockwise
for (let x = 0; x < COLS; x++) cycle.push({ x, y: 0 });
for (let y = 1; y < ROWS; y++) cycle.push({ x: COLS - 1, y });
for (let x = COLS - 2; x >= 0; x--) cycle.push({ x, y: ROWS - 1 });
for (let y = ROWS - 2; y > 0; y--) cycle.push({ x: 0, y });
return cycle;
}; };
// Defense strategy from Fig. 10 and Claim 2: // Find path on border between two positions (along the cycle)
// 1. Consider the attack on v_t in the interior const findBorderPath = (from: Position, to: Position): Position[] => {
// 2. Interior guards shift toward attack (one moves to v_t) const cycle = getBorderCycleOrdered();
// 3. Exactly two guards move from interior to border: u₁→w₁, u₂→w₂ const fromIdx = cycle.findIndex(p => p.x === from.x && p.y === from.y);
// 4. Exactly two guards move from border to interior: w₃→u₃, w₄→u₄ const toIdx = cycle.findIndex(p => p.x === to.x && p.y === to.y);
// 5. Border guards shift along complementary paths P₁,₃ and P₂,₄ to restore coverage
if (fromIdx === -1 || toIdx === -1) return [];
const n = cycle.length;
const path1: Position[] = [];
const path2: Position[] = [];
// Clockwise path
for (let i = fromIdx; ; i = (i + 1) % n) {
path1.push(cycle[i]);
if (i === toIdx) break;
}
// Counter-clockwise path
for (let i = fromIdx; ; i = (i - 1 + n) % n) {
path2.push(cycle[i]);
if (i === toIdx) break;
}
return path1.length <= path2.length ? path1 : path2;
};
// BFS to find shortest path between two positions using hex neighbors
const findShortestPath = (from: Position, to: Position, guards: Set<string>): Position[] => {
const queue: Position[][] = [[from]];
const visited = new Set<string>([posKey(from)]);
while (queue.length > 0) {
const path = queue.shift()!;
const current = path[path.length - 1];
if (current.x === to.x && current.y === to.y) {
return path;
}
for (const neighbor of getHexNeighbors(current.x, current.y)) {
const key = posKey(neighbor);
if (!visited.has(key)) {
visited.add(key);
queue.push([...path, neighbor]);
}
}
}
return [];
};
// Defense strategy following Figure 10 exactly:
// 1. Interior guards shift in chains toward the attack (v_t)
// 2. Two interior guards exit to border (u₁→w₁, u₂→w₂)
// 3. Two border guards enter interior (w₃→u₃, w₄→u₄)
// 4. Border guards shift along complementary paths P₁,₃ and P₂,₄
const defendAttack = (guards: Set<string>, attack: Position): { const defendAttack = (guards: Set<string>, attack: Position): {
newGuards: Set<string>; newGuards: Set<string>;
movements: Array<{ from: Position; to: Position; type: 'interior' | 'border' | 'path' }> movements: Movement[];
} | null => { } | null => {
const attackKey = posKey(attack); const attackKey = posKey(attack);
// Already guarded - no defense needed
if (guards.has(attackKey)) { if (guards.has(attackKey)) {
return { newGuards: guards, movements: [] }; return { newGuards: guards, movements: [] };
} }
// Find adjacent guards
const neighbors = getHexNeighbors(attack.x, attack.y); const neighbors = getHexNeighbors(attack.x, attack.y);
const adjacentGuards = neighbors.filter(p => guards.has(posKey(p))); const adjacentGuards = neighbors.filter(p => guards.has(posKey(p)));
if (adjacentGuards.length === 0) { if (adjacentGuards.length === 0) {
return null; // Cannot defend - no adjacent guard return null;
} }
const newGuards = new Set(guards); const newGuards = new Set(guards);
const movements: Array<{ from: Position; to: Position; type: 'interior' | 'border' | 'path' }> = []; const movements: Movement[] = [];
// Step 1: Find an interior guard to move to the attack (prefer interior, then border) // STEP 1: Find interior guard chains that will shift toward attack
let defender: Position | null = null; // In Figure 10, the arrows show guards shifting in diagonal lines toward v_t
// Prefer interior guards (as in Fig. 10, interior shifts toward attack) // Find an interior guard adjacent to the attack to move there
let primaryDefender: Position | null = null;
for (const g of adjacentGuards) { for (const g of adjacentGuards) {
if (isInterior(g.x, g.y)) { if (isInterior(g.x, g.y)) {
defender = g; primaryDefender = g;
break; break;
} }
} }
// If no interior guard, use a border guard // If attack is on border or no interior guard adjacent, use border guard
if (!defender) { if (!primaryDefender) {
defender = adjacentGuards[0]; primaryDefender = adjacentGuards[0];
newGuards.delete(posKey(primaryDefender));
newGuards.add(attackKey);
movements.push({ from: primaryDefender, to: attack, type: isBorder(primaryDefender.x, primaryDefender.y) ? 'toBorder' : 'interior' });
// If we moved a border guard, shift along border to fill gap
if (isBorder(primaryDefender.x, primaryDefender.y)) {
shiftBorderToFillGap(newGuards, movements, primaryDefender);
} }
// Move defender to attack position return { newGuards, movements };
newGuards.delete(posKey(defender)); }
// STEP 2: Shift interior guards in a chain toward the attack
// This mimics the diagonal arrows in Figure 10
// Move primary defender to attack
newGuards.delete(posKey(primaryDefender));
newGuards.add(attackKey); newGuards.add(attackKey);
movements.push({ from: defender, to: attack, type: 'interior' }); movements.push({ from: primaryDefender, to: attack, type: 'interior' });
// Step 2: If defender was from interior, the interior pattern needs restoration // Now we need to fill the gap left by the primary defender
// Following Fig. 10: shifts may push a guard to border, and border guards fill gaps // Find another interior guard that can shift into that position
let currentGap = primaryDefender;
let chainLength = 0;
const maxChain = 8;
if (isInterior(defender.x, defender.y)) { while (chainLength < maxChain && isInterior(currentGap.x, currentGap.y)) {
// The defender left a gap in the interior domination pattern const gapNeighbors = getHexNeighbors(currentGap.x, currentGap.y);
// Find undominated interior vertices and fill from adjacent border guards let filler: Position | null = null;
// Look for an interior guard to shift into the gap
for (const n of gapNeighbors) {
if (isInterior(n.x, n.y) && newGuards.has(posKey(n))) {
// Prefer guards that are further from the target pattern
filler = n;
break;
}
}
if (filler) {
newGuards.delete(posKey(filler));
newGuards.add(posKey(currentGap));
movements.push({ from: filler, to: currentGap, type: 'interior' });
currentGap = filler;
chainLength++;
} else {
// No interior guard to fill - need border guard (w₃→u₃ movement)
const borderFiller = gapNeighbors.find(n =>
isBorder(n.x, n.y) && newGuards.has(posKey(n))
);
if (borderFiller) {
newGuards.delete(posKey(borderFiller));
newGuards.add(posKey(currentGap));
movements.push({ from: borderFiller, to: currentGap, type: 'toInterior' });
// Now fill the border gap (this creates the need for P₁,₃ or P₂,₄)
shiftBorderToFillGap(newGuards, movements, borderFiller);
}
break;
}
}
// If chain ended at border (interior guard pushed to border = u₁→w₁)
if (isBorder(currentGap.x, currentGap.y) && !newGuards.has(posKey(currentGap))) {
// The gap is on border, needs to be filled by border shift
shiftBorderToFillGap(newGuards, movements, currentGap);
}
// STEP 3: Check if we need a second chain (Figure 10 shows two chains)
// This happens when the attack disrupts multiple domination lines
// Find interior positions that are now undominated and need filling
for (let x = 1; x < COLS - 1; x++) { for (let x = 1; x < COLS - 1; x++) {
for (let y = 2; y < ROWS - 2; y++) { for (let y = 2; y < ROWS - 2; y++) {
if (!isDominated(x, y, newGuards)) { if (!isDominated(x, y, newGuards)) {
// Find an adjacent border guard to move in (w₃→u₃ style) // Need to get a guard here
const nbs = getHexNeighbors(x, y); const nbs = getHexNeighbors(x, y);
const borderGuard = nbs.find(n => isBorder(n.x, n.y) && newGuards.has(posKey(n))); const nearbyGuard = nbs.find(n => newGuards.has(posKey(n)) && isInterior(n.x, n.y));
if (nearbyGuard) {
newGuards.delete(posKey(nearbyGuard));
newGuards.add(posKey({ x, y }));
movements.push({ from: nearbyGuard, to: { x, y }, type: 'interior' });
} else {
// Use border guard
const borderGuard = nbs.find(n => newGuards.has(posKey(n)) && isBorder(n.x, n.y));
if (borderGuard) { if (borderGuard) {
newGuards.delete(posKey(borderGuard)); newGuards.delete(posKey(borderGuard));
newGuards.add(posKey({ x, y })); newGuards.add(posKey({ x, y }));
movements.push({ from: borderGuard, to: { x, y }, type: 'border' }); movements.push({ from: borderGuard, to: { x, y }, type: 'toInterior' });
shiftBorderToFillGap(newGuards, movements, borderGuard);
// Now the border has a gap at borderGuard position
// Shift along the border cycle to fill it (complementary path)
const borderNbs = getHexNeighbors(borderGuard.x, borderGuard.y);
const shifter = borderNbs.find(n =>
isBorder(n.x, n.y) &&
newGuards.has(posKey(n)) &&
!movements.some(m => m.from.x === n.x && m.from.y === n.y)
);
if (shifter) {
newGuards.delete(posKey(shifter));
newGuards.add(posKey(borderGuard));
movements.push({ from: shifter, to: borderGuard, type: 'path' });
}
}
}
}
}
}
// Step 3: Ensure all border positions remain guarded (shift along paths)
// This implements the complementary path shifting from Fig. 10
const borderCycle = getBorderCycle();
let changed = true;
let iterations = 0;
while (changed && iterations < 20) {
changed = false;
iterations++;
for (const pos of borderCycle) {
if (isBorder(pos.x, pos.y) && !newGuards.has(posKey(pos))) {
// Find adjacent border guard to shift
const nbs = getHexNeighbors(pos.x, pos.y);
const shifter = nbs.find(n =>
isBorder(n.x, n.y) &&
newGuards.has(posKey(n)) &&
!movements.some(m => m.to.x === n.x && m.to.y === n.y)
);
if (shifter) {
// Check if shifter can be safely moved (has backup)
const shifterNbs = getHexNeighbors(shifter.x, shifter.y);
const hasBackup = shifterNbs.some(n =>
isBorder(n.x, n.y) &&
newGuards.has(posKey(n)) &&
(n.x !== pos.x || n.y !== pos.y)
);
if (hasBackup) {
newGuards.delete(posKey(shifter));
newGuards.add(posKey(pos));
movements.push({ from: shifter, to: pos, type: 'path' });
changed = true;
} }
} }
} }
@ -277,13 +314,60 @@ const defendAttack = (guards: Set<string>, attack: Position): {
return { newGuards, movements }; return { newGuards, movements };
}; };
// Shift border guards along the cycle to fill a gap (complementary path shifting)
const shiftBorderToFillGap = (
guards: Set<string>,
movements: Movement[],
gap: Position
): void => {
if (guards.has(posKey(gap))) return; // No gap
// Find adjacent border guard to shift into the gap
const gapNeighbors = getHexNeighbors(gap.x, gap.y);
// Find a border guard that can shift in and has a backup
for (const n of gapNeighbors) {
if (isBorder(n.x, n.y) && guards.has(posKey(n))) {
// Check if this guard has another border neighbor that could back it up
const nNeighbors = getHexNeighbors(n.x, n.y);
const hasBackup = nNeighbors.some(nn =>
isBorder(nn.x, nn.y) &&
guards.has(posKey(nn)) &&
(nn.x !== gap.x || nn.y !== gap.y)
);
if (hasBackup) {
guards.delete(posKey(n));
guards.add(posKey(gap));
movements.push({ from: n, to: gap, type: 'pathShift' });
// Recursively fill the new gap
if (!guards.has(posKey(n))) {
shiftBorderToFillGap(guards, movements, n);
}
return;
}
}
}
// If no backup found, just do the shift anyway (maintaining domination)
for (const n of gapNeighbors) {
if (isBorder(n.x, n.y) && guards.has(posKey(n))) {
guards.delete(posKey(n));
guards.add(posKey(gap));
movements.push({ from: n, to: gap, type: 'pathShift' });
return;
}
}
};
const EternalDominationGame: React.FC = () => { const EternalDominationGame: React.FC = () => {
const [guards, setGuards] = useState<Set<string>>(generateInitialGuards); const [guards, setGuards] = useState<Set<string>>(generateInitialGuards);
const [attackHistory, setAttackHistory] = useState<Position[]>([]); const [attackHistory, setAttackHistory] = useState<Position[]>([]);
const [message, setMessage] = useState<string>("Click any unguarded cell to attack."); const [message, setMessage] = useState<string>("Click any unguarded cell to attack.");
const [moveCount, setMoveCount] = useState(0); const [moveCount, setMoveCount] = useState(0);
const [showInfo, setShowInfo] = useState(false); const [showInfo, setShowInfo] = useState(false);
const [lastMovements, setLastMovements] = useState<Array<{ from: Position; to: Position; type: string }>>([]); const [lastMovements, setLastMovements] = useState<Movement[]>([]);
const handleCellClick = useCallback((x: number, y: number) => { const handleCellClick = useCallback((x: number, y: number) => {
const attack: Position = { x, y }; const attack: Position = { x, y };
@ -301,8 +385,16 @@ const EternalDominationGame: React.FC = () => {
setAttackHistory(prev => [...prev, attack]); setAttackHistory(prev => [...prev, attack]);
setMoveCount(prev => prev + 1); setMoveCount(prev => prev + 1);
const interiorMoves = result.movements.filter(m => m.type === 'interior').length;
const borderIn = result.movements.filter(m => m.type === 'toInterior').length;
const borderOut = result.movements.filter(m => m.type === 'toBorder').length;
const pathShifts = result.movements.filter(m => m.type === 'pathShift').length;
if (isFullyDominated(result.newGuards)) { if (isFullyDominated(result.newGuards)) {
setMessage(`Attack defended! ${result.movements.length} guard${result.movements.length !== 1 ? 's' : ''} moved.`); let desc = `Defended! Interior shifts: ${interiorMoves}`;
if (borderIn > 0) desc += `, w→u: ${borderIn}`;
if (pathShifts > 0) desc += `, path shifts: ${pathShifts}`;
setMessage(desc);
} else { } else {
setMessage("Defense incomplete - some vertices undominated!"); setMessage("Defense incomplete - some vertices undominated!");
} }
@ -326,11 +418,21 @@ const EternalDominationGame: React.FC = () => {
}).length; }).length;
const borderGuards = guardCount - interiorGuards; const borderGuards = guardCount - interiorGuards;
// Cell dimensions for rendering
const cellW = 44; const cellW = 44;
const cellH = 40; const cellH = 40;
const offset = cellW / 2; const offset = cellW / 2;
// Color mapping for movement types
const getMovementColor = (type: string) => {
switch (type) {
case 'interior': return '#ef4444'; // Red - interior chain shift
case 'toInterior': return '#06b6d4'; // Cyan - w₃→u₃ (border to interior)
case 'toBorder': return '#f97316'; // Orange - u₁→w₁ (interior to border)
case 'pathShift': return '#22c55e'; // Green - complementary path shift
default: return '#888';
}
};
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@ -340,7 +442,7 @@ const EternalDominationGame: React.FC = () => {
Eternal Domination (Figure 10) Eternal Domination (Figure 10)
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
10×12 extended good rectangle with double-layer border 10×12 grid with double-layer border defense via interior chains and complementary path shifts
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
@ -365,8 +467,13 @@ const EternalDominationGame: React.FC = () => {
{showInfo && ( {showInfo && (
<div className="p-4 bg-muted rounded-lg text-sm space-y-2"> <div className="p-4 bg-muted rounded-lg text-sm space-y-2">
<p><strong>Figure 10 Setup:</strong> The border forms cycle C with guards on all vertices (columns 0/9 and rows 0/1/10/11). Interior guards follow U pattern.</p> <p><strong>Figure 10 Defense Strategy:</strong></p>
<p><strong>Defense (Claim 2):</strong> Interior guards shift toward attack. Two guards move interiorborder (uw, uw), two move borderinterior (wu, wu). Complementary paths P, and P, (green) shift border guards.</p> <ul className="list-disc pl-5 space-y-1">
<li><span className="text-red-500 font-medium">Red arrows:</span> Interior guards shift in chains toward attack (v_t)</li>
<li><span className="text-orange-500 font-medium">Orange:</span> Interior guard pushed to border (uw, uw)</li>
<li><span className="text-cyan-500 font-medium">Cyan:</span> Border guard enters interior (wu, wu)</li>
<li><span className="text-green-500 font-medium">Green arrows:</span> Complementary path shifts (P, and P,) restore border</li>
</ul>
</div> </div>
)} )}
@ -375,7 +482,7 @@ const EternalDominationGame: React.FC = () => {
{message} {message}
</div> </div>
{/* Grid rendering */} {/* Grid */}
<div className="flex justify-center overflow-x-auto pb-4"> <div className="flex justify-center overflow-x-auto pb-4">
<div <div
className="relative" className="relative"
@ -384,12 +491,12 @@ const EternalDominationGame: React.FC = () => {
height: ROWS * cellH + 20 height: ROWS * cellH + 20
}} }}
> >
{/* Edges */}
<svg <svg
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none"
width={COLS * cellW + offset + 20} width={COLS * cellW + offset + 20}
height={ROWS * cellH + 20} height={ROWS * cellH + 20}
> >
{/* Edges */}
{Array.from({ length: ROWS }, (_, y) => {Array.from({ length: ROWS }, (_, y) =>
Array.from({ length: COLS }, (_, x) => { Array.from({ length: COLS }, (_, x) => {
const rowOffset = y % 2 === 1 ? offset : 0; const rowOffset = y % 2 === 1 ? offset : 0;
@ -403,7 +510,6 @@ const EternalDominationGame: React.FC = () => {
const ncx = nb.x * cellW + cellW / 2 + nbOffset + 10; const ncx = nb.x * cellW + cellW / 2 + nbOffset + 10;
const ncy = nb.y * cellH + cellH / 2 + 10; const ncy = nb.y * cellH + cellH / 2 + 10;
// Green edges for border cycle (complementary paths)
const isBorderEdge = isBorder(x, y) && isBorder(nb.x, nb.y); const isBorderEdge = isBorder(x, y) && isBorder(nb.x, nb.y);
return ( return (
@ -411,8 +517,8 @@ const EternalDominationGame: React.FC = () => {
key={`${x}-${y}-${i}`} key={`${x}-${y}-${i}`}
x1={cx} y1={cy} x2={ncx} y2={ncy} x1={cx} y1={cy} x2={ncx} y2={ncy}
stroke={isBorderEdge ? "#22c55e" : "currentColor"} stroke={isBorderEdge ? "#22c55e" : "currentColor"}
strokeOpacity={isBorderEdge ? 0.7 : 0.15} strokeOpacity={isBorderEdge ? 0.6 : 0.15}
strokeWidth={isBorderEdge ? 2.5 : 1} strokeWidth={isBorderEdge ? 2 : 1}
/> />
); );
}); });
@ -428,7 +534,7 @@ const EternalDominationGame: React.FC = () => {
const x2 = move.to.x * cellW + cellW / 2 + toOff + 10; const x2 = move.to.x * cellW + cellW / 2 + toOff + 10;
const y2 = move.to.y * cellH + cellH / 2 + 10; const y2 = move.to.y * cellH + cellH / 2 + 10;
const color = move.type === 'path' ? '#22c55e' : move.type === 'border' ? '#06b6d4' : '#ef4444'; const color = getMovementColor(move.type);
return ( return (
<line <line
@ -436,7 +542,7 @@ const EternalDominationGame: React.FC = () => {
x1={x1} y1={y1} x2={x2} y2={y2} x1={x1} y1={y1} x2={x2} y2={y2}
stroke={color} stroke={color}
strokeWidth={3} strokeWidth={3}
strokeOpacity={0.85} strokeOpacity={0.9}
markerEnd={`url(#arrow-${move.type})`} markerEnd={`url(#arrow-${move.type})`}
/> />
); );
@ -446,10 +552,13 @@ const EternalDominationGame: React.FC = () => {
<marker id="arrow-interior" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"> <marker id="arrow-interior" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#ef4444" /> <polygon points="0 0, 8 3, 0 6" fill="#ef4444" />
</marker> </marker>
<marker id="arrow-border" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"> <marker id="arrow-toInterior" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#06b6d4" /> <polygon points="0 0, 8 3, 0 6" fill="#06b6d4" />
</marker> </marker>
<marker id="arrow-path" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"> <marker id="arrow-toBorder" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#f97316" />
</marker>
<marker id="arrow-pathShift" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#22c55e" /> <polygon points="0 0, 8 3, 0 6" fill="#22c55e" />
</marker> </marker>
</defs> </defs>
@ -481,7 +590,7 @@ const EternalDominationGame: React.FC = () => {
top: y * cellH + 10 + (cellH - 36) / 2, top: y * cellH + 10 + (cellH - 36) / 2,
}} }}
onClick={() => handleCellClick(x, y)} onClick={() => handleCellClick(x, y)}
title={`(${x},${y}) ${hasGuard ? (onBorder ? "Border" : "Interior") : dominated ? "" : "UNDOMINATED"}`} title={`(${x},${y})`}
> >
{hasGuard && <Shield className="w-4 h-4" />} {hasGuard && <Shield className="w-4 h-4" />}
</div> </div>
@ -497,17 +606,17 @@ const EternalDominationGame: React.FC = () => {
<div className="w-6 h-6 bg-cyan-500/60 border-2 border-cyan-600 rounded-full flex items-center justify-center"> <div className="w-6 h-6 bg-cyan-500/60 border-2 border-cyan-600 rounded-full flex items-center justify-center">
<Shield className="w-3 h-3" /> <Shield className="w-3 h-3" />
</div> </div>
<span>Border guard</span> <span>Border</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-6 h-6 bg-red-500/60 border-2 border-red-600 rounded-full flex items-center justify-center"> <div className="w-6 h-6 bg-red-500/60 border-2 border-red-600 rounded-full flex items-center justify-center">
<Shield className="w-3 h-3" /> <Shield className="w-3 h-3" />
</div> </div>
<span>Interior guard</span> <span>Interior</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-green-500"></div> <div className="w-4 h-0.5 bg-green-500"></div>
<span>Complementary paths (P, / P,)</span> <span>Path shifts (P,/P,)</span>
</div> </div>
</div> </div>