GithubHelp home page GithubHelp logo

tic-tac-toe-game's Introduction

Frontend Mentor - Tic Tac Toe solution

This is a solution to the Tic Tac Toe challenge on Frontend Mentor.

Table of contents

Overview

Tic Tac Toe is a simple game in which two players alternately put Xs and Os in compartments of a figure formed by two vertical lines crossing two horizontal lines and each tries to get a row of three Xs or three Os before the opponent does.

What unique about this project is you have two options whether to play vs CPU or another player which makes the game here is more interesting (the cpu part still in the production stage)

The challenge

Users should be able to:

  • View the optimal layout for the site depending on their device's screen size
  • See hover states for all interactive elements on the page
  • play Tic Tac Toe

Screenshot

game-start desktop-game-solo player-win mobile

Links

My process

Built with

  • Semantic HTML5 markup
  • CSS custom properties
  • Flexbox
  • CSS Grid
  • Mobile-first workflow
  • A11y & aria Standards
  • BEM
  • Sass
  • Gsap - JS animation library
  • React - JS library

What I learned

This project was built in order to experiment with creating a complete website using the basics of React in order to increase experience and skill in dealing with React components & state

  • a focus class that gave any element have it a uniq focus stage Using the same colors as the element proportional to its size
:focus {
    outline: 2px solid transparent;
}
.focus {
    --focus-shadow: 0 0 0 transparent;
    --accent-clr: var(--txt-clr);
    box-shadow: var(--focus-shadow), 0 0 0 -0.4em var(--bg-clr), 0 0 0 -0.6em
            var(--accent-clr);
    transition: box-shadow 400ms cubic-bezier(0.66, -0.2, 0.27, 1.15);
}
.focus:focus-visible {
    outline: 2px solid transparent;
    box-shadow: var(--focus-shadow), 0 0 0 0.25em var(--bg-clr), 0 0 0 0.45em
            var(--accent-clr);
}
  • the accent class that gave any element that have it the right variable colors
.accent-X-clr {
    --accent-clr: var(--accent-X-clr);
    --accent-shadow-clr: var(--clr-teal-blue-500);
}
.accent-O-clr {
    --accent-clr: var(--accent-O-clr);
    --accent-shadow-clr: var(--clr-gold-orange-600);
}
  • using isolation: isolate
li {
    // ! isolation: isolate is cool
    isolation: isolate;
}
  • using svh unite
@supports (height: 100svh) {
    #root {
        height: 100svh; //? svh -> Small view height
    }
}
  • using the right color name for easy identification
:root {
    // * ### teal blue ###
    --clr-teal-blue-500: hsl(178, 75%, 65%); // * X button shadow
    --clr-teal-blue-700: hsl(178, 60%, 48%); // * X accent color (button, text)
}
  • using :where
:where(h1, h2, h3, h4, p, a:not(.Button), .Button, span) {
    margin: 0;
    text-transform: uppercase;
    letter-spacing: #{toRem(2px)};
    color: var(--txt-clr);
}
  • the js crypto API to get a random Id
crypto.randomUUID();
  • the itemInfoFun function => a function that can handle any number of items and give each item [id, all the state =>(is played, player, is wining), location=>(index, row, column)]
function itemInfoFun(dimensions) {
    let Dimensions = dimensionsFun(dimensions);
    let arr = [];

    for (let i = 0; i < dimensions; i++) {
        arr.push({});
    }
    class ItemInfo {
        constructor(id, played, player, wining, index, x, y) {
            this.id = id;
            this.played = played;
            this.player = player;
            this.location = {
                index: index,
                x: x,
                y: y,
            };
            this.wining = wining;
        }
    }

    arr.forEach((ele, i) => {
        ele.ItemInfo = new ItemInfo(crypto.randomUUID(), false, null, false, i);
    });

    for (let i = 0; i < Dimensions; i++) {
        let item = i;
        let X = 1;
        for (item; item < dimensions; item = item + Dimensions) {
            arr[item].ItemInfo.location.x = X++;
        }
    }

    for (let i = 0; i < Dimensions; i++) {
        let item = i;
        for (item; item < dimensions; item = item + Dimensions) {
            arr[item].ItemInfo.location.y = i + 1;
        }
    }

    return arr;
}
  • the winingSystem function => a function that can determine if one of the player wins in all horizontal, vertical or diagonal cases, and it can also work with any size, whether 3 * 3 or more
