Skip to content

Commit

Permalink
feat: implement 6x6 and 9x9 modes to the game #49 (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
amirmasud111 authored Oct 16, 2023
1 parent d682ed7 commit 95d7a36
Show file tree
Hide file tree
Showing 32 changed files with 737 additions and 234 deletions.
5 changes: 5 additions & 0 deletions React/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/static/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Joti+One&family=Open+Sans:ital,wght@0,600;0,700;0,800;1,700&display=swap"
rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>dooz (Tic Tac Toe) React</title>
</head>
Expand Down
21 changes: 18 additions & 3 deletions React/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { createContext, useState } from "react";
import classes from "./App.module.scss";
import { AppRouter } from "./AppRouter";

interface IContextValue {
selectedSize: number;
setSelectedSize: React.Dispatch<React.SetStateAction<number>>
}

export const boardSizeContext = createContext<IContextValue>({ selectedSize: 3, setSelectedSize: () => { } });

function App() {
const [selectedSize, setSelectedSize] = useState(3);
const contextValue = {
selectedSize,
setSelectedSize
}
return (
<div className={classes.main}>
<AppRouter />
</div>
<boardSizeContext.Provider value={contextValue}>
<div className={classes.main}>
<AppRouter />
</div>
</boardSizeContext.Provider>
);
}

Expand Down
9 changes: 0 additions & 9 deletions React/src/app/pages/game/PlayerVsBotPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@
background-repeat: no-repeat;
}

.header {
display: flex;
gap: 16px;
margin-top: 36px;
align-items: center;
font-family: Arial, Helvetica, sans-serif;
color: white;
}

.body {
display: flex;
justify-content: center;
Expand Down
73 changes: 37 additions & 36 deletions React/src/app/pages/game/PlayerVsBotPage.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect, useContext } from "react";
import { checkBoard } from "~/utils/check-board";
import { P1, P2, generateRandomTurn } from "~/utils/players";
import { Footer } from "./components/Footer";
import { Header } from "./components/Header";
import { GamePageHeader } from "./components/Header";
import { ResultModal } from "./components/Modal/ResultModal";
import { GameMode } from "~/utils/game-mode";
import classes from "./PlayerVsBotPage.module.scss";
import { anyMovesLeft } from "~/utils/any-moves-left";
import { findBestBotMove } from "~/utils/find-best-bot-move";
import { Board } from "./components/Board";
const INITIAL_BOARD = Array(3)
.fill(null)
.map(() => Array(3).fill(null));
import { boardSizeContext } from "~/App";
import { createBoard } from "~/utils/create-board";

export function PlayerVsBotPage() {
const [boardData, setBoardData] = useState(structuredClone(INITIAL_BOARD));
const { selectedSize } = useContext(boardSizeContext);
const [boardData, setBoardData] = useState(createBoard(selectedSize));
const [currentPlayer, setCurrentPlayer] = useState(generateRandomTurn());
const [winResult, setWinResult] = useState<number[][]>();
const [showModal, setShowModal] = useState(false);
const isFirstMove = useRef(true);

useEffect(() => {
if (isFirstMove.current && currentPlayer === P2) {
if (!isFirstMove.current) {
refreshBoard();
}
}, [selectedSize])

useEffect(() => {
if (currentPlayer === P2 && !winResult) {
handleBot();
}
isFirstMove.current = false;
}, [isFirstMove.current])
}, [currentPlayer]);



const handleClick = (i: number, j: number) => {
if (boardData[i][j] || winResult) return;
Expand All @@ -38,22 +46,22 @@ export function PlayerVsBotPage() {
return;
}
setCurrentPlayer(P2);
handleBot();
}
};

