0% found this document useful (0 votes)
1 views

source code

The document outlines a React application for a Tic Tac Toe game, featuring a main menu for selecting game modes (local multiplayer or AI). It includes components for the game board, game state management, and score tracking, with functionality for handling player moves, AI moves, and game results. The application also provides a user interface with buttons for starting new games and resetting scores.

Uploaded by

victor10228y
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views

source code

The document outlines a React application for a Tic Tac Toe game, featuring a main menu for selecting game modes (local multiplayer or AI). It includes components for the game board, game state management, and score tracking, with functionality for handling player moves, AI moves, and game results. The application also provides a user interface with buttons for starting new games and resetting scores.

Uploaded by

victor10228y
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 8

ink } from 'react-router-dom';

import { Button } from '@/components/ui/button';


import { GameMode } from '../types/game';

interface MainMenuProps {
onSelectGameMode: (mode: GameMode) => void;
}

const MainMenu: React.FC<MainMenuProps> = ({ onSelectGameMode }) => {


return (
<div className="flex flex-col items-center gap-6 py-8">
<h1 className="text-4xl font-bold text-white">Tic Tac Toe</h1>
<p className="text-gray-300 mb-6">Choose a game mode to start playing</p>

<div className="flex flex-col gap-4 w-full max-w-xs">


<Button
onClick={() => onSelectGameMode('local')}
size="lg"
className="w-full bg-blue-600 hover:bg-blue-700"
>
Local Multiplayer
</Button>

<Button
onClick={() => onSelectGameMode('ai')}
size="lg"
className="w-full bg-orange-600 hover:bg-orange-700"
>
Play Against AI
</Button>
</div>
</div>
);
};

export default MainMenu;

export type CellValue = 'X' | 'O' | null;


export type BoardState = CellValue[];
export type GameMode = 'local' | 'ai' | null;

export interface GameStatus {


xScore: number;
oScore: number;
drawScore: number;
}

import React, { useState } from 'react';


import GameBoard from './GameBoard';
import MainMenu from './MainMenu';
import { Button } from '@/components/ui/button';
import { GameMode, GameStatus } from '../types/game';
import { ChevronLeft } from 'lucide-react';

const GameContainer = () => {


const [gameMode, setGameMode] = useState<GameMode>(null);
const [gameStatus, setGameStatus] = useState<GameStatus>({
xScore: 0,
oScore: 0,
drawScore: 0
});

const handleSelectGameMode = (mode: GameMode) => {


setGameMode(mode);
};

const handleBackToMenu = () => {


setGameMode(null);
};

const handleGameScoreUpdate = (newStatus: GameStatus) => {


setGameStatus(newStatus);
};

return (
<div className="min-h-screen flex flex-col items-center justify-center bg-
gradient-to-b from-gray-900 to-gray-800 p-4">
<h1 className="text-4xl font-bold mb-2 text-white">Tic Tac Toe</h1>
<p className="text-gray-300 mb-8">Classic game with a modern twist</p>

<div className="bg-white/10 backdrop-blur-md rounded-xl shadow-xl p-6 md:p-


8">
{gameMode === null ? (
<MainMenu onSelectGameMode={handleSelectGameMode} />
) : (
<div className="flex flex-col items-center">
<div className="self-start mb-4">
<Button
variant="outline"
size="sm"
className="text-gray-300 border-gray-700"
onClick={handleBackToMenu}
>
<ChevronLeft className="mr-1 h-4 w-4" />
Back to Menu
</Button>
</div>
<GameBoard
gameMode={gameMode}
onScoreUpdate={handleGameScoreUpdate}
xScore={gameStatus.xScore}
oScore={gameStatus.oScore}
drawScore={gameStatus.drawScore}
/>
</div>
)}
</div>

<footer className="mt-12 text-gray-400 text-sm">


<p>© {new Date().getFullYear()} Tic Tac Toe | Made with ♥</p>
</footer>
</div>
);
};

export default GameContainer;


import React, { useState, useEffect } from 'react';
import { X, Circle } from 'lucide-react';
import { toast } from 'sonner';
import { CellValue, BoardState, GameMode, GameStatus } from '../types/game';

// Winner patterns - indexes of winning combinations


const WINNING_PATTERNS = [
[0, 1, 2], // top row
[3, 4, 5], // middle row
[6, 7, 8], // bottom row
[0, 3, 6], // left column
[1, 4, 7], // middle column
[2, 5, 8], // right column
[0, 4, 8], // diagonal top-left to bottom-right
[2, 4, 6], // diagonal top-right to bottom-left
];

interface GameBoardProps {
gameMode: GameMode;
xScore: number;
oScore: number;
drawScore: number;
onScoreUpdate: (scores: GameStatus) => void;
}

