Skip to main content
Minesweeper is a fully functional recreation of the classic Windows XP game with authentic gameplay, graphics, and difficulty settings.

Features

  • Three Difficulty Levels: Beginner, Intermediate, Expert
  • Mine Detection: Click to reveal cells, right-click to flag
  • Auto-Reveal: Automatically opens adjacent cells when appropriate
  • Timer: Tracks game duration
  • Smile Face: Shows game status (playing, won, died)
  • Mine Counter: Displays remaining mines
  • Mobile Detection: Blocks mobile devices (mouse-only game)

Component Structure

Location: src/WinXP/apps/Minesweeper/index.jsx
function MineSweeper({ defaultDifficulty, onClose }) {
  const [state, dispatch] = useReducer(reducer, getInitState(defaultDifficulty));
  const seconds = useTimer(state.status);
  
  // Game logic
}

Configuration

Game Settings

From config.js:
export const Config = {
  Beginner: {
    rows: 9,
    columns: 9,
    ceils: 81,
    mines: 10,
  },
  Intermediate: {
    rows: 16,
    columns: 16,
    ceils: 256,
    mines: 40,
  },
  Expert: {
    rows: 16,
    columns: 30,
    ceils: 480,
    mines: 99,
  },
};

App Settings

From apps/index.jsx:
Minesweeper: {
  name: 'Minesweeper',
  header: { icon: mine, title: 'Minesweeper' },
  component: WrappedMinesweeper,
  defaultSize: { width: 0, height: 0 }, // Auto-size based on grid
  defaultOffset: getCenter(0, 0),
  resizable: false,
  minimized: false,
  maximized: shouldMaximize(0, 0, false),
  multiInstance: true,
}

Mobile Detection

Minesweeper blocks mobile devices since it requires mouse interaction:
const WrappedMinesweeper = props => {
  if (checkMinesweeperBlock()) {
    return (
      <ErrorBox
        {...props}
        message="Mobile Device Detected: Minesweeper is designed for desktop mouse interaction and does not function correctly on mobile devices."
        title="Compatibility Warning"
      />
    );
  }
  return <MinesweeperComponent {...props} />;
};

const checkMinesweeperBlock = () => isMobileUA();
From dropDownData.js:

Game Menu

  • New (F2) - Start new game
  • Separator
  • Beginner - Switch to 9x9 grid with 10 mines
  • Intermediate - Switch to 16x16 grid with 40 mines
  • Expert - Switch to 16x30 grid with 99 mines
  • Custom… - Custom grid configuration
  • Separator
  • Marks (?) - Toggle question mark feature (checked)
  • Color - Toggle color mode (checked)
  • Sound - Toggle sound effects
  • Separator
  • Best Times… - View high scores
  • Separator
  • Exit - Close game

Help Menu

  • Contents (F1) - Help documentation
  • Search for Help on… - Search help topics
  • Using Help - Help system tutorial
  • Separator
  • About Minesweeper - Version info

Game State Management

The game uses a reducer pattern for state management:
function reducer(state, action) {
  switch (action.type) {
    case 'CLEAR_MAP':
      return getInitState(action.payload || state.difficulty);
    
    case 'START_GAME':
      return {
        ...state,
        ...insertMines({ ...Config[state.difficulty], exclude: action.payload }, state.ceils),
        status: 'started',
      };
    
    case 'OPEN_CEIL':
      const indexes = autoCeils(state, action.payload);
      const ceils = [...state.ceils];
      indexes.forEach(i => {
        ceils[i] = { ...ceils[i], state: 'open' };
      });
      return { ...state, ceils };
    
    case 'CHANGE_CEIL_STATE':
      // Cycle: cover -> flag -> unknown -> cover
      const ceil = state.ceils[index];
      const newState = ceil.state === 'cover' ? 'flag' 
                     : ceil.state === 'flag' ? 'unknown' 
                     : 'cover';
      ceils[index] = { ...ceil, state: newState };
      return { ...state, ceils };
    
    case 'GAME_OVER':
      // Reveal all mines, mark incorrect flags
      return { ...state, status: 'died', ceils: revealedCeils };
    
    case 'WON':
      // Open all safe cells, flag all mines
      return { ...state, status: 'won', ceils: winCeils };
  }
}