const handleBot = () => {

let movement = findBestBotMove(boardData)
if (movement) {
boardData[movement.i][movement.j] = P2;
}
setBoardData([...boardData]);
if (checkWinner(P2)) return;
if (anyMovesLeft(boardData)) {
setShowModal(true);
}
setCurrentPlayer(P1);
const handleBot = async () => {
setTimeout(() => {
let bestBotMove = findBestBotMove(boardData);
if (bestBotMove) {
boardData[bestBotMove.i][bestBotMove.j] = P2;
}
setBoardData([...boardData]);
if (checkWinner(P2)) return;
if (anyMovesLeft(boardData)) {
setShowModal(true);
}
setCurrentPlayer(P1);
}, 500);
};

const checkWinner = (currentPlayer: string) => {
Expand All @@ -65,25 +73,20 @@ export function PlayerVsBotPage() {
};

const refreshBoard = () => {
let newRandomTurnGenerator = generateRandomTurn();
setBoardData(structuredClone(INITIAL_BOARD));
setCurrentPlayer(newRandomTurnGenerator);
if (newRandomTurnGenerator === P2) {
isFirstMove.current = true;
}
setBoardData(createBoard(selectedSize));
let cp = generateRandomTurn();
setCurrentPlayer(cp);
isFirstMove.current = true;
setWinResult(undefined);
setShowModal(false);
}

return (
<div className={classes.page}>
<div className={classes.header}>
<Header
currentPlayer={currentPlayer}
gameMode={GameMode.playerVsBot}
/>
</div>

<GamePageHeader
currentPlayer={currentPlayer}
gameMode={GameMode.playerVsBot}
/>
<div className={classes.body}>
<Board
boardData={boardData}
Expand All @@ -92,13 +95,11 @@ export function PlayerVsBotPage() {
result={winResult}
/>
</div>

{showModal && <ResultModal
winner={winResult && currentPlayer}
onRefresh={refreshBoard}
gameMode={GameMode.playerVsBot}
/>}

<div className={classes.footer}>
<Footer onRefresh={refreshBoard} />
</div>
Expand Down
9 changes: 0 additions & 9 deletions React/src/app/pages/game/PlayerVsPlayerPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@
background-repeat: no-repeat;
}

.header {
display: flex;
gap: 16px;
margin-top: 36px;
align-items: center;
font-family: Arial, Helvetica, sans-serif;
color: white;
}

.body {
display: flex;
justify-content: center;
Expand Down
34 changes: 16 additions & 18 deletions React/src/app/pages/game/PlayerVsPlayerPage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { useState } from "react";
import { useContext, useEffect, useState } from "react";
import { checkBoard } from "~/utils/check-board";
import { P1, P2, generateRandomTurn } from "~/utils/players";
import { Board } from "./components/Board";
import { Footer } from "./components/Footer";
import { Header } from "./components/Header";
import { GamePageHeader } from "./components/Header";
import { ResultModal } from "./components/Modal/ResultModal";
import { GameMode } from "~/utils/game-mode";
import classes from "./PlayerVsPlayerPage.module.scss";
import { anyMovesLeft } from "~/utils/any-moves-left";

const INITIAL_BOARD = Array(3)
.fill(null)
.map(() => Array(3).fill(null));
import { boardSizeContext } from "~/App";
import { createBoard } from "~/utils/create-board";

export function PlayerVsPlayerPage() {
const [boardData, setBoardData] = useState(structuredClone(INITIAL_BOARD));
const { selectedSize } = useContext(boardSizeContext);
const [boardData, setBoardData] = useState(createBoard(selectedSize));
const [currentPlayer, setCurrentPlayer] = useState(generateRandomTurn());
const [winResult, setWinResult] = useState<number[][]>();
const [showModal, setShowModal] = useState(false);

useEffect(() => {
refreshBoard();
}, [selectedSize]);

const handleClick = (i: number, j: number) => {
if (boardData[i][j] || winResult) return;
boardData[i][j] = currentPlayer;
Expand All @@ -36,27 +39,24 @@ export function PlayerVsPlayerPage() {
const checkWinner = () => {
const result = checkBoard(boardData, currentPlayer);
if (!result) return false;
setWinResult(winResult);
setWinResult(result);
setShowModal(true);
return true;
};

const refreshBoard = () => {
setBoardData(structuredClone(INITIAL_BOARD));
setBoardData(createBoard(selectedSize));
setCurrentPlayer(generateRandomTurn());
setWinResult(undefined);
setShowModal(false);
}

return (
<div className={classes.page}>
<div className={classes.header}>
<Header
currentPlayer={currentPlayer}
gameMode={GameMode.playerVsPlayerLocal}
/>
</div>

<GamePageHeader
currentPlayer={currentPlayer}
gameMode={GameMode.playerVsPlayerLocal}
/>
<div className={classes.body}>
<Board
boardData={boardData}
Expand All @@ -65,13 +65,11 @@ export function PlayerVsPlayerPage() {
result={winResult}
/>
</div>

{showModal && <ResultModal
winner={winResult && currentPlayer}
onRefresh={refreshBoard}
gameMode={GameMode.playerVsPlayerLocal}
/>}

<div className={classes.footer}>
<Footer onRefresh={refreshBoard} />
</div>
Expand Down
39 changes: 17 additions & 22 deletions React/src/app/pages/game/components/Board/Board.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,54 @@
height: 312px;
border: 1px solid #5570fd;
backdrop-filter: blur(3px);
border-radius: 64px;
padding: 12px;
}

.container {
.template {
display: flex;
flex-direction: column;
background-color: #191685;
border-radius: 56px;
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}

.container {
display: flex;
flex-direction: column;
height: 100%;
}

.row {
display: flex;
height: 100%;
}

.cell {
border: 1px dashed #d9d9d9;
height: 96px;
border-top: 1px dashed #d9d9d9;
border-right: 1px dashed #d9d9d9;
height: 100%;
display: flex;
flex-basis: 96px;
flex-basis: 100%;
justify-content: center;
align-items: center;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}

.row:first-child :first-child,
.row:first-child :last-child {
border: none;
}

.row:last-child :first-child,
.row:last-child :last-child {
border: none;
}

.row:first-child :nth-child(2),
.row:last-child :nth-child(2) {
.container .row:first-child .cell {
border-top: none;
border-bottom: none;
}

.row:nth-child(2) :first-child,
.row:nth-child(2) :last-child {
border-left: none;
.container .row :last-child {
border-right: none;
}

.cell img {
width: 70%;
}

.winLine {
border: 1px solid inherit;
width: 4px;
Expand Down
16 changes: 12 additions & 4 deletions React/src/app/pages/game/components/Board/Board.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { winLineStyleMaker } from "~/utils/winline-style-maker";
import classes from "./Board.module.scss";
import { useEffect, useState } from "react";
import { getBoardRadius } from "~/utils/getBoardRadius";

interface Props {
boardData: string[][];
Expand All @@ -10,11 +12,17 @@ interface Props {

export function Board({ boardData, onClick, currentPlayer, result }: Props) {

const winLineStyle = winLineStyleMaker(currentPlayer, result);
const winLineStyle = winLineStyleMaker(currentPlayer, boardData.length, result);
const [boardRadius, setBoardRadius] = useState(getBoardRadius(boardData.length));

useEffect(() => {
setBoardRadius(getBoardRadius(boardData.length));
}, [boardData.length]);

return (
<div className={classes.main}>
<div className={classes.container}>
<div>
<div className={classes.main} style={{ borderRadius: boardRadius.mainRadius }}>
<div className={classes.template} style={{ borderRadius: boardRadius.templateRadius }}>
<div className={classes.container}>
{boardData.map((row, i) => (
<div key={`row-${i}`} className={classes.row}>
{row.map((col, j) => (
Expand Down
Loading

0 comments on commit 95d7a36

Please sign in to comment.