Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 6x6 and 9x9 modes to the game (#49) #53

Merged
merged 25 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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