Cell States

Each cell can be in one of these states:
  • cover: Hidden, clickable
  • flag: Right-clicked once, marked as mine
  • unknown: Right-clicked twice, marked with ”?”
  • open: Revealed, shows number or empty
  • mine: Revealed mine (on game over)
  • die: The mine that was clicked (red background)
  • misflagged: Flagged but not a mine (on game over)

Gameplay Functions

function openCeil(index) {
  switch (state.status) {
    case 'new':
      // First click - place mines avoiding this cell
      dispatch({ type: 'START_GAME', payload: index });
      dispatch({ type: 'OPEN_CEIL', payload: index });
      break;
    
    case 'started':
      const ceil = state.ceils[index];
      if (ceil.state === 'flag' || ceil.state === 'open') break;
      
      if (ceil.minesAround < 0) {
        // Hit a mine!
        dispatch({ type: 'GAME_OVER', payload: index });
      } else {
        // Safe cell
        dispatch({ type: 'OPEN_CEIL', payload: index });
      }
      break;
  }
}

function changeCeilState(index) {
  // Right-click to flag/unflag
  const ceil = state.ceils[index];
  if (ceil.state === 'open' || ['won', 'died'].includes(state.status)) return;
  dispatch({ type: 'CHANGE_CEIL_STATE', payload: index });
}

function openCeils(index) {
  // Double-click on revealed number to open adjacent cells
  const ceil = state.ceils[index];
  if (ceil.state !== 'open' || ceil.minesAround <= 0) return;
  
  const indexes = getNearIndexes(index, state.rows, state.columns);
  const nearCeils = indexes.map(i => state.ceils[i]);
  
  // Only if correct number of flags placed
  if (nearCeils.filter(c => c.state === 'flag').length !== ceil.minesAround) return;
  
  // Check for unflagged mine
  const mineIndex = indexes.find(
    i => state.ceils[i].minesAround < 0 && state.ceils[i].state !== 'flag'
  );
  
  if (mineIndex) {
    dispatch({ type: 'GAME_OVER', payload: mineIndex });
  } else {
    indexes.forEach(i => dispatch({ type: 'OPEN_CEIL', payload: i }));
  }
}

Auto-Reveal Algorithm

When clicking an empty cell (0 adjacent mines), automatically reveal all connected empty cells:
function autoCeils(state, index) {
  const { rows, columns } = state;
  const ceils = state.ceils.map(ceil => ({ ...ceil, walked: false }));
  
  function walkCeils(index) {
    const ceil = ceils[index];
    if (ceil.walked || ceil.minesAround < 0 || ceil.state === 'flag') return [];
    ceil.walked = true;
    
    if (ceil.minesAround > 0) return [index]; // Stop at numbered cells
    
    // Continue walking to adjacent cells
    return [
      index,
      ...getNearIndexes(index, rows, columns).reduce(
        (acc, ceilIndex) => [...acc, ...walkCeils(ceilIndex)],
        []
      ),
    ];
  }
  
  return walkCeils(index);
}

Timer

Custom hook for tracking game duration:
function useTimer(status) {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    let timer;
    switch (status) {
      case 'started':
        timer = setInterval(() => setSeconds(sec => sec + 1), 1000);
        break;
      case 'new':
        setSeconds(0);
        break;
    }
    return () => clearInterval(timer);
  }, [status]);
  
  return seconds;
}

Win Condition

Game is won when all safe cells are revealed:
useEffect(() => {
  if (state.status === 'started' && checkRemains() === 0) {
    dispatch({ type: 'WON' });
  }
});

function checkRemains() {
  const safeCeils = state.ceils
    .filter(ceil => ceil.state !== 'open')
    .filter(ceil => ceil.minesAround >= 0);
  return safeCeils.length;
}

Build docs developers (and LLMs) love