//! if winingSystem return true => O wines, false => X wines, null => draw
//! winingSystem change the wining items state by itself
function winingSystem() {
    // for diagonals
    let diagonals = [[], []];
    // * diagonals[0] for the first diagonal diagonals[1] for the second diagonal
    for (let i = 0; i < dimensions; i++) {
        if (
            gameItems[i].ItemInfo.location.x +
                gameItems[i].ItemInfo.location.y ==
                Dim + 1 ||
            gameItems[i].ItemInfo.location.x -
                gameItems[i].ItemInfo.location.y ==
                0
        ) {
            if (
                gameItems[i].ItemInfo.location.x ==
                gameItems[i].ItemInfo.location.y
            ) {
                diagonals[0].push(gameItems[i]);
                if (diagonals[0].length == Dim) {
                    if (
                        diagonals[0].every(
                            (ele) => ele.ItemInfo.player == false
                        )
                    ) {
                        // if x wins
                        xWins(diagonals[0]);
                    }
                    if (
                        diagonals[0].every((ele) => ele.ItemInfo.player == true)
                    ) {
                        // if o wins
                        oWins(diagonals[0]);
                    }
                }
            }
            if (
                gameItems[i].ItemInfo.location.x !=
                    gameItems[i].ItemInfo.location.y ||
                gameItems[i].ItemInfo.location.x +
                    gameItems[i].ItemInfo.location.y ==
                    Dim + 1
            ) {
                diagonals[1].push(gameItems[i]);
                if (diagonals[1].length == Dim) {
                    if (
                        diagonals[1].every(
                            (ele) => ele.ItemInfo.player == false
                        )
                    ) {
                        // if x wins
                        xWins(diagonals[1]);
                    }
                    if (
                        diagonals[1].every((ele) => ele.ItemInfo.player == true)
                    ) {
                        // if o wins
                        oWins(diagonals[1]);
                    }
                }
            }
        }
    }

    // for column
    for (let i = 0; i < Dim; i++) {
        let item = i;
        let winnerColX = [];
        let winnerColO = [];

        for (item; item < dimensions; item = item + Dim) {
            if (gameItems[item].ItemInfo.player == false) {
                // if X wins in a column
                winnerColX.push(gameItems[item]);
                if (winnerColX.length == Dim) {
                    xWins(winnerColX);
                }
            }
            if (gameItems[item].ItemInfo.player == true) {
                // if O wins in a column
                winnerColO.push(gameItems[item]);
                if (winnerColO.length == Dim) {
                    oWins(winnerColO);
                }
            }
        }
    }

    // for rows
    for (let i = 0; i < dimensions; i = i + 3) {
        let start = i;
        let end = i + Dim;
        let winnerRowX = [];
        let winnerRowO = [];
        for (start; start < end; start++) {
            if (gameItems[start].ItemInfo.player == false) {
                // if X wins in a row
                winnerRowX.push(gameItems[start]);
                if (winnerRowX.length == Dim) {
                    xWins(winnerRowX);
                }
            }
            if (gameItems[start].ItemInfo.player == true) {
                // if O wins in a row
                winnerRowO.push(gameItems[start]);
                if (winnerRowO.length == Dim) {
                    oWins(winnerRowO);
                }
            }
        }
    }

    // if draw
    let d = gameItems.every((item) => {
        return item.ItemInfo.played == true && item.ItemInfo.wining == false;
    });
    if (d) {
        draw();
    }
}
  • if css aspect-ratio dose note work the hight of the element will be calculated using JS
useEffect(() => {
    if (!CSS.supports("aspect-ratio: 1 / 1")) {
        let gameBoardItem = document.querySelector(
            ".Stage2 main#Game > div:has(button)"
        );
        setPgWidth(gameBoardItem.offsetWidth);
        window.onresize = () => {
            setPgWidth(gameBoardItem.offsetWidth);
            gameBoardItem.style.height = pgWidth;
        };
    }
}, [window.innerWidth]);
  • the idea of using on single useStage object for the entire project
const [pageStates, setPageStates] = useState({
    pageState: 1,
    gameResult: {
        restart: false,
        draw: false,
        winner: null,
        history: {
            xWins: 0,
            oWins: 0,
            draw: 0,
        },
    },
    players: {
        cpu: {
            playingWith: false,
            cpuPlayer: null,
        },
        p1: false,
        currentPlayer: false,
    },
});
  • making an svg React component To get different state of the same element
function OIcon(prop) {
    let stroke;
    prop.hover ? (stroke = 2) : (stroke = 0);

    return (
        <svg
            width={64 + stroke}
            height={64 + stroke}
            viewBox={`0 0 ${64 + stroke} ${64 + stroke}`}
            xmlns="http://www.w3.org/2000/svg"
            className={`${accentFun(prop.accent)} O-icon`}
        >
            <title>O icon</title>
            <path
                d={
                    prop.hover
                        ? "M33 1c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32C15.327 65 1 50.673 1 33 1 15.327 15.327 1 33 1Zm0 18.963c-7.2 0-13.037 5.837-13.037 13.037 0 7.2 5.837 13.037 13.037 13.037 7.2 0 13.037-5.837 13.037-13.037 0-7.2-5.837-13.037-13.037-13.037Z"
                        : "M32 0c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32C14.327 64 0 49.673 0 32 0 14.327 14.327 0 32 0Zm0 18.963c-7.2 0-13.037 5.837-13.037 13.037 0 7.2 5.837 13.037 13.037 13.037 7.2 0 13.037-5.837 13.037-13.037 0-7.2-5.837-13.037-13.037-13.037Z"
                }
                fill={prop.hover ? "none" : "var(--accent-clr)"}
                stroke={prop.hover ? "var(--accent-clr)" : "none"}
                strokeWidth={stroke}
                fillRule={
                    (!prop.hover && prop.accent == "x") || prop.accent == "o"
                        ? "evenodd"
                        : "nonzero"
                }
            />
        </svg>
    );
}

Continued development

  • React components & state
  • Gsap filp
  • static website & dynamic website

Useful resources

Check out my latest previous articles:

Author

tic-tac-toe-game's People

Contributors

ymhaah avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.