
Classic Minesweeper rebuilt in 3D with Unreal Engine 5 C++. The grid spawns as physical actors; cell reveals use line traces against the world. Bombs are placed via a shuffle-pool algorithm that guarantees no duplicates. The game-over reveal floods all cells in one pass.
AMinesweeperCell and reveals itself or triggers game over.GameOver() iterates all cells and calls Reveal() on each, flipping materials to show bomb locations.Pool shuffle: all valid indices in a TArray, shuffled, first N taken as bomb positions. No hash-set needed, no retry loop.
void AMinesweeperGrid::InitializeGrid()
{
Cells.Empty();
const int32 Total = GridWidth * GridHeight;
// Spawn all cell actors on the grid
for (int32 i = 0; i < Total; ++i)
{
FVector Loc = IndexToWorld(i);
AMinesweeperCell* Cell = GetWorld()->SpawnActor<AMinesweeperCell>(CellClass, Loc, FRotator::ZeroRotator);
Cells.Add(Cell);
}
// Fisher-Yates pool shuffle — duplicate-free bomb placement
TArray<int32> Options;
for (int32 i = 0; i < Total; ++i) Options.Add(i);
for (int32 i = Total - 1; i > 0; --i)
Options.Swap(i, FMath::RandRange(0, i));
for (int32 k = 0; k < BombCount; ++k)
Cells[Options[k]]->SetBomb(true);
// Set adjacent-bomb counts for non-bomb cells
for (int32 i = 0; i < Total; ++i)
if (!Cells[i]->IsBomb())
Cells[i]->SetAdjacentCount(CountAdjacentBombs(i));
}
void AMinesweeperGrid::LineTraceRevealCell(const FVector& Start, const FVector& End)
{
FHitResult Hit;
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility))
{
if (AMinesweeperCell* Cell = Cast<AMinesweeperCell>(Hit.GetActor()))
{
if (Cell->IsBomb()) GameOver();
else Cell->Reveal();
}
}
}
void AMinesweeperGrid::GameOver()
{
for (AMinesweeperCell* Cell : Cells) Cell->Reveal(); // expose all
OnGameOver.Broadcast();
}