בהמשך לשיעור הקודם – קורס חינם React & GraphQL – שיעור ראשון – אתחול צד לקוח
ממשיכים לבנות את הUI . כפי שציינתי הפיתוח של צד לקוח יהיה ב React בשילוב של Typescript . הפרויקט אמנם פיצפון אבל זה תרגול טוב כי הוא משלב הרבה קונבנציות שימושיות בתעשיה (נכון להיום).
לוח המשחק עם React
בשביל להרכיב את הלוח נפריד בין המבנה לבין הפונקציונליות:
מבנה הלוח
בשביל לבנות לוח של איקס עיגול נצטרך לייצר לוח של 3 על 3. משבצות עדיפות לbutton כך מכוון שהיוזר הולך ללחוץ על משבצת רנדומלית ונצטרך לתפוס את האיוונט ולייצר דטה מתאים (או איקס או עיגול). יהיה הכי נוח לייצר רכיב נפרד לBoard וגם רכיב אשר יגיד משבצת (Square). הלוח יהיה בגודל (גובה ורוחב שווים על מנת לייצר ריבוע) וגודל המשבצת תוגדר כך שיכנסו 3 בשורה. (למשל באחוזים 33%)
לוגיקת לוח המשחק
בלוגיקה יש לנו כמה דברים :
- אפשרויות לניצחון
- תור איזה שחקן לשחק עכשיו (איקס יהיה השחקן שהתחיל ועיגול יהיה השחקן השני)
- שמירת התוצאות התורות של השחקנים ולהכריז על המצנח במידה ויש כזה.
להלן הקבצים של היואיי שלי . כמובן שכל אחד יכול לקחת את זה לכיוון אחר. יש הרבה אפשרויות ליצור לוח כזה.
import './App.scss'; import Board from './components/board'; const App = (props: any): JSX.Element => { return ( <div className="App"> <h1>TIK-TAK-TOE</h1> <Board /> </div> ); } export default App;
.App { text-align: center; h1 { color: #fff; } } button { background: transparent; border: none; } .boardContainer { margin: 30px; width: 300px; height: 300px; margin: 0 auto; border: 1px solid turquoise; button.squareContainer { display: inline-block; color: #fff; width: 33.3333333%; height: 33.333333%; border: 1px solid turquoise; position: relative; cursor: pointer; span { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } } } .winnerContainer { color: #fff; font-size: 20px; display: block; margin: 0 auto; margin-top: 30px; }
import React,{useState, useEffect} from 'react' import Square from './square'; const optionsToWin = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 5, 9], [3, 7, 9], [1, 4, 7], [2, 5, 8], [3, 6, 9]]; const Board = (props: any) : JSX.Element => { const [players, setPlayers] = useState({ one: true, two: false }); const [resultsOne, setResultsOne] = useState<(number | null)[]>([]); const [resultsTwo, setResultsTwo] = useState<(number | null)[]>([]); const [position, setPosition] = useState<number | null>(); const [winner, setWinner] = useState<string | null>(null); const checkPlayerResults = () => { optionsToWin.forEach((chanceToWin: number[]) => { let results: number[] = chanceToWin.filter((item: number) => resultsOne.includes(item)); if (results.length > 2) { setWinner('Player One ' + resultsOne.toString()) } }); optionsToWin.forEach((chanceToWin: number[]) => { let results: number[] = chanceToWin.filter((item: number) => resultsTwo.includes(item)); if (results.length > 2) { setWinner('Player Two ' + resultsTwo.toString()) } }) } useEffect(() => { if (position) { if (players.one) { setResultsOne([...resultsOne, position]) setPlayers({one: false , two: true}) } else { setResultsTwo([...resultsTwo, position]); setPlayers({one: true , two: false}) } } }, [position]); useEffect(() => { checkPlayerResults(); }, [resultsOne, resultsTwo]); return ( <> {!winner ? <div className="boardContainer"> <Square propsWinner={winner} pos={1} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={2} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={3} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={4} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={5} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={6} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={7} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={8} getPos={setPosition} players={players} setPlayer={setPlayers} /> <Square propsWinner={winner} pos={9} getPos={setPosition} players={players} setPlayer={setPlayers} /> </div> : <span className="winnerContainer">{winner}</span>} </> ) } export default Board;
import React , {useState} from 'react' import {string ,shape, bool,func,number,arrayOf} from 'prop-types'; interface squareProps { players :{one: boolean, two: boolean}, setPlayer : Function, pos: number, getPos: Function, propsWinner: string | null } const Square = (props: squareProps) : JSX.Element => { const [state, setstate] = useState<string | null>(null); const handleClick = () :void =>{ props.getPos(props.pos); if(props.players.one) { setstate('X') }else { setstate('O'); } } return ( <button className="squareContainer" onClick={state || props.propsWinner ? undefined : handleClick}> <span>{state}</span> </button> ) } export default Square; Square.propTypes = { players : shape({ one: bool, two: bool }), setPlayer: func, getPos: func, pos: number, propsWinner: string }
הקוד פשוט ואין הרבה מה להסביר. מי שכמובן רוצה לשאול משהו תמיד יכול לרשום בתגובות. מה שיש כאן זה לוח שנבנה עם כפתורים וקצת CSS . ברמת הלוגיקה כפי שציינתי , כל שחקן "אוסף" את המשבצות שלחץ עליהן, יש את המקרים בהם השחקן מנצח את המשחק, וכל תור הלוח בודק אם אחד השחקנים מחזיק באחד מאפשרויות הזכייה.
במידה ויש זוכה, אציג את תוצאות השחקן הזוכה.
סיימנו את הUI אפשר לעבור לGQL
אז עכשיו שיש לנו משחק איקס עיגול בReact נוכל לעבור ולחבר לו את הGQL . בשביל זה נצטרך לדעת קצת Nodejs אז מי שלא יודע לא לדאוג , מדובר בכמה שורות פשוטות . בשיעור הבא נתחיל עם הסבר קצר על מה אנחנו רוצים לשמור ונבנה מוק למידע זה ונבנה צד שרת קטן בNodejs. נתראה בשיעור הבא!