Add more logic
This commit is contained in:
parent
0918fe65fa
commit
5ea9e93239
8 changed files with 106 additions and 32 deletions
8
.github/workflows/vite_deploy.yaml
vendored
8
.github/workflows/vite_deploy.yaml
vendored
|
@ -4,7 +4,7 @@ name: Deploy static content to Pages
|
||||||
on:
|
on:
|
||||||
# Runs on pushes targeting the default branch
|
# Runs on pushes targeting the default branch
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -17,7 +17,7 @@ permissions:
|
||||||
|
|
||||||
# Allow one concurrent deployment
|
# Allow one concurrent deployment
|
||||||
concurrency:
|
concurrency:
|
||||||
group: 'pages'
|
group: "pages"
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -34,7 +34,7 @@ jobs:
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
rm package-lock.json
|
rm package-lock.json
|
||||||
|
@ -47,7 +47,7 @@ jobs:
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
# Upload dist folder
|
# Upload dist folder
|
||||||
path: './dist'
|
path: "./dist"
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Frontend Mentor - Tic Tac Toe solution
|
# Frontend Mentor - Tic Tac Toe solution
|
||||||
|
|
||||||
This is a solution to the [Tic Tac Toe challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/tic-tac-toe-game-Re7ZF_E2v). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
This is a solution to the [Tic Tac Toe challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/tic-tac-toe-game-Re7ZF_E2v). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ Users should be able to:
|
||||||
|
|
||||||
Add a screenshot of your solution. The easiest way to do this is to use Firefox to view your project, right-click the page and select "Take a Screenshot". You can choose either a full-height screenshot or a cropped one based on how long the page is. If it's very long, it might be best to crop it.
|
Add a screenshot of your solution. The easiest way to do this is to use Firefox to view your project, right-click the page and select "Take a Screenshot". You can choose either a full-height screenshot or a cropped one based on how long the page is. If it's very long, it might be best to crop it.
|
||||||
|
|
||||||
Alternatively, you can use a tool like [FireShot](https://getfireshot.com/) to take the screenshot. FireShot has a free option, so you don't need to purchase it.
|
Alternatively, you can use a tool like [FireShot](https://getfireshot.com/) to take the screenshot. FireShot has a free option, so you don't need to purchase it.
|
||||||
|
|
||||||
Then crop/optimize/edit your image however you like, add it to your project, and update the file path in the image above.
|
Then crop/optimize/edit your image however you like, add it to your project, and update the file path in the image above.
|
||||||
|
|
||||||
|
@ -71,15 +71,17 @@ To see how you can add code snippets, see below:
|
||||||
```html
|
```html
|
||||||
<h1>Some HTML code I'm proud of</h1>
|
<h1>Some HTML code I'm proud of</h1>
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.proud-of-this-css {
|
.proud-of-this-css {
|
||||||
color: papayawhip;
|
color: papayawhip;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const proudOfThisFunc = () => {
|
const proudOfThisFunc = () => {
|
||||||
console.log('🎉')
|
console.log("🎉");
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want more help with writing markdown, we'd recommend checking out [The Markdown Guide](https://www.markdownguide.org/) to learn more.
|
If you want more help with writing markdown, we'd recommend checking out [The Markdown Guide](https://www.markdownguide.org/) to learn more.
|
||||||
|
|
|
@ -83,12 +83,12 @@ Remember, if you're looking for feedback on your solution, be sure to ask questi
|
||||||
|
|
||||||
There are multiple places you can share your solution:
|
There are multiple places you can share your solution:
|
||||||
|
|
||||||
1. Share your solution page in the **#finished-projects** channel of the [community](https://www.frontendmentor.io/community).
|
1. Share your solution page in the **#finished-projects** channel of the [community](https://www.frontendmentor.io/community).
|
||||||
2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around.
|
2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around.
|
||||||
3. Share your solution on other social channels like LinkedIn.
|
3. Share your solution on other social channels like LinkedIn.
|
||||||
4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/).
|
4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/).
|
||||||
|
|
||||||
We provide templates to help you share your solution once you've submitted it on the platform. Please do edit them and include specific questions when you're looking for feedback.
|
We provide templates to help you share your solution once you've submitted it on the platform. Please do edit them and include specific questions when you're looking for feedback.
|
||||||
|
|
||||||
The more specific you are with your questions the more likely it is that another member of the community will give you feedback.
|
The more specific you are with your questions the more likely it is that another member of the community will give you feedback.
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import MainMenu from "./mainMenu";
|
import MainMenu from "./mainMenu";
|
||||||
import Game from "./game";
|
import Game from "./game";
|
||||||
import Modal from "./modal";
|
import { useStore } from "./store";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const isGameRunning = useStore((state) => state.isGameRunning);
|
||||||
|
const gameKey = useStore((state) => state.gameKey);
|
||||||
|
const players = useStore((state) => state.players);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen items-center justify-center bg-navy-700">
|
<main className="flex min-h-screen items-center justify-center bg-navy-700">
|
||||||
{/* <MainMenu /> */}
|
{isGameRunning ? <Game players={players} key={gameKey} /> : <MainMenu />}
|
||||||
<Game />
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
55
src/game.jsx
55
src/game.jsx
|
@ -8,9 +8,39 @@ import OvalOutline from "../assets/icon-o-outline.svg?react";
|
||||||
import logo from "../assets/logo.svg";
|
import logo from "../assets/logo.svg";
|
||||||
import restart from "../assets/icon-restart.svg";
|
import restart from "../assets/icon-restart.svg";
|
||||||
|
|
||||||
|
import { restartGame } from "./store";
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
|
|
||||||
export default () => {
|
const getWinner = (grid) => {
|
||||||
|
const combos = [
|
||||||
|
[0, 1, 2],
|
||||||
|
[3, 4, 5],
|
||||||
|
[6, 7, 8],
|
||||||
|
[0, 3, 6],
|
||||||
|
[1, 4, 7],
|
||||||
|
[2, 5, 8],
|
||||||
|
[0, 4, 8],
|
||||||
|
[2, 4, 6],
|
||||||
|
];
|
||||||
|
|
||||||
|
const wins = (combo) => {
|
||||||
|
const symbols = combo.map((i) => grid[i]);
|
||||||
|
|
||||||
|
return symbols.every((symbol) => symbol == symbols[0] && symbol != "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const winningCombo = combos.find(wins);
|
||||||
|
|
||||||
|
if (winningCombo) {
|
||||||
|
return grid[winningCombo[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ players: _players }) => {
|
||||||
|
const [score, updateScore] = useImmer({ X: 0, O: 0, ties: 0 });
|
||||||
|
|
||||||
const [grid, updateGrid] = useImmer(Array(9).fill(""));
|
const [grid, updateGrid] = useImmer(Array(9).fill(""));
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
|
|
||||||
|
@ -18,6 +48,15 @@ export default () => {
|
||||||
const TurnOutline = turn == "X" ? CrossOutline : OvalOutline;
|
const TurnOutline = turn == "X" ? CrossOutline : OvalOutline;
|
||||||
const TurnIndicator = turn == "X" ? Cross : Oval;
|
const TurnIndicator = turn == "X" ? Cross : Oval;
|
||||||
|
|
||||||
|
const winner = getWinner(grid);
|
||||||
|
if (winner) {
|
||||||
|
updateScore((score) => {
|
||||||
|
score[winner]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateGrid(() => Array(9).fill(""));
|
||||||
|
}
|
||||||
|
|
||||||
const renderSymbol = (symbol) => {
|
const renderSymbol = (symbol) => {
|
||||||
if (symbol == "") {
|
if (symbol == "") {
|
||||||
let colorClass = turn == "X" ? "text-blue-700" : "text-yellow-700";
|
let colorClass = turn == "X" ? "text-blue-700" : "text-yellow-700";
|
||||||
|
@ -77,16 +116,16 @@ export default () => {
|
||||||
|
|
||||||
<footer className="flex items-center justify-between text-navy-700">
|
<footer className="flex items-center justify-between text-navy-700">
|
||||||
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-blue-700">
|
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-blue-700">
|
||||||
<p className="text-base uppercase">X (you)</p>
|
<p className="text-base uppercase">X ({_players.X})</p>
|
||||||
<p className="text-h-m uppercase">14</p>
|
<p className="text-h-m uppercase">{score.X}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-silver-700">
|
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-silver-700">
|
||||||
<p className="text-base uppercase">Ties</p>
|
<p className="text-base uppercase">Ties</p>
|
||||||
<p className="text-h-m uppercase">32</p>
|
<p className="text-h-m uppercase">{score.ties}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-yellow-700">
|
<div className="flex h-20 w-36 flex-col items-center justify-center rounded-2xl bg-yellow-700">
|
||||||
<p className="text-base uppercase">O (cpu)</p>
|
<p className="text-base uppercase">O ({_players.O})</p>
|
||||||
<p className="text-h-m uppercase">11</p>
|
<p className="text-h-m uppercase">{score.O}</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,9 +145,7 @@ export default () => {
|
||||||
<p className="text-h-xs uppercase">No, cancel</p>
|
<p className="text-h-xs uppercase">No, cancel</p>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={restartGame}
|
||||||
updateGrid(() => Array(9).fill(""));
|
|
||||||
}}
|
|
||||||
className="rounded-xl bg-yellow-700 px-5 py-4 inner-shadow-1-yellow-900 hover:bg-yellow-400"
|
className="rounded-xl bg-yellow-700 px-5 py-4 inner-shadow-1-yellow-900 hover:bg-yellow-400"
|
||||||
>
|
>
|
||||||
<p className="text-h-xs uppercase">Yes, restart</p>
|
<p className="text-h-xs uppercase">Yes, restart</p>
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import logo from "../assets/logo.svg";
|
import logo from "../assets/logo.svg";
|
||||||
import Cross from "../assets/icon-x.svg?react";
|
import Cross from "../assets/icon-x.svg?react";
|
||||||
import Oval from "../assets/icon-o.svg?react";
|
import Oval from "../assets/icon-o.svg?react";
|
||||||
import { useStore, setPlayerOneSymbol } from "./store";
|
|
||||||
|
import { setIsGameRunning, updatePlayers } from "./store";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const playerOneSymbol = useStore((state) => state.playerOneSymbol);
|
const [playerOneSymbol, setPlayerOneSymbol] = useState("X");
|
||||||
|
const playerTwoSymbol = playerOneSymbol == "X" ? "O" : "X";
|
||||||
|
|
||||||
|
const startGame = (playerTwo) => {
|
||||||
|
updatePlayers({
|
||||||
|
[playerOneSymbol]: "P1",
|
||||||
|
[playerTwoSymbol]: playerTwo,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsGameRunning(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="m-6 flex w-full max-w-lg flex-col items-center space-y-10">
|
<div className="m-6 flex w-full max-w-lg flex-col items-center space-y-10">
|
||||||
|
@ -22,8 +34,8 @@ export default () => {
|
||||||
loop={false}
|
loop={false}
|
||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
["cross", Cross],
|
["X", Cross],
|
||||||
["oval", Oval],
|
["O", Oval],
|
||||||
].map(([value, Symbol]) => (
|
].map(([value, Symbol]) => (
|
||||||
<RadioGroup.Item
|
<RadioGroup.Item
|
||||||
className="group h-full w-1/2 rounded-xl hover:bg-silver-700/5 data-[state='checked']:bg-silver-700"
|
className="group h-full w-1/2 rounded-xl hover:bg-silver-700/5 data-[state='checked']:bg-silver-700"
|
||||||
|
@ -43,10 +55,16 @@ export default () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full space-y-5">
|
<div className="w-full space-y-5">
|
||||||
<button className="flex w-full items-center justify-center rounded-2xl bg-yellow-700 p-4 inner-shadow-2-yellow-900 hover:bg-yellow-400">
|
<button
|
||||||
|
onClick={() => startGame("CPU")}
|
||||||
|
className="flex w-full items-center justify-center rounded-2xl bg-yellow-700 p-4 inner-shadow-2-yellow-900 hover:bg-yellow-400"
|
||||||
|
>
|
||||||
<p className="text-h-s uppercase text-navy-700">New game (vs cpu)</p>
|
<p className="text-h-s uppercase text-navy-700">New game (vs cpu)</p>
|
||||||
</button>
|
</button>
|
||||||
<button className="flex w-full items-center justify-center rounded-2xl bg-blue-700 p-4 inner-shadow-2-blue-900 hover:bg-blue-400">
|
<button
|
||||||
|
onClick={() => startGame("P2")}
|
||||||
|
className="flex w-full items-center justify-center rounded-2xl bg-blue-700 p-4 inner-shadow-2-blue-900 hover:bg-blue-400"
|
||||||
|
>
|
||||||
<p className="text-h-s uppercase text-navy-700">
|
<p className="text-h-s uppercase text-navy-700">
|
||||||
New game (vs player)
|
New game (vs player)
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default ({ isOpen, children, className, onClose }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<dialog
|
||||||
className="backdrop:bg-black/50 h-64 w-screen max-w-[100vw] bg-navy-700"
|
className="h-64 w-screen max-w-[100vw] bg-navy-700 backdrop:bg-black/50"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
|
import { produce } from "immer";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
export const useStore = create((set) => ({
|
export const useStore = create((set) => ({
|
||||||
playerOneSymbol: "cross",
|
isGameRunning: false,
|
||||||
|
gameKey: 0,
|
||||||
|
players: {
|
||||||
|
X: "",
|
||||||
|
O: "",
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const setPlayerOneSymbol = (playerOneSymbol) =>
|
export const setIsGameRunning = (isGameRunning) =>
|
||||||
useStore.setState(() => ({ playerOneSymbol }));
|
useStore.setState(() => ({ isGameRunning }));
|
||||||
|
|
||||||
|
export const updatePlayers = (players) =>
|
||||||
|
useStore.setState(() => ({ players }));
|
||||||
|
|
||||||
|
export const restartGame = () =>
|
||||||
|
useStore.setState(({ gameKey }) => ({
|
||||||
|
gameKey: 1 - gameKey,
|
||||||
|
}));
|
||||||
|
|
Reference in a new issue