Mercurial
view react_games/src/CardMatchiing/main.tsx @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | fb9bcd3145cb |
| children |
line wrap: on
line source
/** * * You’re tasked with building a simple memory card-matching game in React. Each card has a hidden value, and the player flips two cards at a time to try to find a match. If the cards match, they stay face-up. If not, they flip back after a short delay. The game ends when all pairs are matched. // Basic components // Board // Cards (values 1 to 10) // There will be 20 cards, 4 x 5. // // GameState: playable or not // logic check if the value sames // // listOfCurrentlyTurnOpend: len(2) number[] */ import React, { createContext, useContext, useEffect, useReducer, } from "react"; /* ──────────────────── Types ──────────────────── */ interface CardValue { value: number; isFacing: boolean; isSolved: boolean; } interface CardProp { row: number; col: number; card: CardValue; } interface RowProp { rowPos: number; row: CardValue[]; } type Action = | { type: "move"; row: number; col: number } | { type: "flipBack" } | { type: "reset" }; interface Position { row: number; col: number; } interface GameState { board: CardValue[][]; open: Position[]; // cards currently face-up but not yet decided (max 2) busy: boolean; // UI locked while we wait to flip cards back } /* ──────────────────── Helpers ──────────────────── */ const shuffle = <T,>(arr: T[]): T[] => [...arr].sort(() => Math.random() - 0.5); const makeBoard = (): CardValue[][] => { // two of each from 1-10, then shuffle and slice into 4×5 const values = shuffle( Array.from({ length: 10 }, (_, i) => i + 1).flatMap((v) => [v, v]) ); return Array.from({ length: 4 }, (_, r) => Array.from({ length: 5 }, (_, c) => ({ value: values[r * 5 + c], isFacing: false, isSolved: false, })) ); }; /* ──────────────────── Context ──────────────────── */ const BoardContext = createContext<React.Dispatch<Action> | null>(null); const useBoardDispatch = () => { const ctx = useContext(BoardContext); if (!ctx) throw new Error("BoardContext missing"); return ctx; }; /* ──────────────────── Reducer ──────────────────── */ const initial = (): GameState => ({ board: makeBoard(), open: [], busy: false }); function reducer(state: GameState, action: Action): GameState { switch (action.type) { case "reset": return initial(); case "flipBack": { // hide the two open cards const [a, b] = state.open; const nextBoard = state.board.map((row, r) => row.map((card, c) => (r === a.row && c === a.col) || (r === b.row && c === b.col) ? { ...card, isFacing: false } : card ) ); return { board: nextBoard, open: [], busy: false }; } case "move": { if (state.busy) return state; // ignore clicks while waiting const { row, col } = action; const target = state.board[row][col]; if (target.isFacing || target.isSolved) return state; // flip this one up const nextBoard = state.board.map((r, ri) => r.map((c, ci) => ri === row && ci === col ? { ...c, isFacing: true } : c ) ); const open = [...state.open, { row, col }]; if (open.length < 2) return { ...state, board: nextBoard, open }; // now we have two cards – decide match / mismatch const [a, b] = open; const first = nextBoard[a.row][a.col]; const second = nextBoard[b.row][b.col]; if (first.value === second.value) { // match → mark solved, leave face-up const solvedBoard = nextBoard.map((r, ri) => r.map((c, ci) => (ri === a.row && ci === a.col) || (ri === b.row && ci === b.col) ? { ...c, isSolved: true } : c ) ); return { board: solvedBoard, open: [], busy: false }; } // mismatch → leave them up temporarily, then flip back via effect return { board: nextBoard, open, busy: true }; } default: return state; } } /* ──────────────────── UI Components ──────────────────── */ const Card = ({ row, col, card }: CardProp) => { const dispatch = useBoardDispatch(); const style: React.CSSProperties = { width: 60, height: 80, margin: 4, fontSize: 24, display: "flex", alignItems: "center", justifyContent: "center", background: card.isSolved ? "#8bc34a" : card.isFacing ? "#fff" : "#90caf9", cursor: card.isSolved ? "default" : "pointer", borderRadius: 6, userSelect: "none", }; return ( <div style={style} onClick={() => dispatch({ type: "move", row, col })}> {card.isFacing || card.isSolved ? card.value : "🂠"} </div> ); }; const Row = ({ row, rowPos }: RowProp) => ( <div style={{ display: "flex" }}> {row.map((card, idx) => ( <Card key={idx} row={rowPos} col={idx} card={card} /> ))} </div> ); /* ──────────────────── Root component ──────────────────── */ export const MemoryGame = () => { const [state, dispatch] = useReducer(reducer, undefined, initial); /* Handle “flipBack” after 1 s for a mismatch */ useEffect(() => { if (state.busy) { const t = setTimeout(() => dispatch({ type: "flipBack" }), 1000); return () => clearTimeout(t); } }, [state.busy]); /* Quick win detection */ const solved = state.board.every((r) => r.every((c) => c.isSolved)); return ( <> <h3 style={{ textAlign: "center" }}> {solved ? "🎉 You won!" : "Memory Game"} </h3> <BoardContext.Provider value={dispatch}> {state.board.map((row, idx) => ( <Row key={idx} rowPos={idx} row={row} /> ))} </BoardContext.Provider> <div style={{ textAlign: "center", marginTop: 12 }}> <button onClick={() => dispatch({ type: "reset" })}>Reset</button> </div> </> ); };