Mercurial
comparison react_games/src/Games.tsx @ 37:fb9bcd3145cb
[ReactGames] Few games I made using react just to practice few things.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 01 Dec 2025 20:22:47 -0800 |
| parents | |
| children | 49b611c808e7 |
comparison
equal
deleted
inserted
replaced
| 36:84672efec192 | 37:fb9bcd3145cb |
|---|---|
| 1 import ReactDOM from 'react-dom/client'; | |
| 2 import React, { CSSProperties, lazy, LazyExoticComponent, Suspense, useEffect, useState } from "react"; | |
| 3 | |
| 4 const LazyTicTacToe = lazy(() => import("./Tictactoe/main.tsx").then(m => ({ default: m.TicTacToe }))); | |
| 5 const LazyConnectFour = lazy(() => import("./Connectfour/main.tsx").then(m => ({ default: m.ConnectFour }))); | |
| 6 const LazyMemoryGame = lazy(() => import("./CardMatchiing/main.tsx").then(m => ({ default: m.MemoryGame }))); | |
| 7 const LazyLightsOut = lazy(() => import("./LightsOut/main.tsx").then(m => ({ default: m.LightsOut }))); | |
| 8 const LazyWordle = lazy(() => import("./Wordle/main.tsx").then(m => ({ default: m.Wordle }))); | |
| 9 const LazyMinesweeper = lazy(() => import("./Minesweeper/main.tsx").then(m => ({ default: m.Minesweeper }))); | |
| 10 | |
| 11 | |
| 12 | |
| 13 const wrapperStyle: CSSProperties = { | |
| 14 display: 'flex', | |
| 15 justifyContent: 'center', | |
| 16 alignItems: 'center', | |
| 17 minHeight: '100vh', | |
| 18 fontFamily: 'system-ui, sans-serif', | |
| 19 }; | |
| 20 | |
| 21 const menuStyle: CSSProperties = { | |
| 22 listStyle: 'none', | |
| 23 padding: 0, | |
| 24 margin: '2rem auto', | |
| 25 width: 280, | |
| 26 display: 'flex', | |
| 27 flexDirection: 'column', | |
| 28 gap: '1rem', | |
| 29 cursor: 'pointer', | |
| 30 }; | |
| 31 | |
| 32 const menuItemStyle: CSSProperties = { | |
| 33 padding: '0.75rem 1rem', | |
| 34 borderRadius: 8, | |
| 35 background: '#f3f4f6', | |
| 36 textAlign: 'center', | |
| 37 transition: 'background 120ms', | |
| 38 }; | |
| 39 | |
| 40 type ComponentLabels = | |
| 41 | "tictactoe" | |
| 42 | "connectfour" | |
| 43 | "memorygame" | |
| 44 | "lightsout" | |
| 45 | "wordle" | |
| 46 | "minesweeper" | |
| 47 | |
| 48 interface ComponentData { | |
| 49 LazyComponent: LazyExoticComponent<() => React.ReactNode>; | |
| 50 title: string; | |
| 51 } | |
| 52 | |
| 53 const gameMap: (Record<ComponentLabels, ComponentData>) = { | |
| 54 tictactoe: { | |
| 55 LazyComponent: LazyTicTacToe, | |
| 56 title: "TicTacToe", | |
| 57 }, | |
| 58 connectfour: { | |
| 59 LazyComponent: LazyConnectFour, | |
| 60 title: "Connect Four", | |
| 61 }, | |
| 62 memorygame: { | |
| 63 LazyComponent: LazyMemoryGame, | |
| 64 title: "Memory Game", | |
| 65 }, | |
| 66 lightsout: { | |
| 67 LazyComponent: LazyLightsOut, | |
| 68 title: "Lights out", | |
| 69 }, | |
| 70 wordle: { | |
| 71 LazyComponent: LazyWordle, | |
| 72 title: "Wordle", | |
| 73 }, | |
| 74 minesweeper: { | |
| 75 LazyComponent: LazyMinesweeper, | |
| 76 title: "Minesweeper", | |
| 77 }, | |
| 78 } | |
| 79 | |
| 80 type GameKey = ComponentLabels | null; | |
| 81 | |
| 82 const BASE = "/games"; | |
| 83 const Games = () => { | |
| 84 const getKey = (p: string): GameKey => { | |
| 85 const m = p.match(/^\/games\/?([^/]+)?/i); | |
| 86 const k = (m?.[1] || '').toLowerCase() as GameKey; | |
| 87 return k && k in gameMap ? k : null; | |
| 88 }; | |
| 89 | |
| 90 const [game, setGame] = useState<GameKey>(() => getKey(location.pathname)); | |
| 91 | |
| 92 useEffect(() => { | |
| 93 const onPop = () => setGame(getKey(location.pathname)); | |
| 94 addEventListener('popstate', onPop); | |
| 95 return () => removeEventListener('popstate', onPop); | |
| 96 }, []); | |
| 97 | |
| 98 useEffect(() => { | |
| 99 history.pushState(null, '', game ? `${BASE}/${game}` : BASE); | |
| 100 }, [game]); | |
| 101 | |
| 102 const selected = game ? gameMap[game] : null; | |
| 103 | |
| 104 return ( | |
| 105 <main style={wrapperStyle}> | |
| 106 <section> | |
| 107 <h1 style={{ textAlign: "center" }}>MAI React Playground</h1> | |
| 108 | |
| 109 {selected?.LazyComponent ? ( | |
| 110 <Suspense fallback={<div>loading...</div>}> | |
| 111 <div style={ | |
| 112 { | |
| 113 display: "flex", | |
| 114 flexDirection: "column", | |
| 115 justifyContent: "center", | |
| 116 alignItems: "center", | |
| 117 gap: 10, | |
| 118 }}> | |
| 119 <selected.LazyComponent /> | |
| 120 </div> | |
| 121 </Suspense> | |
| 122 ) : ( | |
| 123 <ul style={menuStyle}> | |
| 124 {Object.entries(gameMap).map(([key, value]) => ( | |
| 125 <li key={key} style={menuItemStyle} onClick={() => setGame(key as GameKey)}> | |
| 126 {value.title} | |
| 127 </li> | |
| 128 ))} | |
| 129 </ul> | |
| 130 )} | |
| 131 | |
| 132 {game && ( | |
| 133 <button style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "cetner", margin: "1.5rem auto 0" }} onClick={() => setGame(null)}> | |
| 134 Back to menu | |
| 135 </button> | |
| 136 )} | |
| 137 | |
| 138 </section> | |
| 139 </main> | |
| 140 ); | |
| 141 }; | |
| 142 | |
| 143 export { | |
| 144 Games, | |
| 145 } | |
| 146 | |
| 147 ReactDOM.createRoot(document.getElementById('root')!).render(<Games />); |