const GameBoard: React.FC<GameBoardProps> = ({


gameMode,
xScore,
oScore,
drawScore,
onScoreUpdate
}) => {
// Game state
const [board, setBoard] = useState<BoardState>(Array(9).fill(null));
const [isXNext, setIsXNext] = useState<boolean>(true);
const [winner, setWinner] = useState<CellValue>(null);
const [winningCombination, setWinningCombination] = useState<number[] |
null>(null);
const [gameOver, setGameOver] = useState<boolean>(false);

// Check for winner


const checkWinner = (boardState: BoardState): CellValue => {
for (const pattern of WINNING_PATTERNS) {
const [a, b, c] = pattern;
if (
boardState[a] &&
boardState[a] === boardState[b] &&
boardState[a] === boardState[c]
) {
setWinningCombination(pattern);
return boardState[a];
}
}

// Check for draw


if (boardState.every((cell) => cell !== null)) {
return 'draw';
}

return null;
};

// Get available moves (empty cells)


const getAvailableMoves = (boardState: BoardState): number[] => {
return boardState
.map((cell, index) => (cell === null ? index : -1))
.filter(index => index !== -1);
};

// AI move - simple implementation


const makeAIMove = (boardState: BoardState): number => {
const availableMoves = getAvailableMoves(boardState);

// Check for winning move


for (const index of availableMoves) {
const testBoard = [...boardState];
testBoard[index] = 'O';

if (checkWinner(testBoard) === 'O') {


return index;
}
}

// Block player's winning move


for (const index of availableMoves) {
const testBoard = [...boardState];
testBoard[index] = 'X';

if (checkWinner(testBoard) === 'X') {


return index;
}
}

// Take center if available


if (availableMoves.includes(4)) {
return 4;
}

// Take a random move


const randomIndex = Math.floor(Math.random() * availableMoves.length);
return availableMoves[randomIndex];
};

// Handle cell click


const handleCellClick = (index: number): void => {
// If cell is already filled or game is over, don't do anything
if (board[index] || winner || gameOver) return;

// Update board
const newBoard = [...board];
newBoard[index] = isXNext ? 'X' : 'O';
setBoard(newBoard);

// Check for winner after player's move


const result = checkWinner(newBoard);
if (result) {
handleGameResult(result);
return;
}

setIsXNext(!isXNext);
};

// Effect for AI's turn


useEffect(() => {
// If it's AI's turn (O) in AI mode
if (gameMode === 'ai' && !isXNext && !winner && !gameOver) {
const timer = setTimeout(() => {
const aiMoveIndex = makeAIMove(board);

const newBoard = [...board];


newBoard[aiMoveIndex] = 'O';
setBoard(newBoard);

// Check if AI won
const result = checkWinner(newBoard);
if (result) {
handleGameResult(result);
return;
}

setIsXNext(true);
}, 600); // Small delay for better UX

return () => clearTimeout(timer);


}
}, [board, isXNext, winner, gameOver, gameMode]);

// Handle game result


const handleGameResult = (result: CellValue | 'draw'): void => {
if (result === 'draw') {
setGameOver(true);
const newScores = {
xScore,
oScore,
drawScore: drawScore + 1
};
onScoreUpdate(newScores);
toast("It's a draw!");
} else {
setWinner(result);
setGameOver(true);

const newScores = {
xScore: result === 'X' ? xScore + 1 : xScore,
oScore: result === 'O' ? oScore + 1 : oScore,
drawScore
};
onScoreUpdate(newScores);

if (result === 'X') {


toast("Player X wins!");
} else if (result === 'O') {
toast(gameMode === 'ai' ? "AI wins!" : "Player O wins!");
}
}
};

// Reset game
const resetGame = (): void => {
setBoard(Array(9).fill(null));
setIsXNext(true);
setWinner(null);
setWinningCombination(null);
setGameOver(false);
};

// Reset scores
const resetScores = (): void => {
onScoreUpdate({
xScore: 0,
oScore: 0,
drawScore: 0
});
resetGame();
};

// Render cell content


const renderCell = (index: number): JSX.Element => {
const value = board[index];
const isWinningCell = winningCombination?.includes(index);

return (
<div
className={`flex items-center justify-center w-full h-full transition-
colors ${
value
? "cursor-default"
: "cursor-pointer hover:bg-game-cell-hover"
} ${isWinningCell ? "bg-opacity-80 animate-pulse-soft" : ""}`}
onClick={() => handleCellClick(index)}
>
{value === "X" && (
<X
className={`text-game-x animate-mark-cell w-12 h-12 sm:w-16 sm:h-16 ${
isWinningCell ? "drop-shadow-lg" : ""
}`}
strokeWidth={3}
/>
)}
{value === "O" && (
<Circle
className={`text-game-o animate-mark-cell w-12 h-12 sm:w-16 sm:h-16 ${
isWinningCell ? "drop-shadow-lg" : ""
}`}
strokeWidth={3}
/>
)}
</div>
);
};

return (
<div className="flex flex-col items-center gap-6">
{/* Game status */}
<div className="flex justify-between w-full max-w-xs sm:max-w-md">
<div className="text-game-x font-bold">
{gameMode === 'ai' ? 'You' : 'X'}: {xScore}
</div>
<div className="text-gray-500 font-bold">
Draws: {drawScore}
</div>
<div className="text-game-o font-bold">
{gameMode === 'ai' ? 'AI' : 'O'}: {oScore}
</div>
</div>

{/* Current turn */}


<div className="text-lg mb-2">
{!winner && !gameOver && (
<div className="flex items-center gap-2 animate-pulse-soft">
Turn:
{isXNext ? (
<X className="text-game-x" />
) : (
<Circle className="text-game-o" />
)}
{gameMode === 'ai' && !isXNext && (
<span className="text-game-o ml-1">AI thinking...</span>
)}
</div>
)}
</div>

{/* Game board */}


<div className="grid grid-cols-3 gap-2 bg-game-board p-3 rounded-lg shadow-
lg">
{Array(9).fill(null).map((_, index) => (
<div
key={index}
className="w-20 h-20 sm:w-24 sm:h-24 bg-game-cell rounded-md shadow-
inner"
>
{renderCell(index)}
</div>
))}
</div>

{/* Game controls */}


<div className="flex gap-4 mt-4">
<button
onClick={resetGame}
className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium
rounded-md transition-colors"
>
New Game
</button>
<button
onClick={resetScores}
className="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium
rounded-md transition-colors"
>
Reset Scores
</button>
</div>
</div>
);
};

export default GameBoard;

You might also like