diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/App.js | 90 | ||||
-rw-r--r-- | src/components/App.tsx | 42 | ||||
-rw-r--r-- | src/components/Box.js (renamed from src/components/Box.tsx) | 8 | ||||
-rw-r--r-- | src/components/Footer.js (renamed from src/components/Footer.tsx) | 2 | ||||
-rw-r--r-- | src/components/GamemodeChooser.js | 63 | ||||
-rw-r--r-- | src/components/Grid.js (renamed from src/components/Grid.tsx) | 50 | ||||
-rw-r--r-- | src/components/MessageBox.js (renamed from src/components/MessageBox.tsx) | 9 | ||||
-rw-r--r-- | src/components/MultiplayerGrid.js | 77 | ||||
-rw-r--r-- | src/components/MultiplayerMenu.js | 40 | ||||
-rw-r--r-- | src/components/ScoreBoard.js (renamed from src/components/ScoreBoard.tsx) | 15 | ||||
-rw-r--r-- | src/components/style.css | 30 | ||||
-rw-r--r-- | src/index.js | 4 | ||||
-rw-r--r-- | src/index.tsx | 16 | ||||
-rw-r--r-- | src/reportWebVitals.ts | 15 | ||||
-rw-r--r-- | src/server/index.js | 61 |
15 files changed, 380 insertions, 142 deletions
diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..db5af1b --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,90 @@ +/* + * Tic Tac Toe - Minimalistic Tic Tac Toe + * Copyright (C) 2021 Vidhu Kant Sharma + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import React, { useState } from 'react'; +import GamemodeChooser from './GamemodeChooser'; +import MessageBox from './MessageBox'; +import ScoreBoard from './ScoreBoard'; +import Grid from './Grid'; +import MultiplayerGrid from './MultiplayerGrid'; +import Footer from './Footer'; +import './style.css'; + +const App = () => { + const [gameStarted, setGameStarted] = useState(false); + const [multiplayer, setMultiplayer] = useState(false); + const [scoreX, setScoreX] = useState(0); + const [scoreO, setScoreO] = useState(0); + const [turn, setTurn] = useState(1); + const [showMessageBox, setShowMessageBox] = useState(false); + const [message, setMessage] = useState(""); + const [isHost, setIsHost] = useState(false); + + return gameStarted ? ( + <> + {showMessageBox && + <MessageBox + message={message} + setShowMessage={setShowMessageBox} + /> + } + + <ScoreBoard + turn={turn} + scoreX={scoreX} + scoreO={scoreO} + multiplayer={multiplayer} + isHost={isHost} + /> + + {multiplayer ? <MultiplayerGrid + turn={turn} + isHost={isHost} + setTurn={setTurn} + scoreX={scoreX} + setScoreX={setScoreX} + scoreO={scoreO} + setScoreO={setScoreO} + setMessage={setMessage} + setShowMessage={setShowMessageBox} + /> : <Grid + turn={turn} + setTurn={setTurn} + scoreX={scoreX} + setScoreX={setScoreX} + scoreO={scoreO} + setScoreO={setScoreO} + setMessage={setMessage} + setShowMessage={setShowMessageBox} + /> + } + + <Footer/> + </> + ) : ( + <> + <GamemodeChooser + setMultiplayer={setMultiplayer} + setGameStarted={setGameStarted} + setIsHost={setIsHost} + /> + <Footer/> + </> + ); +} + +export default App; diff --git a/src/components/App.tsx b/src/components/App.tsx deleted file mode 100644 index 6a9c60e..0000000 --- a/src/components/App.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Tic Tac Toe - Minimalistic Tic Tac Toe - * Copyright (C) 2021 Vidhu Kant Sharma - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -import React, { useState } from 'react'; -import MessageBox from './MessageBox'; -import ScoreBoard from './ScoreBoard'; -import Grid from './Grid'; -import Footer from './Footer'; -import './style.css'; - -const App: React.FC = () => { - const [scoreX, setScoreX] = useState<number>(0); - const [scoreO, setScoreO] = useState<number>(0); - const [turn, setTurn] = useState<number>(0); - const [showMessageBox, setShowMessageBox] = useState<boolean>(false); - const [message, setMessage] = useState<string>(""); - return ( - <> - {showMessageBox && <MessageBox message={message} setShowMessage={setShowMessageBox}/>} - <ScoreBoard turn={turn} scoreX={scoreX} scoreO={scoreO}/> - <Grid turn={turn} setTurn={setTurn} scoreX={scoreX} setScoreX={setScoreX} scoreO={scoreO} setScoreO={setScoreO} setMessage={setMessage} setShowMessage={setShowMessageBox}/> - <Footer/> - </> - ); -} - -export default App; diff --git a/src/components/Box.tsx b/src/components/Box.js index abe8b66..effd446 100644 --- a/src/components/Box.tsx +++ b/src/components/Box.js @@ -19,13 +19,7 @@ import React from 'react'; import './style.css'; -interface Props { - sign: number - index: number - setSign: (index: number) => void -} - -const Box: React.FC<Props> = (props) => { +const Box = (props) => { const handleClick = () => { props.sign === 2 && props.setSign(props.index) diff --git a/src/components/Footer.tsx b/src/components/Footer.js index 3bcaa2a..3ec1aea 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.js @@ -19,7 +19,7 @@ import React from 'react'; import './style.css'; -const Footer: React.FC = () => { +const Footer = () => { return ( <div className={"Footer"}> <p><a href="https://github.com/MikunoNaka/tic-tac-toe">Tic Tac Toe</a> Copyright (C) 2021 Vidhu Kant Sharma</p> diff --git a/src/components/GamemodeChooser.js b/src/components/GamemodeChooser.js new file mode 100644 index 0000000..91010e4 --- /dev/null +++ b/src/components/GamemodeChooser.js @@ -0,0 +1,63 @@ +/* + * Tic Tac Toe - Minimalistic Tic Tac Toe + * Copyright (C) 2021 Vidhu Kant Sharma + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import React, { useState } from 'react'; +import MultiplayerMenu from './MultiplayerMenu'; +import './style.css'; + +const GamemodeChooser = (props) => { + const [showMPMenu, setShowMPMenu] = useState(false); + + return ( + <div className={"GamemodeChooser"}> + {showMPMenu || + <> + <div className={"GamemodeButton"} onClick={() => { + props.setGameStarted(true); + props.setMultiplayer(false); + }}> + SINGLE PLAYER + </div> + + <div className={"GamemodeButton"} onClick={() => { + setShowMPMenu(true); + props.setMultiplayer(true); + }}> + MULTIPLAYER (beta) + </div> + </> + } + + {showMPMenu && + <> + <MultiplayerMenu + setIsHost={props.setIsHost} + setGameStarted={props.setGameStarted} + /> + + <div className={"GamemodeButton"} onClick={() => { + setShowMPMenu(false); + }}> + GO BACK + </div> + </> + } + </div> + ); +} + +export default GamemodeChooser; diff --git a/src/components/Grid.tsx b/src/components/Grid.js index 30bab11..289f142 100644 --- a/src/components/Grid.tsx +++ b/src/components/Grid.js @@ -16,49 +16,38 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import React, { useState, useEffect, Dispatch, SetStateAction } from 'react'; +import React, { useState, useEffect } from 'react'; import Box from './Box'; import './style.css'; -interface Props { - turn: number - setTurn: Dispatch<SetStateAction<number>> - scoreX: number - scoreO: number - setScoreX: Dispatch<SetStateAction<number>> - setScoreO: Dispatch<SetStateAction<number>> - setMessage: Dispatch<SetStateAction<string>> - setShowMessage: Dispatch<SetStateAction<boolean>> -} - -const Grid: React.FC<Props> = (props) => { +const Grid = (props) => { // 0 is O, 1 is X, 2 is blank - const [board, setBoard] = useState<number[]>([2,2,2,2,2,2,2,2,2]); + const [board, setBoard] = useState([2,2,2,2,2,2,2,2,2]); const turn = props.turn; - const getBoard = (index: number) => { + const getBoard = (index) => { setBoard(board.slice(0, index).concat(turn).concat(board.slice(index+1, 9))); props.setTurn(turn === 1 ? 0 : 1) } - const allEqual = (arr: number[]) => + const allEqual = (arr) => arr.includes(2) ? false : arr.every(i => i === arr[0]) - const getRow = (r: number) => board.slice(r * 3, (r * 3) + 3); - const getRows: any = (r: number = 0) => + const getRow = (r) => board.slice(r * 3, (r * 3) + 3); + const getRows = (r = 0) => r <= 2 ? [getRow(r)].concat(getRows(r+1)) : getRow(r) - const getCol = (arr: number[][], c: number) => arr.map((i: any) => i[c]) - const getCols: any = (rows: number[][], c: number = 0) => + const getCol = (arr, c) => arr.map((i) => i[c]) + const getCols = (rows, c = 0) => c < 2 ? [getCol(rows, c)].concat(getCols(rows, c+1)) : [getCol(rows, c)] - const getLeftDiagonal: any = (i: number = 0) => + const getLeftDiagonal = (i = 0) => i < 3 ? [getRow(i)[i]].concat(getLeftDiagonal(i+1)) : []; - const getRightDiagonal: any = (i: number = 2) => + const getRightDiagonal = (i = 2) => i >= 0 ? [getRow(2-i)[i]].concat(getRightDiagonal(i-1)) : []; - const endGame = (winner: number) => { + const endGame = (winner) => { const gameWinner = winner < 2 ? (winner === 1 ? "X" : "O") : "Draw"; props.setMessage(`WINNER: ${gameWinner}`); props.setShowMessage(true); @@ -72,21 +61,18 @@ const Grid: React.FC<Props> = (props) => { useEffect(() => { if (board.includes(0) || board.includes(1)) { const rows = getRows(); - (rows.some((i: number[]) => allEqual(i)) - || getCols(rows).some((i: number[]) => allEqual(i)) + (rows.some((i) => allEqual(i)) + || getCols(rows).some((i) => allEqual(i)) || [getLeftDiagonal(), getRightDiagonal()].some((i) => allEqual(i)) ) ? endGame(turn === 0 ? 1 : 0) : (board.includes(2) || endGame(2)); } }); return ( - <div className="Grid"> - { - board.map( - (i, index) => - <Box key={index} index={index} sign={i} setSign={getBoard}/> - ) - } + <div className="Grid"> + {board.map((i, index) => + <Box key={index} index={index} sign={i} setSign={getBoard}/> + )} </div> ); } diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.js index de65c48..6194f7b 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.js @@ -16,15 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import React, {Dispatch, SetStateAction} from 'react'; +import React from 'react'; import './style.css'; -interface Props { - message: string - setShowMessage: Dispatch<SetStateAction<boolean>> -} - -const MessageBox: React.FC<Props> = (props) => { +const MessageBox = (props) => { return ( <div className={"MessageBox"} onClick={() => props.setShowMessage(false)}> <p>{props.message}</p> diff --git a/src/components/MultiplayerGrid.js b/src/components/MultiplayerGrid.js new file mode 100644 index 0000000..88a8259 --- /dev/null +++ b/src/components/MultiplayerGrid.js @@ -0,0 +1,77 @@ +/* + * Tic Tac Toe - Minimalistic Tic Tac Toe + * Copyright (C) 2021 Vidhu Kant Sharma + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import React, { useState, useEffect } from 'react'; +import { io } from 'socket.io-client'; +import Box from './Box'; +import './style.css'; + +const socket = io("http://localhost:5000"); + +const MultiplayerGrid = (props) => { + // 0 is O, 1 is X, 2 is blank + const [board, setBoard] = useState([2,2,2,2,2,2,2,2,2]); + const turn = props.turn; + + const getBoard = (index) => { + // if it's not your turn you can't play + if ((turn === 0) === props.isHost) return; + + const newBoard = board.slice(0, index).concat(turn).concat(board.slice(index+1, 9)); + socket.emit("update-remote-data", { + board: newBoard, + turn: turn, + scoreX: props.scoreX, + scoreO: props.scoreO + }); + } + + const endGame = (data) => { + props.setMessage( + `${data.winner === "Data" ? "" : "WINNER: "}${data.winner}` + ); + props.setShowMessage(true); + props.setScoreX(data.scoreX); + props.setScoreO(data.scoreO); + + // socket.emit messes everything up + // and this does the job very well + setBoard([2,2,2,2,2,2,2,2,2]); + props.setTurn(data.winner === "Draw" ? turn : (data.winner === "X" ? 1 : 0)); + } + + useEffect(() => { + socket.on("update-client-data", (data) => { + setBoard(data.board); + props.setTurn(data.turn); + props.setScoreX(data.score.scoreX); + props.setScoreO(data.score.scoreO); + }); + socket.on("update-winner", (data) => endGame(data)); + }); + + return ( + <div className="Grid"> + {board.map((i, index) => + <Box key={index} index={index} sign={i} setSign={getBoard}/> + )} + </div> + ); +} + +export default MultiplayerGrid; diff --git a/src/components/MultiplayerMenu.js b/src/components/MultiplayerMenu.js new file mode 100644 index 0000000..1dfe9dd --- /dev/null +++ b/src/components/MultiplayerMenu.js @@ -0,0 +1,40 @@ +/* + * Tic Tac Toe - Minimalistic Tic Tac Toe + * Copyright (C) 2021 Vidhu Kant Sharma + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import React from 'react'; +import './style.css'; + +const MultiplayerMenu = (props) => { + return ( + <div className={"MultiplayerMenu"}> + <div className={"GamemodeButton"} onClick={() => { + props.setIsHost(true); + props.setGameStarted(true); + }}> + HOST GAME + </div> + + <div className={"GamemodeButton"} onClick={() => { + props.setGameStarted(true); + }}> + JOIN GAME + </div> + </div> + ); +} + +export default MultiplayerMenu; diff --git a/src/components/ScoreBoard.tsx b/src/components/ScoreBoard.js index b50af30..d393285 100644 --- a/src/components/ScoreBoard.tsx +++ b/src/components/ScoreBoard.js @@ -19,20 +19,19 @@ import React from 'react'; import './style.css'; -interface Props { - scoreX: number - scoreO: number - turn: number -} - -const ScoreBoard: React.FC<Props> = (props) => { +const ScoreBoard = (props) => { return ( <div className={"ScoreBoardContainer"}> <div className={"ScoreBoard"}> <span>X: {props.scoreX}</span> <span>O: {props.scoreO}</span> </div> - <div className={"turnMessage"}>-- {props.turn === 0 ? "O" : "X"}'s turn --</div> + <div className={"turnMessage"}> + -- + {` ${props.turn === 0 ? "O" : "X"}'s turn `} + {props.multiplayer && ` (You are ${props.isHost ? "'X'" : "'O'"}) `} + -- + </div> </div> ); } diff --git a/src/components/style.css b/src/components/style.css index d33d775..79ef49e 100644 --- a/src/components/style.css +++ b/src/components/style.css @@ -16,18 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -/*body, #root { - margin: 0px; - height: 100vh; - width: 100vw; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: #232627; - color: white; -}*/ - body { margin: 0; padding: 0; @@ -42,6 +30,24 @@ body { left: 0; right: 0; } +.GamemodeChooser { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 90%; + width: 100%; +} + +.GamemodeChooser .GamemodeButton { + border: 1px solid #5b76b7; + text-align: center; + font-size: 1.5rem; + padding: 0.5rem 0; + width: 15rem; + margin: 1rem; +} + .MessageBox { font-size: 7rem; display: flex; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..463687c --- /dev/null +++ b/src/index.js @@ -0,0 +1,4 @@ +import ReactDOM from 'react-dom'; +import App from './components/App'; + +ReactDOM.render(<App />, document.getElementById('root')); diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 6ca3f59..0000000 --- a/src/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './components/App'; -import reportWebVitals from './reportWebVitals'; - -ReactDOM.render( - <React.StrictMode> - <App /> - </React.StrictMode>, - document.getElementById('root') -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 49a2a16..0000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from 'web-vitals'; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/server/index.js b/src/server/index.js index 2b85ea5..2799805 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,11 +1,68 @@ const express = require('express'); -const path = require('path'); const app = express(); +const http = require('http').Server(app); +const path = require('path'); + +// const io = require('socket.io')(http); +const io = require("socket.io")(http, { + cors: { + origin: ["http://localhost:5000", "http://localhost:3000"], + "Access-Control-Allow-Origin": "*", + methods: ["GET", "POST"] + } +}); + +const allEqual = (arr) => + arr.includes(2) ? false : arr.every(i => i === arr[0]) + +const getRow = (board, r) => board.slice(r * 3, (r * 3) + 3); +const getRows = (board, r = 0) => + r <= 2 ? [getRow(board, r)].concat(getRows(board, r+1)) : getRow(board, r) + +const getCol = (board, c) => board.map((i) => i[c]) +const getCols = (rows, c = 0) => + c < 2 ? [getCol(rows, c)].concat(getCols(rows, c+1)) : [getCol(rows, c)] + +const getLeftDiagonal = (board, i = 0) => + i < 3 ? [getRow(board, i)[i]].concat(getLeftDiagonal(board, i+1)) : []; + +const getRightDiagonal = (board, i = 2) => + i >= 0 ? [getRow(board, 2-i)[i]].concat(getRightDiagonal(board, i-1)) : []; + +const getScore = (winner, scoreX, scoreO, board) => ({ + winner: winner < 2 + ? (winner === 0 ? 'O' : 'X') + : (board.includes(2) ? undefined : 'Draw'), + scoreX: winner === 1 ? scoreX + 1 : scoreX, + scoreO: winner === 0 ? scoreO + 1 : scoreO +}) + +io.on('connection', (socket) => { + socket.on('update-remote-data', (data) => { + if (data.board.includes(0) || data.board.includes(1)) { + const rows = getRows(data.board); + const winner = (rows.some((i) => allEqual(i)) + || getCols(rows).some((i) => allEqual(i)) + || [getLeftDiagonal(data.board), getRightDiagonal(data.board)].some((i) => allEqual(i)) + ) ? data.turn : 2; + + const score = getScore(winner, data.scoreX, data.scoreO, data.board) + io.emit('update-client-data', { + board: data.board, + turn: score.winner ? data.turn : (data.turn === 0 ? 1 : 0), + score: score + }); + + score.winner && io.emit('update-winner', score) + }; + }); +}); +// serve static front end app.use(express.static(path.join(__dirname, '../../build'))); // Start the server const PORT = process.env.PORT || 5000; -app.listen(PORT, () => { +http.listen(PORT, () => { console.log(`App listening on port ${PORT}`); }); |