Changes
This commit is contained in:
parent
ab7a417629
commit
ff3a0fc9c3
2 changed files with 210 additions and 152 deletions
BIN
src/assets/fig10-reference.jpg
Normal file
BIN
src/assets/fig10-reference.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
|
|
@ -4,15 +4,18 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
import { Shield, Target, RotateCcw, Info } from "lucide-react";
|
import { Shield, Target, RotateCcw, Info } from "lucide-react";
|
||||||
|
|
||||||
// 12 columns (x: 0-11) × 10 rows (y: 0-9)
|
// Hexagonal grid F₃(12, 10) - 12 columns × 10 rows
|
||||||
|
// Following the "brick wall" offset pattern for hexagonal grids
|
||||||
const COLS = 12;
|
const COLS = 12;
|
||||||
const ROWS = 10;
|
const ROWS = 10;
|
||||||
|
|
||||||
type Position = { x: number; y: number };
|
type Position = { x: number; y: number };
|
||||||
|
|
||||||
// Check if a position is on the border of the rectangle
|
// Position utilities
|
||||||
const isBorder = (x: number, y: number): boolean => {
|
const posKey = (p: Position): string => `${p.x},${p.y}`;
|
||||||
return x === 0 || x === COLS - 1 || y === 0 || y === 1 || y === ROWS - 2 || y === ROWS - 1;
|
const parseKey = (key: string): Position => {
|
||||||
|
const [x, y] = key.split(",").map(Number);
|
||||||
|
return { x, y };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if position is valid
|
// Check if position is valid
|
||||||
|
|
@ -20,35 +23,55 @@ const isValidPos = (x: number, y: number): boolean => {
|
||||||
return x >= 0 && x < COLS && y >= 0 && y < ROWS;
|
return x >= 0 && x < COLS && y >= 0 && y < ROWS;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get adjacent positions (4-connectivity for square grid)
|
// Hexagonal grid adjacency (brick pattern)
|
||||||
const getAdjacent = (x: number, y: number): Position[] => {
|
// Even rows: neighbors at (-1,0), (1,0), (-1,-1), (0,-1), (-1,1), (0,1)
|
||||||
|
// Odd rows: neighbors at (-1,0), (1,0), (0,-1), (1,-1), (0,1), (1,1)
|
||||||
|
const getHexNeighbors = (x: number, y: number): Position[] => {
|
||||||
const neighbors: Position[] = [];
|
const neighbors: Position[] = [];
|
||||||
const deltas = [[-1, 0], [1, 0], [0, -1], [0, 1]];
|
|
||||||
for (const [dx, dy] of deltas) {
|
// Horizontal neighbors (same for all rows)
|
||||||
const nx = x + dx;
|
if (isValidPos(x - 1, y)) neighbors.push({ x: x - 1, y });
|
||||||
const ny = y + dy;
|
if (isValidPos(x + 1, y)) neighbors.push({ x: x + 1, y });
|
||||||
if (isValidPos(nx, ny)) {
|
|
||||||
neighbors.push({ x: nx, y: ny });
|
// Diagonal neighbors depend on row parity (brick pattern)
|
||||||
}
|
const isEvenRow = y % 2 === 0;
|
||||||
|
|
||||||
|
if (isEvenRow) {
|
||||||
|
// Even row: upper-left, upper-right at x-1 and x
|
||||||
|
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 });
|
||||||
|
// Lower neighbors
|
||||||
|
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 });
|
||||||
|
} else {
|
||||||
|
// Odd row: upper-left, upper-right at x and x+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 });
|
||||||
|
// Lower neighbors
|
||||||
|
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return neighbors;
|
return neighbors;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Position key for Set operations
|
// Check if a position is on the border of the "interested rectangle"
|
||||||
const posKey = (p: Position): string => `${p.x},${p.y}`;
|
// Following Figure 10: border forms cycle C around the rectangle
|
||||||
const parseKey = (key: string): Position => {
|
const isBorder = (x: number, y: number): boolean => {
|
||||||
const [x, y] = key.split(",").map(Number);
|
return x === 0 || x === COLS - 1 || y === 0 || y === ROWS - 1;
|
||||||
return { x, y };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial guard placement based on the paper's configuration
|
// Interior positions (not on border)
|
||||||
// Following the pattern from Theorem 4 (square grid eternal dominating set):
|
const isInterior = (x: number, y: number): boolean => {
|
||||||
// S defined as: (0,0) ∈ S; if (x,y) ∈ S then (x+2,y+1), (x-1,y+2), (x-2,y-1), (x+1,y-2) ∈ S
|
return x > 0 && x < COLS - 1 && y > 0 && y < ROWS - 1;
|
||||||
// Plus all border vertices are guarded for the finite case
|
};
|
||||||
|
|
||||||
|
// Generate initial guard placement following the paper's U₃ configuration
|
||||||
|
// Interior guards follow a specific pattern, all border vertices are guarded
|
||||||
const generateInitialGuards = (): Set<string> => {
|
const generateInitialGuards = (): Set<string> => {
|
||||||
const guards = new Set<string>();
|
const guards = new Set<string>();
|
||||||
|
|
||||||
// Add all border vertices (the cycle C from Figure 10)
|
// All border vertices are guarded (forming cycle C)
|
||||||
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)) {
|
||||||
|
|
@ -57,14 +80,15 @@ const generateInitialGuards = (): Set<string> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add interior guards following the pattern from Theorem 4
|
// Interior guards following the eternal dominating set pattern
|
||||||
// For square grid: (0,0) starts, then (x+2,y+1), (x-1,y+2), etc.
|
// Based on the paper: we need interior guards at density 7/15 nm
|
||||||
// We place guards so each interior vertex is dominated by exactly one guard
|
// Pattern: guards at positions creating a dominating set
|
||||||
// Pattern: guards at positions where (2x + y) mod 5 === 0
|
// Using pattern similar to Figure 10's interior placement
|
||||||
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 = 1; y < ROWS - 1; y++) {
|
||||||
// Interior region: x in [1, COLS-2], y in [2, ROWS-3]
|
// Place guards following a hexagonal domination pattern
|
||||||
// Use the diagonal pattern: place guards at specific intervals
|
// Every vertex in interior needs to be within distance 1 of a guard
|
||||||
|
// Pattern: (2x + y) mod 5 === 0 creates suitable coverage
|
||||||
if ((2 * x + y) % 5 === 0) {
|
if ((2 * x + y) % 5 === 0) {
|
||||||
guards.add(posKey({ x, y }));
|
guards.add(posKey({ x, y }));
|
||||||
}
|
}
|
||||||
|
|
@ -74,11 +98,11 @@ const generateInitialGuards = (): Set<string> => {
|
||||||
return guards;
|
return guards;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if a vertex is dominated (has a guard on it or adjacent to a guard)
|
// Check if a vertex is dominated
|
||||||
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;
|
||||||
const adj = getAdjacent(x, y);
|
const neighbors = getHexNeighbors(x, y);
|
||||||
return adj.some(p => guards.has(posKey(p)));
|
return neighbors.some(p => guards.has(posKey(p)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if all vertices are dominated
|
// Check if all vertices are dominated
|
||||||
|
|
@ -91,34 +115,31 @@ const isFullyDominated = (guards: Set<string>): boolean => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find the optimal defense move for an attack
|
// Defense strategy from Figure 10 and page 23:
|
||||||
// Returns new guard positions after defending
|
// 1. Interior guards shift toward attack
|
||||||
|
// 2. If a guard moves from interior to border, border guards shift along complementary paths
|
||||||
const defendAttack = (guards: Set<string>, attack: Position): Set<string> | null => {
|
const defendAttack = (guards: Set<string>, attack: Position): Set<string> | null => {
|
||||||
const attackKey = posKey(attack);
|
const attackKey = posKey(attack);
|
||||||
|
|
||||||
// If attack is on a guard, no movement needed
|
// If attack is on a guard, already defended
|
||||||
if (guards.has(attackKey)) {
|
if (guards.has(attackKey)) {
|
||||||
return guards;
|
return guards;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find adjacent guards that can move to defend
|
// Find adjacent guards that can move to defend
|
||||||
const adjacent = getAdjacent(attack.x, attack.y);
|
const neighbors = getHexNeighbors(attack.x, attack.y);
|
||||||
const adjacentGuards = adjacent.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 - should not happen with proper dominating set
|
return null; // Should not happen with proper dominating set
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy based on Figure 10:
|
|
||||||
// 1. Move interior guard toward attack
|
|
||||||
// 2. Shift guards along border paths to maintain coverage
|
|
||||||
|
|
||||||
const newGuards = new Set(guards);
|
const newGuards = new Set(guards);
|
||||||
|
|
||||||
// Pick the best guard to move (prefer interior guards)
|
// Strategy: prefer interior guards to move (as per Figure 10)
|
||||||
let defender: Position | null = null;
|
let defender: Position | null = null;
|
||||||
for (const g of adjacentGuards) {
|
for (const g of adjacentGuards) {
|
||||||
if (!isBorder(g.x, g.y)) {
|
if (isInterior(g.x, g.y)) {
|
||||||
defender = g;
|
defender = g;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -131,57 +152,71 @@ const defendAttack = (guards: Set<string>, attack: Position): Set<string> | null
|
||||||
newGuards.delete(posKey(defender));
|
newGuards.delete(posKey(defender));
|
||||||
newGuards.add(attackKey);
|
newGuards.add(attackKey);
|
||||||
|
|
||||||
// If defender was interior and moved to border, we need to shift border guards
|
// If defender was interior and attack is on border, apply border shift strategy
|
||||||
|
// Following the complementary paths P₁,₃ and P₂,₄ from Figure 10
|
||||||
|
if (isInterior(defender.x, defender.y)) {
|
||||||
|
// Interior to border movement: shift guards along border cycle
|
||||||
// to maintain the dominating set property
|
// to maintain the dominating set property
|
||||||
if (!isBorder(defender.x, defender.y) && isBorder(attack.x, attack.y)) {
|
applyBorderShift(newGuards, defender, attack);
|
||||||
// The complementary path strategy from Figure 10
|
|
||||||
// Shift guards along the border cycle to fill the gap left by interior movement
|
|
||||||
performBorderShift(newGuards, attack, defender);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we still have a dominating set, if not, apply additional shifts
|
// Ensure domination is maintained
|
||||||
ensureDominatingSet(newGuards, attack);
|
ensureDomination(newGuards);
|
||||||
|
|
||||||
return newGuards;
|
return newGuards;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Shift guards along border to maintain coverage
|
// Border shift strategy: when interior guard moves out, shift border guards
|
||||||
const performBorderShift = (guards: Set<string>, to: Position, from: Position): void => {
|
// along complementary paths to fill gaps
|
||||||
// Simple strategy: if we created a gap, try to fill it with neighboring guard shifts
|
const applyBorderShift = (guards: Set<string>, from: Position, to: Position): void => {
|
||||||
const fromKey = posKey(from);
|
// Check if any interior vertices became undominated
|
||||||
|
for (let x = 1; x < COLS - 1; x++) {
|
||||||
// Find border neighbors of the vacated position that have guards
|
for (let y = 1; y < ROWS - 1; y++) {
|
||||||
const neighbors = getAdjacent(from.x, from.y);
|
if (!isDominated(x, y, guards)) {
|
||||||
|
// Find a neighboring guard to shift in
|
||||||
|
const neighbors = getHexNeighbors(x, y);
|
||||||
for (const n of neighbors) {
|
for (const n of neighbors) {
|
||||||
if (isBorder(n.x, n.y) && guards.has(posKey(n))) {
|
if (guards.has(posKey(n))) {
|
||||||
// Found a border guard that can help
|
// Check if we can shift this guard
|
||||||
// For now, we don't need to shift if the vacated position was interior
|
const nNeighbors = getHexNeighbors(n.x, n.y);
|
||||||
break;
|
const canShift = nNeighbors.some(nn =>
|
||||||
|
guards.has(posKey(nn)) &&
|
||||||
|
posKey(nn) !== posKey(n) &&
|
||||||
|
posKey(nn) !== posKey(to)
|
||||||
|
);
|
||||||
|
if (canShift || isBorder(n.x, n.y)) {
|
||||||
|
// Shift a border guard toward the gap
|
||||||
|
const borderNeighbors = neighbors.filter(nb =>
|
||||||
|
isBorder(nb.x, nb.y) && guards.has(posKey(nb))
|
||||||
|
);
|
||||||
|
if (borderNeighbors.length > 0) {
|
||||||
|
const shifter = borderNeighbors[0];
|
||||||
|
guards.delete(posKey(shifter));
|
||||||
|
guards.add(posKey({ x, y }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure dominating set property after movement
|
// Ensure all vertices remain dominated after movement
|
||||||
const ensureDominatingSet = (guards: Set<string>, attackPos: Position): void => {
|
const ensureDomination = (guards: Set<string>): void => {
|
||||||
// Check all vertices are still dominated
|
|
||||||
// If not, this would indicate an issue with our defense strategy
|
|
||||||
// In practice, with the border fully guarded, we maintain domination
|
|
||||||
|
|
||||||
// Add back any critical positions if needed
|
|
||||||
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 (!isDominated(x, y, guards)) {
|
if (!isDominated(x, y, guards)) {
|
||||||
// Find nearest guard and shift toward this position
|
// Emergency: place a guard at nearest available position
|
||||||
const adj = getAdjacent(x, y);
|
const neighbors = getHexNeighbors(x, y);
|
||||||
for (const a of adj) {
|
for (const n of neighbors) {
|
||||||
const adjKey = posKey(a);
|
const nNeighbors = getHexNeighbors(n.x, n.y);
|
||||||
// Try to find a guard that can shift
|
for (const nn of nNeighbors) {
|
||||||
const adjAdj = getAdjacent(a.x, a.y);
|
if (guards.has(posKey(nn))) {
|
||||||
for (const aa of adjAdj) {
|
|
||||||
if (guards.has(posKey(aa)) && !posKey(aa).includes(posKey(attackPos))) {
|
|
||||||
// Shift this guard
|
// Shift this guard
|
||||||
guards.delete(posKey(aa));
|
guards.delete(posKey(nn));
|
||||||
guards.add(adjKey);
|
guards.add(posKey(n));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,20 +229,18 @@ const ensureDominatingSet = (guards: Set<string>, attackPos: Position): void =>
|
||||||
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 cell to attack. Guards will move to defend.");
|
const [message, setMessage] = useState<string>("Click any cell to attack. Guards will shift to defend.");
|
||||||
const [moveCount, setMoveCount] = useState(0);
|
const [moveCount, setMoveCount] = useState(0);
|
||||||
const [showInfo, setShowInfo] = useState(false);
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
|
|
||||||
const handleCellClick = useCallback((x: number, y: number) => {
|
const handleCellClick = useCallback((x: number, y: number) => {
|
||||||
const attack: Position = { x, y };
|
const attack: Position = { x, y };
|
||||||
|
|
||||||
// Check if attack is valid (not on a guard)
|
|
||||||
if (guards.has(posKey(attack))) {
|
if (guards.has(posKey(attack))) {
|
||||||
setMessage("Cannot attack a guarded position. Choose an unguarded cell.");
|
setMessage("Cannot attack a guarded position. Choose an unguarded cell.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defend the attack
|
|
||||||
const newGuards = defendAttack(guards, attack);
|
const newGuards = defendAttack(guards, attack);
|
||||||
|
|
||||||
if (newGuards) {
|
if (newGuards) {
|
||||||
|
|
@ -228,65 +261,41 @@ const EternalDominationGame: React.FC = () => {
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
setGuards(generateInitialGuards());
|
setGuards(generateInitialGuards());
|
||||||
setAttackHistory([]);
|
setAttackHistory([]);
|
||||||
setMessage("Click any cell to attack. Guards will move to defend.");
|
setMessage("Click any cell to attack. Guards will shift to defend.");
|
||||||
setMoveCount(0);
|
setMoveCount(0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getCellStyle = (x: number, y: number): string => {
|
|
||||||
const key = posKey({ x, y });
|
|
||||||
const hasGuard = guards.has(key);
|
|
||||||
const isOnBorder = isBorder(x, y);
|
|
||||||
const dominated = isDominated(x, y, guards);
|
|
||||||
|
|
||||||
let base = "w-8 h-8 border border-border flex items-center justify-center text-lg cursor-pointer transition-all hover:scale-105 ";
|
|
||||||
|
|
||||||
if (hasGuard) {
|
|
||||||
base += isOnBorder
|
|
||||||
? "bg-primary/30 border-primary"
|
|
||||||
: "bg-accent/50 border-accent-foreground";
|
|
||||||
} else if (dominated) {
|
|
||||||
base += "bg-muted hover:bg-muted/80";
|
|
||||||
} else {
|
|
||||||
base += "bg-destructive/20 hover:bg-destructive/30"; // Should not happen
|
|
||||||
}
|
|
||||||
|
|
||||||
return base;
|
|
||||||
};
|
|
||||||
|
|
||||||
const guardCount = guards.size;
|
const guardCount = guards.size;
|
||||||
const interiorGuards = Array.from(guards).filter(k => {
|
const interiorGuards = Array.from(guards).filter(k => {
|
||||||
const p = parseKey(k);
|
const p = parseKey(k);
|
||||||
return !isBorder(p.x, p.y);
|
return isInterior(p.x, p.y);
|
||||||
}).length;
|
}).length;
|
||||||
const borderGuards = guardCount - interiorGuards;
|
const borderGuards = guardCount - interiorGuards;
|
||||||
|
|
||||||
|
// Hexagonal cell dimensions
|
||||||
|
const cellWidth = 36;
|
||||||
|
const cellHeight = 32;
|
||||||
|
const rowOffset = cellWidth / 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Shield className="w-5 h-5" />
|
<Shield className="w-5 h-5" />
|
||||||
Eternal Domination on a Grid
|
Eternal Domination on a Hexagonal Grid
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
12×10 grid with m-eternal dominating set defense strategy
|
12×10 hexagonal grid F₃(12, 10) with m-eternal dominating set defense
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="flex flex-wrap gap-2 items-center justify-between">
|
<div className="flex flex-wrap gap-2 items-center justify-between">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Badge variant="outline">
|
<Badge variant="outline">Guards: {guardCount}</Badge>
|
||||||
Total Guards: {guardCount}
|
<Badge variant="secondary">Border: {borderGuards}</Badge>
|
||||||
</Badge>
|
<Badge variant="secondary">Interior: {interiorGuards}</Badge>
|
||||||
<Badge variant="secondary">
|
<Badge variant="outline">Attacks: {moveCount}</Badge>
|
||||||
Border: {borderGuards}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="secondary">
|
|
||||||
Interior: {interiorGuards}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline">
|
|
||||||
Attacks: {moveCount}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" size="sm" onClick={() => setShowInfo(!showInfo)}>
|
<Button variant="outline" size="sm" onClick={() => setShowInfo(!showInfo)}>
|
||||||
|
|
@ -302,9 +311,9 @@ 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>m-Eternal Domination:</strong> Guards occupy vertices forming a dominating set. When you attack an unguarded vertex, guards move to defend while maintaining domination of all vertices.</p>
|
<p><strong>m-Eternal Domination on Hexagonal Grid:</strong> Guards form a dominating set on F₃(n,m). Each attack must be defended by moving guards while maintaining domination.</p>
|
||||||
<p><strong>Strategy:</strong> Border vertices are always guarded (cycle C). Interior guards shift toward attacks, while border guards shift along complementary paths to maintain coverage.</p>
|
<p><strong>Strategy (Figure 10):</strong> Border vertices form cycle C (always guarded). Interior guards shift toward attacks. Complementary paths along the border shift guards to fill gaps.</p>
|
||||||
<p><strong>Goal:</strong> The attacker wins if they can find an attack sequence that leaves some vertex undominated. Try to break the defense!</p>
|
<p><strong>Goal:</strong> Try to find an attack sequence that leaves some vertex undominated!</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -313,53 +322,102 @@ const EternalDominationGame: React.FC = () => {
|
||||||
{message}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Hexagonal Grid */}
|
||||||
<div className="flex justify-center overflow-x-auto pb-4">
|
<div className="flex justify-center overflow-x-auto pb-4">
|
||||||
<div className="inline-block">
|
|
||||||
<div className="text-xs text-muted-foreground mb-1 pl-4">
|
|
||||||
<div className="flex">
|
|
||||||
{Array.from({ length: COLS }, (_, i) => (
|
|
||||||
<div key={i} className="w-8 text-center">{i}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{Array.from({ length: ROWS }, (_, y) => (
|
|
||||||
<div key={y} className="flex items-center">
|
|
||||||
<div className="w-4 text-xs text-muted-foreground text-right pr-1">
|
|
||||||
{y}
|
|
||||||
</div>
|
|
||||||
{Array.from({ length: COLS }, (_, x) => (
|
|
||||||
<div
|
<div
|
||||||
key={x}
|
className="relative"
|
||||||
className={getCellStyle(x, y)}
|
style={{
|
||||||
onClick={() => handleCellClick(x, y)}
|
width: COLS * cellWidth + rowOffset + 20,
|
||||||
title={`(${x}, ${y}) - ${guards.has(posKey({ x, y })) ? "Guard" : "Empty"}`}
|
height: ROWS * cellHeight + 20
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{guards.has(posKey({ x, y })) ? "🛡️" : ""}
|
{/* Draw edges first */}
|
||||||
|
<svg
|
||||||
|
className="absolute inset-0 pointer-events-none"
|
||||||
|
width={COLS * cellWidth + rowOffset + 20}
|
||||||
|
height={ROWS * cellHeight + 20}
|
||||||
|
>
|
||||||
|
{Array.from({ length: ROWS }, (_, y) =>
|
||||||
|
Array.from({ length: COLS }, (_, x) => {
|
||||||
|
const offset = y % 2 === 1 ? rowOffset : 0;
|
||||||
|
const cx = x * cellWidth + cellWidth / 2 + offset + 10;
|
||||||
|
const cy = y * cellHeight + cellHeight / 2 + 10;
|
||||||
|
|
||||||
|
return getHexNeighbors(x, y)
|
||||||
|
.filter(n => n.x > x || (n.x === x && n.y > y))
|
||||||
|
.map((neighbor, i) => {
|
||||||
|
const nOffset = neighbor.y % 2 === 1 ? rowOffset : 0;
|
||||||
|
const ncx = neighbor.x * cellWidth + cellWidth / 2 + nOffset + 10;
|
||||||
|
const ncy = neighbor.y * cellHeight + cellHeight / 2 + 10;
|
||||||
|
return (
|
||||||
|
<line
|
||||||
|
key={`${x}-${y}-${i}`}
|
||||||
|
x1={cx}
|
||||||
|
y1={cy}
|
||||||
|
x2={ncx}
|
||||||
|
y2={ncy}
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeOpacity={0.2}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Cells */}
|
||||||
|
{Array.from({ length: ROWS }, (_, y) =>
|
||||||
|
Array.from({ length: COLS }, (_, x) => {
|
||||||
|
const offset = y % 2 === 1 ? rowOffset : 0;
|
||||||
|
const hasGuard = guards.has(posKey({ x, y }));
|
||||||
|
const onBorder = isBorder(x, y);
|
||||||
|
const dominated = isDominated(x, y, guards);
|
||||||
|
|
||||||
|
let bgClass = "bg-muted hover:bg-muted/80";
|
||||||
|
if (hasGuard) {
|
||||||
|
bgClass = onBorder
|
||||||
|
? "bg-primary/40 border-primary"
|
||||||
|
: "bg-accent border-accent-foreground";
|
||||||
|
} else if (!dominated) {
|
||||||
|
bgClass = "bg-destructive/30";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`${x}-${y}`}
|
||||||
|
className={`absolute w-7 h-7 rounded-full border flex items-center justify-center text-sm cursor-pointer transition-all hover:scale-110 ${bgClass}`}
|
||||||
|
style={{
|
||||||
|
left: x * cellWidth + offset + 10 + (cellWidth - 28) / 2,
|
||||||
|
top: y * cellHeight + 10 + (cellHeight - 28) / 2,
|
||||||
|
}}
|
||||||
|
onClick={() => handleCellClick(x, y)}
|
||||||
|
title={`(${x}, ${y}) - ${hasGuard ? "Guard" : dominated ? "Dominated" : "Undominated!"}`}
|
||||||
|
>
|
||||||
|
{hasGuard ? "🛡️" : ""}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
</div>
|
})
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legend */}
|
{/* Legend */}
|
||||||
<div className="flex flex-wrap gap-4 text-sm">
|
<div className="flex flex-wrap gap-4 text-sm">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-6 h-6 bg-primary/30 border border-primary rounded flex items-center justify-center text-xs">🛡️</div>
|
<div className="w-6 h-6 bg-primary/40 border border-primary rounded-full flex items-center justify-center text-xs">🛡️</div>
|
||||||
<span>Border Guard</span>
|
<span>Border Guard</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-6 h-6 bg-accent/50 border border-accent-foreground rounded flex items-center justify-center text-xs">🛡️</div>
|
<div className="w-6 h-6 bg-accent border border-accent-foreground rounded-full flex items-center justify-center text-xs">🛡️</div>
|
||||||
<span>Interior Guard</span>
|
<span>Interior Guard</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-6 h-6 bg-muted border border-border rounded"></div>
|
<div className="w-6 h-6 bg-muted border border-border rounded-full"></div>
|
||||||
<span>Dominated (unguarded)</span>
|
<span>Dominated</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Attack History */}
|
|
||||||
{attackHistory.length > 0 && (
|
{attackHistory.length > 0 && (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<p className="text-muted-foreground mb-1">Recent attacks:</p>
|
<p className="text-muted-foreground mb-1">Recent attacks:</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue