Skip to content
Snippets Groups Projects
Commit 6db7f17a authored by ='s avatar =
Browse files

first commit

parents
No related branches found
No related tags found
No related merge requests found
Showing with 739 additions and 0 deletions
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
version: "3.9"
services:
dev:
build:
context: .
tty: true
\ No newline at end of file
FROM node
WORKDIR /app
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/game-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Tic-Tac-Toe</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
This diff is collapsed.
{
"name": "react-essentials-deep-dive-adv",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.5"
}
}
public/bg-pattern-dark.png

13.7 KiB

public/bg-pattern.png

12.8 KiB

public/game-logo.png

219 KiB

import ListItem from "./components/ListItem.jsx";
import GameBoard from "./components/GameBoard.jsx";
import gameLogoImage from "../public/game-logo.png";
import Log from "./components/Log.jsx";
import GameOver from "./components/GameOver.jsx";
import { useState } from "react";
function App() {
const [turns, setTurns] = useState([]);
const [winner, setWinner] = useState(null);
const [draw, setDraw] = useState(false);
const [playerName, setPlayerName] = useState({ X: "player1", O: "player2" });
function handelSelectField(row, col) {
setTurns((prevTurns) => {
let symbol = "X";
if (prevTurns.length > 0 && prevTurns[0]["symbol"] == "X") {
symbol = "O";
}
let newTurns = [{ row: row, col: col, symbol: symbol }, ...prevTurns];
return newTurns;
});
}
function getTurn() {
return turns.length % 2 === 0;
}
return (
<>
<header>
<h1>Tic-Tac-Toe</h1>
<img src={gameLogoImage} alt="game Logo" />
</header>
<main>
<div id="game-container">
<ol id="players" className="player highlight-player">
<ListItem
playerName="player1"
playerSymbol="X"
isMyTurn={getTurn()}
setPlayerName={setPlayerName}
></ListItem>
<ListItem
playerName="player2"
playerSymbol="O"
isMyTurn={!getTurn()}
setPlayerName={setPlayerName}
></ListItem>
</ol>
<GameBoard
turns={turns}
handelSelectField={(row, col) => {
handelSelectField(row, col);
}}
setWinner={setWinner}
setDraw={setDraw}
></GameBoard>
{(winner || draw) && (
<GameOver
winner={playerName[winner]}
resetBoard={setTurns}
resetWinner={setWinner}
draw ={draw}
setDraw = {setDraw}
></GameOver>
)}
</div>
<Log turns={turns}></Log>
</main>
</>
);
}
export default App;
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file
import { useEffect } from "react";
import SymbolFeld from "./SymbolFeld.jsx";
import { whoWins } from "../logic/winningLogic.js";
import { isDraw } from "../logic/winningLogic.js";
export default function GameBoard({turns, handelSelectField ,setWinner,setDraw}) {
const gameBoard = [
[null, null, null],
[null, null, null],
[null, null, null],
];
function computeBoard () {
for (let turn of turns) {
const { row , col , symbol } = turn ;
gameBoard[row][col] = symbol;
}
}
computeBoard () ;
useEffect(() => {
let winner = whoWins(gameBoard);
if (winner) {
setWinner((oldValue) => winner);
return ;
}
if (isDraw(gameBoard)){
setDraw((oldValue)=>{
return true ;
})
}
}, [turns]);
return (
<ol id="game-board">
{gameBoard.map((row, rowIndex) => {
return (
<li key={rowIndex}>
<ol>
{row.map((Symbol, colIndex) => {
return (
<li key={`${rowIndex} ${colIndex}`}>
<SymbolFeld
gameBoard = {gameBoard}
symbol = {Symbol}
row={rowIndex}
col={colIndex}
handelSelectField = {(row,col)=>{handelSelectField(row,col)}}
/>
</li>
);
})}
</ol>
</li>
);
})}
</ol>
);
}
export default function GameOver ({winner, resetBoard ,resetWinner,draw,setDraw}) {
function handelClick(){
resetBoard((turns)=>{
return [] ;
})
resetWinner((oldWinner)=>{
return null ;
})
setDraw ((oldValue)=>{
return false ;
})
}
let gameResult = draw ? <p>it's draw </p> : <p> the winner is {winner}</p>
return (
<div id="game-over">
<h2>Game Over </h2>
{gameResult}
<button onClick={handelClick}> Rematch </button>
</div>
) ;
}
\ No newline at end of file
import { useState } from "react";
export default function ListItem({ playerName, playerSymbol , isMyTurn, setPlayerName }) {
const [isEditing, setIsEditing] = useState(false);
const [changedName, setChangedName] = useState(playerName);
let element = isEditing ? (
<input value={changedName} onChange={handelChange}></input>
) : (
<span className="player-name">{changedName}</span>
);
let buttonValue = isEditing ? "Save" : "Edit";
function handelClick(event) {
setIsEditing((lastValue) => {
return !lastValue;
});
setPlayerName((oldValue)=>{
if(playerSymbol==="X"){
oldValue.X = changedName ;
}else{
oldValue.O = changedName ;
}
return oldValue ;
});
}
function handelChange(event) {
let newValue = event.target.value ;
setChangedName(newValue);
}
return (
<li className= {isMyTurn? "active" : ""}>
<span className="player-info">
{element}
<span className="player-symbol">{playerSymbol}</span>
</span>
<button onClick={handelClick}>{buttonValue}</button>
</li>
);
}
export default function Log({turns}) {
return (
<ol id="log">
{turns.map((value,index)=>{
const {row , col , symbol} = value ;
return <li key={index}>{symbol} hat used the {`${row},${col}`}</li>;
})}
</ol>
);
}
export default function SymbolFeld({ row, col, handelSelectField ,symbol,gameBoard}) {
function handelClick(event) {
handelSelectField(row, col);
if (gameBoard[row][col]){
event.target.disabled = true;
}else {
event.target.disabled = false;
}
}
return <button onClick={handelClick}>{symbol}</button>;
}
@import url('https://fonts.googleapis.com/css2?family=Caprasimo&family=Roboto+Slab:wght@400;700&display=swap');
* {
box-sizing: border-box;
}
html {
font-family: 'Roboto Slab', sans-serif;
line-height: 1.5;
color: #ebe7ef;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
background: radial-gradient(
circle at top,
rgba(241, 210, 70, 0.98),
rgba(250, 176, 103, 0.87)
),
url('bg-pattern-dark.png');
background-repeat: repeat;
background-size: 100% 100%, 30% 30%, 100% 100%;
min-height: 110rem;
}
header {
text-align: center;
}
header img {
width: 8rem;
object-fit: contain;
margin: 3rem auto 1rem auto;
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.4));
}
h1 {
font-family: 'Caprasimo', cursive;
font-size: 3rem;
margin: 0 auto 3rem auto;
color: #3f3b00;
}
#game-container {
max-width: 45rem;
margin: 3rem auto;
padding: 2rem;
border-radius: 6px;
background: linear-gradient(#383624, #282617);
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
position: relative;
}
#players {
list-style: none;
padding: 0;
margin: 1rem 0;
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
}
#players li {
display: flex;
align-items: center;
width: 50%;
border: 2px solid transparent;
}
#players.highlight-player li.active {
border-color: #f6e35a;
animation: pulse 2s infinite ease-in-out;
}
#players.highlight-player li.active .player-name,
#players.highlight-player li.active .player-symbol {
color: #f6e35a;
}
#players button {
width: 3rem;
border: none;
background: none;
color: #c3ba78;
font-size: 0.9rem;
cursor: pointer;
transition: color 0.2s;
padding: 0.5rem 0.25rem 0.25rem 0.25rem;
text-align: center;
}
#players button:hover {
color: #f8ca31;
}
.player {
border: 2px solid transparent;
padding: 0.5rem;
border-radius: 4px;
font-weight: bold;
}
.player-name {
display: inline-block;
width: 10rem;
font-size: 1rem;
color: #e1dec7;
text-transform: uppercase;
margin: 0;
padding: 0.5rem;
border-radius: 4px;
text-overflow: ellipsis;
text-align: center;
}
.player input {
font: inherit;
font-size: 1rem;
width: 10rem;
border: none;
padding: 0.5rem;
animation: pulse-text 2s infinite;
background-color: #46432f;
text-align: center;
text-transform: uppercase;
}
.player-symbol {
margin-left: 1rem;
font-size: 1rem;
color: #e1dec7;
}
ol {
list-style: none;
margin: 0;
padding: 0;
}
#pre-game {
text-align: center;
}
#pre-game button {
cursor: pointer;
background: none;
color: #f8c031;
border: none;
font-family: 'Caprasimo', cursive;
font-size: 4rem;
text-shadow: 0 0 12px rgba(0, 0, 0, 0.7);
animation: pulse-text-size 2s infinite ease-out;
}
#game-board {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
margin: 3rem 0;
padding: 0;
flex-direction: column;
}
#game-board ol {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
margin: 0;
padding: 0;
}
#game-board button {
width: 8rem;
height: 8rem;
border: none;
background: #aca788;
color: #3f3b00;
font-size: 5rem;
cursor: pointer;
font-family: 'Caprasimo', cursive;
padding: 1rem;
}
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgba(40, 38, 23, 0.95);
animation: pop-in 0.5s cubic-bezier(0.68, -0.55, 0.65, 0.52) forwards;
}
#game-over h2 {
font-family: 'Caprasimo', cursive;
font-size: 4rem;
text-align: center;
color: #fcd256;
margin: 0;
}
#game-over p {
font-size: 2rem;
text-align: center;
color: #e1dec7;
}
#game-over button {
display: block;
margin: 0 auto;
font-size: 1.5rem;
background: none;
border: 2px solid #fcd256;
color: #fcd256;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s, color 0.2s;
box-shadow: 0 0 8px rgba(255, 187, 0, 0.4);
}
#game-over button:hover {
background: #fcd256;
color: #3f3b00;
transform: scale(1.1);
box-shadow: 0 0 20px rgba(255, 187, 0, 0.8);
}
#log {
max-width: 20rem;
color: #3f3b00;
list-style: none;
margin: 2rem auto;
padding: 0;
text-align: center;
}
#log li {
border-radius: 4px;
animation: slide-in-from-left 1s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
margin: 0.75rem;
}
#log li.highlighted {
background-color: #3f3b00;
color: white;
}
#game-hints {
text-align: center;
color: #46432f;
}
#game-hints h2 {
font-family: 'Caprasimo', cursive;
font-size: 2rem;
margin: 0;
}
#game-hints ul {
list-style: none;
padding: 0;
margin: 0;
}
#game-hints button {
cursor: pointer;
border: none;
background: transparent;
color: #23221f;
font: inherit;
margin-top: 1.5rem;
}
@keyframes slide-in-from-left {
0% {
opacity: 0;
transform: translateX(-30%);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes pulse-text {
0% {
color: #e1dec7;
}
50% {
color: #9f9d83;
}
100% {
color: #e1dec7;
}
}
@keyframes pulse-text-size {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
@keyframes pulse {
0% {
border-color: #f6e35a;
box-shadow: 0 0 0 0 rgba(246, 227, 90, 0.4);
}
50% {
border-color: #f8c031;
box-shadow: 0 0 0 0.5rem rgba(248, 165, 49, 0);
}
100% {
border-color: #f6e35a;
box-shadow: 0 0 0 0 rgba(246, 227, 90, 0);
}
}
/* Fancy animation for showing the "Game Over" element */
@keyframes pop-in {
0% {
transform: scale(0);
opacity: 0;
}
80% {
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(1);
}
}
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
export const WINNING_COMBINATIONS = [
[
{ row: 0, column: 0 },
{ row: 0, column: 1 },
{ row: 0, column: 2 },
],
[
{ row: 1, column: 0 },
{ row: 1, column: 1 },
{ row: 1, column: 2 },
],
[
{ row: 2, column: 0 },
{ row: 2, column: 1 },
{ row: 2, column: 2 },
],
[
{ row: 0, column: 0 },
{ row: 1, column: 0 },
{ row: 2, column: 0 },
],
[
{ row: 0, column: 1 },
{ row: 1, column: 1 },
{ row: 2, column: 1 },
],
[
{ row: 0, column: 2 },
{ row: 1, column: 2 },
{ row: 2, column: 2 },
],
[
{ row: 0, column: 0 },
{ row: 1, column: 1 },
{ row: 2, column: 2 },
],
[
{ row: 0, column: 2 },
{ row: 1, column: 1 },
{ row: 2, column: 0 },
],
];
\ No newline at end of file
import { WINNING_COMBINATIONS } from "./winningCombi.js";
export function whoWins(gameBoard) {
for (let winningCombi of WINNING_COMBINATIONS) {
const [firstField,secondField,thirdField] = winningCombi ;
if (
gameBoard[firstField.row][firstField.column] &&
gameBoard[firstField.row][firstField.column] ==
gameBoard[secondField.row][secondField.column] &&
gameBoard[firstField.row][firstField.column] ==
gameBoard[thirdField.row][thirdField.column]
) {
return gameBoard[firstField.row][firstField.column];
}
}
return false;
}
export function isDraw (gameBoard) {
for (const row of gameBoard){
for (const item of row){
if (item ===null) {
return false ;
}
}
}
return true ;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment