view react_games/src/Games.tsx @ 78:e7bf9e002850

amend
author June Park <parkjune1995@gmail.com>
date Wed, 31 Dec 2025 15:07:43 -0800
parents fb9bcd3145cb
children 49b611c808e7
line wrap: on
line source

import ReactDOM from 'react-dom/client';
import React, { CSSProperties, lazy, LazyExoticComponent, Suspense, useEffect, useState } from "react";

const LazyTicTacToe   = lazy(() => import("./Tictactoe/main.tsx").then(m => ({ default: m.TicTacToe })));
const LazyConnectFour = lazy(() => import("./Connectfour/main.tsx").then(m => ({ default: m.ConnectFour })));
const LazyMemoryGame  = lazy(() => import("./CardMatchiing/main.tsx").then(m => ({ default: m.MemoryGame })));
const LazyLightsOut  = lazy(() => import("./LightsOut/main.tsx").then(m => ({ default: m.LightsOut })));
const LazyWordle  = lazy(() => import("./Wordle/main.tsx").then(m => ({ default: m.Wordle })));
const LazyMinesweeper  = lazy(() => import("./Minesweeper/main.tsx").then(m => ({ default: m.Minesweeper })));



const wrapperStyle: CSSProperties = {
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  minHeight: '100vh',
  fontFamily: 'system-ui, sans-serif',
};

const menuStyle: CSSProperties = {
  listStyle: 'none',
  padding: 0,
  margin: '2rem auto',
  width: 280,
  display: 'flex',
  flexDirection: 'column',
  gap: '1rem',
  cursor: 'pointer',
};

const menuItemStyle: CSSProperties = {
  padding: '0.75rem 1rem',
  borderRadius: 8,
  background: '#f3f4f6',
  textAlign: 'center',
  transition: 'background 120ms',
};

type ComponentLabels = 
  | "tictactoe"
  | "connectfour"
  | "memorygame"
  | "lightsout"
  | "wordle"
  | "minesweeper"

interface ComponentData {
  LazyComponent: LazyExoticComponent<() => React.ReactNode>;
  title: string;
}

const gameMap: (Record<ComponentLabels, ComponentData>) = {
  tictactoe: {
    LazyComponent: LazyTicTacToe,
    title: "TicTacToe",
  },
  connectfour: {
    LazyComponent: LazyConnectFour,
    title: "Connect Four",
  },
  memorygame: {
    LazyComponent: LazyMemoryGame,
    title: "Memory Game",
  },
  lightsout: {
    LazyComponent: LazyLightsOut,
    title: "Lights out",
  },
  wordle: {
    LazyComponent: LazyWordle,
    title: "Wordle",
  },
  minesweeper: {
    LazyComponent: LazyMinesweeper,
    title: "Minesweeper",
  },
}

type GameKey = ComponentLabels | null;

const BASE = "/games";
const Games = () => {
  const getKey = (p: string): GameKey => {
    const m = p.match(/^\/games\/?([^/]+)?/i);
    const k = (m?.[1] || '').toLowerCase() as GameKey;
    return k && k in gameMap ? k : null;
  };
  
  const [game, setGame] = useState<GameKey>(() => getKey(location.pathname));
  
  useEffect(() => {
    const onPop = () => setGame(getKey(location.pathname));
    addEventListener('popstate', onPop);
    return () => removeEventListener('popstate', onPop);
  }, []);
  
  useEffect(() => {
    history.pushState(null, '', game ? `${BASE}/${game}` : BASE);
  }, [game]);

  const selected = game ? gameMap[game] : null;

  return (
    <main style={wrapperStyle}>
      <section>
        <h1 style={{ textAlign: "center" }}>MAI React Playground</h1>

        {selected?.LazyComponent ? (
          <Suspense fallback={<div>loading...</div>}>
            <div style={
              { 
                display: "flex", 
                flexDirection: "column", 
                justifyContent: "center", 
                alignItems: "center",
                gap: 10,
              }}> 
              <selected.LazyComponent />
            </div>
          </Suspense>
        ) : (
          <ul style={menuStyle}>
            {Object.entries(gameMap).map(([key, value]) => (
              <li key={key} style={menuItemStyle} onClick={() => setGame(key as GameKey)}>
                {value.title}
              </li>
            ))}
          </ul>
        )}

        {game && (
          <button style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "cetner", margin: "1.5rem auto 0" }} onClick={() => setGame(null)}>
            Back to menu
          </button>
        )}

      </section>
    </main>
  );
};

export {
  Games,
}

ReactDOM.createRoot(document.getElementById('root')!).render(<Games />);