diff --git a/src/App.css b/src/App.css index da1168b..027e0b2 100644 --- a/src/App.css +++ b/src/App.css @@ -2,6 +2,6 @@ display: grid; place-content: center; height: 100vh; - background-color: black; + background-color: var(--app-background-color); } diff --git a/src/App.tsx b/src/App.tsx index 6a35175..323fca3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import './App.css' +import './mediaQueries.css' import Arbiter from './components/Arbiter/Arbiter' // The app component diff --git a/src/Constants.ts b/src/Constants.ts index bc64ee0..1c002be 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -5,9 +5,6 @@ import { PieceType, TeamType } from './Types' export const VERTICAL_AXIS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] export const HORIZONTAL_AXIS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] -// Grid Size -export const GRID_SIZE = 50 - // InitialBoardState export const initialBoard: Board = new Board( [ @@ -87,7 +84,7 @@ export const initialBoard: Board = new Board( new Piece(new Position(12, 9), PieceType.PAWN, TeamType.GREEN, false), new Piece(new Position(12, 10), PieceType.PAWN, TeamType.GREEN, false), ], - 0 + 1 ) initialBoard.calculateAllMoves() diff --git a/src/components/Arbiter/Arbiter.tsx b/src/components/Arbiter/Arbiter.tsx index 394f237..f1659d8 100644 --- a/src/components/Arbiter/Arbiter.tsx +++ b/src/components/Arbiter/Arbiter.tsx @@ -4,6 +4,8 @@ import { initialBoard } from '../../Constants' import { PieceType, TeamType } from '../../Types' import { Piece, Position } from '../../models' import { useRef, useState } from 'react' +import { ChessProvider } from '../context/ChessContext' +import ChessWrapper from '../ChessWrapper/ChessWrapper' export default function Arbiter() { // Declaring the constants @@ -91,16 +93,19 @@ export default function Arbiter() { } // Deciding the type of color of the pieces when opening the modal - function promotionTeamType() { - if (promotionPawn?.team === TeamType.RED) { - return 'r' - } else if (promotionPawn?.team === TeamType.BLUE) { - return 'b' - } else if (promotionPawn?.team === TeamType.YELLOW) { - return 'y' - } else if (promotionPawn?.team === TeamType.GREEN) { - return 'g' + function promotionTeamType(): 'r' | 'b' | 'y' | 'g' { + if (promotionPawn && promotionPawn.team) { + if (promotionPawn.team === TeamType.RED) { + return 'r' + } else if (promotionPawn.team === TeamType.BLUE) { + return 'b' + } else if (promotionPawn.team === TeamType.YELLOW) { + return 'y' + } else if (promotionPawn.team === TeamType.GREEN) { + return 'g' + } } + return 'r'; // defaulting to red team at initial state } // Writing the full name of the winning team @@ -126,29 +131,29 @@ export default function Arbiter() { setBoard(initialBoard.clone()) } - + return ( <>
promotePawn(PieceType.ROOK)} - src={`${basePath}/assets/images/${promotionTeamType()}R.png`} + src={`${basePath}assets/images/${promotionTeamType()}R.png`} alt='Rook' /> promotePawn(PieceType.KNIGHT)} - src={`${basePath}/assets/images/${promotionTeamType()}N.png`} + src={`${basePath}assets/images/${promotionTeamType()}N.png`} alt='Knight' /> promotePawn(PieceType.BISHOP)} - src={`${basePath}/assets/images/${promotionTeamType()}B.png`} + src={`${basePath}assets/images/${promotionTeamType()}B.png`} alt='Bishop' /> promotePawn(PieceType.QUEEN)} - src={`${basePath}/assets/images/${promotionTeamType()}Q.png`} + src={`${basePath}assets/images/${promotionTeamType()}Q.png`} alt='Queen' />
@@ -166,7 +171,7 @@ export default function Arbiter() { {teamNames[team]} {`${teamNames[team]} @@ -179,13 +184,16 @@ export default function Arbiter() {
- + + + + + ) } diff --git a/src/components/ChessWrapper/ChessWrapper.css b/src/components/ChessWrapper/ChessWrapper.css new file mode 100644 index 0000000..f8fcfd1 --- /dev/null +++ b/src/components/ChessWrapper/ChessWrapper.css @@ -0,0 +1,81 @@ +.game-layout { + display: flex; + flex-direction: column; + padding: 20px; + gap: 10px; +} + +.game-layout > .middle-panel { + display: flex; + gap: 10px; +} + +.game-layout > .panel-area, +.game-layout > .middle-panel > .panel-area { + display: flex; + align-items: center; + justify-content: center; +} + +.middle-panels > .player-panel { + writing-mode: vertical-rl; + height: calc(var(--tile-size) * 8); + width: 0.5vw; + padding: 0 12px; +} + +.game-layout > .panel-area > .player-panel { + height: 0.5vw; + width: calc(var(--tile-size) * 8); + padding: 12px 0; +} + +div#left-panel { + transform: rotate(-180deg); +} + +.player-panel { + display: flex; + justify-content: center; + align-items: center; + border-radius: calc(var(--tile-size) * 0.1); +} + +.game-display { + display: none; +} + +@media all and (max-width: 700px) { + /* phone styles here */ + .game-layout { + padding: 4vh 4vw; + } + + .player-panel > .player-name { + display: none; + } + + .middle-panels > .player-panel { + padding: 0; + width: 5px; + border-radius: 1px; + } + + .game-layout > .panel-area > .player-panel { + padding: 0; + height: 5px; + border-radius: 1px; + } + + .game-display { + display: flex; + justify-content: center; + align-items: center; + } + + .game-display > .currentPlayerDisplay { + color: var(--secondary-text-color); + padding: 2px 10px; + border-radius: calc(1rem + 2px); + } +} diff --git a/src/components/ChessWrapper/ChessWrapper.tsx b/src/components/ChessWrapper/ChessWrapper.tsx new file mode 100644 index 0000000..b8272b3 --- /dev/null +++ b/src/components/ChessWrapper/ChessWrapper.tsx @@ -0,0 +1,102 @@ +import './ChessWrapper.css' +import { useChessContext } from '../context/ChessContext' +import { TeamType } from '../../Types' + +type PlayerState = 'active' | 'inactive' | 'lost'; + +/** Wrapper component for the chess board, holds player panels surrounding the board */ +export default function ChessWrapper({ + children, + loseOrder: lostTeams, +}: { + children: React.ReactNode; + loseOrder: TeamType[]; +}) { + const { currentPlayer } = useChessContext(); + const players = [ + { name: "Red Player", color: "red", id: "r" }, + { name: "Blue Player", color: "blue", id: "b" }, + { name: "Yellow Player", color: "yellow", id: "y" }, + { name: "Green Player", color: "green", id: "g" }, + ]; + + const getPlayerState = (playerId: TeamType): PlayerState => { + if (lostTeams.includes(playerId)) return 'lost' + return currentPlayer === playerId ? 'active' : 'inactive' + } + + const displayBgColor = players.find((p) => p.id === currentPlayer)?.color || 'grey'; + + return ( +
+
+
+ {players.find((p) => p.id === currentPlayer)?.name} +
+
+
+
+ +
+
+
+ +
+
{children}
+
+ +
+
+
+ +
+
+
+ ); +} + +interface PlayerPanelProps { + name: string + color: string + playerState: PlayerState +} + +/** Player panel component, holds player name and indicates active player */ +function PlayerPanel({ name, color, playerState }: PlayerPanelProps) { + const stateToColorMap = { + active: color, + inactive: 'grey', + lost: '#444', + } + + return ( +
+
{`${name} ${playerState === 'lost' ? 'lost' : ''}`}
+
+ ) +} diff --git a/src/components/Chessboard/Chessboard.css b/src/components/Chessboard/Chessboard.css index 776b6e4..8e0f55a 100644 --- a/src/components/Chessboard/Chessboard.css +++ b/src/components/Chessboard/Chessboard.css @@ -1,19 +1,23 @@ #chessboard { display: grid; - grid-template-columns: repeat(14, 50px); - grid-template-rows: repeat(14, 50px); - width: 700px; - height: 700px; - background-color: black; + grid-template-columns: repeat(14, var(--tile-size)); + grid-template-rows: repeat(14, var(--tile-size)); + width: calc(var(--tile-size) * 14); + height: calc(var(--tile-size) * 14); + background-color: var(--app-background-color); } .modal { - position: absolute; - top: 0px; - right: 0px; - bottom: 0px; - left: 0px; - z-index: 1000; + position: fixed; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + width: 100dvw; + height: 100dvh; + background-color: rgba(0, 0, 0, 0.75); + z-index: 20; } .modal.hidden { @@ -21,21 +25,18 @@ } .modal > .modal-body { - position: absolute; - top: 50%; - transform: translateY(-50%); - left: calc(50% - 350px); display: flex; align-items: center; justify-content: space-around; - width: 700px; + gap: 10px; + height: fit-content; + width: calc(var(--tile-size) * 14); background-color: rgba(0, 0, 0, 0.75); - padding: 20px 0; } .modal > .modal-body > img { - height: 90px; - padding: 20px; + width: 100%; + padding: 10px; border-radius: 50%; } @@ -47,19 +48,19 @@ .modal > .modal-body > .checkmate-body { display: flex; flex-direction: column; + padding: 10px; gap: 24px; width: 45%; } .modal > .modal-body > .checkmate-body > span { - font-size: 28px; - color: white; + font-size: 1.5rem; text-align: center; } .modal > .modal-body > .checkmate-body .team { font-size: 20px; - color: white; + color: var(--secondary-text-color); } .modal > .modal-body > .checkmate-body td:nth-child(2) { @@ -67,12 +68,12 @@ } .modal > .modal-body > .checkmate-body > button { - background-color: #779556; + background-color: var(--button-background-color); border: none; border-radius: 8px; padding: 16px; font-size: 28px; - color: white; + color: var(--secondary-text-color); } .modal > .modal-body > .checkmate-body > button:hover { diff --git a/src/components/Chessboard/Chessboard.tsx b/src/components/Chessboard/Chessboard.tsx index 6164fb6..cd3bf3d 100644 --- a/src/components/Chessboard/Chessboard.tsx +++ b/src/components/Chessboard/Chessboard.tsx @@ -1,225 +1,212 @@ -import './Chessboard.css' -import PlayerName from '../PlayerName/PlayerName' -import { Piece, Position } from '../../models' -import Tile from '../Tile/Tile' -import { useRef, useState } from 'react' -import { VERTICAL_AXIS, HORIZONTAL_AXIS, GRID_SIZE } from '../../Constants' +import "./Chessboard.css"; +import { Piece, Position } from "../../models"; +import Tile from "../Tile/Tile"; +import { useEffect, useRef, useState } from "react"; +import { VERTICAL_AXIS, HORIZONTAL_AXIS } from "../../Constants"; import { PieceType, TeamType } from '../../Types' +import { useChessContext } from "../context/ChessContext"; -// Interface deciding the types interface Props { - playMove: (piece: Piece, position: Position) => boolean - pieces: Piece[] - whoseTurn: TeamType - loseOrder: TeamType[] - isChecked: boolean + playMove: (piece: Piece, position: Position) => boolean; + pieces: Piece[]; + loseOrder: TeamType[]; + isChecked: boolean; } -export default function Chessboard({ - playMove, - pieces, - whoseTurn, - loseOrder, - isChecked, -}: Props) { - // Declaring Constants - const [activePiece, setActivePiece] = useState(null) - const [grabPosition, setGrabPosition] = useState( - new Position(-1, -1) - ) - const [isClicked, setIsClicked] = useState(false) - const chessboardRef = useRef(null) - // Function when player grabs a piece - function grabPiece(e: React.MouseEvent) { - // Grabbing the pieces off the chessboard - const chessboard = chessboardRef.current - const element = e.target as HTMLElement - - if (element.classList.contains('chess-piece') && chessboard) { - const grabX = Math.floor((e.clientX - chessboard.offsetLeft) / GRID_SIZE) - const grabY = Math.abs( - Math.abs( - Math.ceil((e.clientY - chessboard.offsetTop - 700) / GRID_SIZE) - ) - ) - - setGrabPosition(new Position(grabX, grabY)) - - const x = e.clientX - GRID_SIZE / 2 - const y = e.clientY - GRID_SIZE / 2 - - element.style.position = 'absolute' - element.style.left = `${x}px` - element.style.top = `${y}px` - element.style.zIndex = '10' - - setActivePiece(element) - setIsClicked(false) - document.body.style.userSelect = 'none' +export default function Chessboard({ playMove, pieces, loseOrder, isChecked }: Props) { + + // Dynamic grid size calculation + const getGridSize = () => { + const vw = window.innerWidth * 0.8; + const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); + return window.innerWidth > 768 ? 2.5 * rem : vw / 14; + }; + const GRID_SIZE = getGridSize(); + const { currentPlayer: whoseTurn } = useChessContext(); + + // Piece tracking states + const [currentPiece, setCurrentPiece] = useState(null); + const [activePieceElement, setActivePieceElement] = useState(null); + const [selectedPieceElement, setSelectedPieceElement] = useState(null); + const [showPossibleMoves, setShowPossibleMoves] = useState(false); + + // board related states and constants + const chessboardRef = useRef(null); + const [boardMetrics, setBoardMetrics] = useState({ + top: 0, left: 0, width: 0, height: 0 + }); + + /** Calculates boundaries of the chessboard */ + const calculateBoundaries = () => { + if (!chessboardRef.current) return null; + + const { left, top, width, height } = boardMetrics; + const sidePortionWidth = 3 * GRID_SIZE; + + return { + centralRegionLeft: left, + centralRegionRight: left + width, + centralRegionTop: top + sidePortionWidth, + centralRegionBottom: top + height - sidePortionWidth, + topBottomRegionLeft: left + sidePortionWidth, + topBottomRegionRight: left + width - sidePortionWidth, + topRegionBottom: top + sidePortionWidth, + bottomRegionTop: top + height - sidePortionWidth, + bottom: top + height + }; + }; + + // Updates board metrics on resize + useEffect(() => { + const updateOffset = () => { + if (chessboardRef.current) { + const rect = chessboardRef.current.getBoundingClientRect(); + setBoardMetrics({ + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height + }); + } + }; + + updateOffset(); + window.addEventListener("resize", updateOffset); + return () => window.removeEventListener("resize", updateOffset); + }, []); + + /** Transforms client position to chessboard grid position coordinates */ + const calculateGridPosition = (clientX: number, clientY: number) => ({ + x: Math.floor((clientX - boardMetrics.left) / GRID_SIZE), + y: Math.floor((boardMetrics.height - (clientY - boardMetrics.top)) / GRID_SIZE) + }); + + /* Piece Interaction Event Handling */ + /** Handles piece grabbing event, loads piece htmlElement, piece object */ + const grabPiece = (e: React.MouseEvent) => { + const element = e.target as HTMLElement; + const chessboard = chessboardRef.current; + + if (element.classList.contains("chess-piece") && chessboard) { + const { x: grabX, y: grabY } = calculateGridPosition(e.clientX, e.clientY); + const x = e.clientX - GRID_SIZE / 2; + const y = e.clientY - GRID_SIZE / 2; + + element.style.position = "fixed"; + element.style.left = `${x}px`; + element.style.top = `${y}px`; + element.style.zIndex = "100"; + + setCurrentPiece(pieces.find(p => p.samePosition(new Position(grabX, grabY))) || null); + setActivePieceElement(element); + setShowPossibleMoves(true); + document.body.style.userSelect = 'none'; } - } - - // Function when player clicks a piece - function clickPiece(e: React.MouseEvent) { - const chessboard = chessboardRef.current - const element = e.target as HTMLElement - - if (element.classList.contains('chess-piece') && chessboard) { - const grabX = Math.floor((e.clientX - chessboard.offsetLeft) / GRID_SIZE) - const grabY = Math.abs( - Math.abs( - Math.ceil((e.clientY - chessboard.offsetTop - 700) / GRID_SIZE) - ) - ) - - setGrabPosition(new Position(grabX, grabY)) - setActivePiece(element) - setIsClicked(true) + }; + + /** Handles Piece move event, restricts piece movement to insides of chessboard */ + const movePiece = (e: React.MouseEvent) => { + if (activePieceElement && chessboardRef.current) { + const boundaries = calculateBoundaries(); + if (!boundaries) return; + + const x = e.clientX - GRID_SIZE / 2; + const y = e.clientY - GRID_SIZE / 2; + + const isInsideValidRegion = ( + (x > boundaries.centralRegionLeft && + x < boundaries.centralRegionRight && + y > boundaries.centralRegionTop && + y < boundaries.centralRegionBottom) || + (x > boundaries.topBottomRegionLeft && + x < boundaries.topBottomRegionRight && + ((y > boardMetrics.top && y < boundaries.topRegionBottom) || + (y > boundaries.bottomRegionTop && y < boundaries.bottom))) + ); + + activePieceElement.style.position = "fixed"; + activePieceElement.style.left = `${x}px`; + activePieceElement.style.top = `${y}px`; + activePieceElement.style.visibility = isInsideValidRegion ? "visible" : "hidden"; } - } - - // Function when player tries to move a piece - function movePiece(e: React.MouseEvent) { - const chessboard = chessboardRef.current - - if (activePiece && chessboard) { - // Declaring constants for restricting the pieces - const leftX = chessboard.offsetLeft - 4 - const midleftX = - chessboard.offsetLeft + (chessboard.clientWidth / 14) * 3 - 4 - const topY = chessboard.offsetTop - 4 - const midtopY = - chessboard.offsetTop + (chessboard.clientHeight / 14) * 3 - 4 - const rightX = chessboard.offsetLeft + chessboard.clientWidth - 46 - const midrightX = - chessboard.offsetLeft - - (chessboard.clientWidth / 14) * 3 + - chessboard.clientWidth - - 46 - const bottomY = chessboard.offsetTop + chessboard.clientHeight - 46 - const midbottomY = - chessboard.offsetTop - - (chessboard.clientHeight / 14) * 3 + - chessboard.clientHeight - - 46 - const x = e.clientX - GRID_SIZE / 2 - const y = e.clientY - GRID_SIZE / 2 - activePiece.style.position = 'absolute' - - // Restricting the x position - if (x < leftX) { - activePiece.style.left = `${leftX}px` - } else if (x > rightX) { - activePiece.style.left = `${rightX}px` - } else { - activePiece.style.left = `${x}px` + }; + + /** Handles piece placement event, plays the move, checks for success, reflects the result on chessboard */ + const dropPiece = (e: React.MouseEvent) => { + if (chessboardRef.current && currentPiece) { + const targetElement = activePieceElement || selectedPieceElement; + if (!targetElement) return; + + const oldPosition = currentPiece.position; + const { x: placeX, y: placeY } = calculateGridPosition(e.clientX, e.clientY); + + // Reset piece if dropped in the same position + if (oldPosition?.x === placeX && oldPosition?.y === placeY) { + resetPiecePosition(targetElement); + setSelectedPieceElement(activePieceElement); + setActivePieceElement(null); + document.body.style.userSelect = 'auto'; + return; } - // Restricting the y position - if (y < topY) { - activePiece.style.top = `${topY}px` - } else if (y > bottomY) { - activePiece.style.top = `${bottomY}px` - } else { - activePiece.style.top = `${y}px` - } + const success = playMove(currentPiece.clone(), new Position(placeX, placeY)); - // Restricting the cutout portions - if ((x < midleftX && y > midbottomY) || (x < midleftX && y < midtopY)) { - activePiece.style.left = `${midleftX}px` - } else if ( - (x > midrightX && y > midbottomY) || - (x > midrightX && y < midtopY) - ) { - activePiece.style.left = `${midrightX}px` - } - } - } - - // Function when player drops a piece - function dropPiece(e: React.MouseEvent) { - const chessboard = chessboardRef.current - // Dropping the pieces on the right grid - if (activePiece && chessboard) { - const x = Math.floor((e.clientX - chessboard.offsetLeft) / GRID_SIZE) - const y = Math.abs( - Math.ceil((e.clientY - chessboard.offsetTop - 700) / GRID_SIZE) - ) - - const currentPiece = pieces.find((p) => p.samePosition(grabPosition)) - - if (currentPiece) { - var success = playMove(currentPiece.clone(), new Position(x, y)) - - if (!success) { - // Resets the piece position - activePiece.style.position = 'static' - activePiece.style.removeProperty('top') - activePiece.style.removeProperty('left') - activePiece.style.removeProperty('z-index') - } + if (!success) { + resetPiecePosition(targetElement); } - setActivePiece(null) - document.body.style.userSelect = 'auto' + setActivePieceElement(null); + setShowPossibleMoves(false); + document.body.style.userSelect = 'auto'; } - } - // Setting the four player chessboard - let board = [] - - for (let j = VERTICAL_AXIS.length - 1; j >= 0; j--) { - for (let i = 0; i < HORIZONTAL_AXIS.length; i++) { - const num_i = i - const num_j = j - const piece = pieces.find((p) => p.samePosition(new Position(i, j))) - let image = piece ? piece.image : undefined - - let currentPiece = - activePiece != null - ? pieces.find((p) => p.samePosition(grabPosition)) - : undefined - - // For highlighting the attacked pieces - let highlight = currentPiece?.possibleMoves - ? currentPiece.possibleMoves.some((p) => - p.samePosition(new Position(i, j)) - ) - : false - - board.push( - - ) + }; + + /** Helper function for resetting piece position back to original place */ + const resetPiecePosition = (element: HTMLElement) => { + element.style.position = "relative"; + element.style.removeProperty("top"); + element.style.removeProperty("left"); + element.style.removeProperty("z-index"); + }; + + /** chessboard constructor */ + const renderBoard = () => { + const board = []; + for (let j = VERTICAL_AXIS.length - 1; j >= 0; j--) { + for (let i = 0; i < HORIZONTAL_AXIS.length; i++) { + const piece = pieces.find(p => p.samePosition(new Position(i, j))); + const highlight = showPossibleMoves && + currentPiece?.possibleMoves?.some(p => p.samePosition(new Position(i, j))); + + board.push( + + ); + } } - } + return board; + }; + return ( - <> -
{ - if (!isClicked) { - movePiece(e) - } - }} - onMouseDown={(e) => grabPiece(e)} - onClick={(e) => clickPiece(e)} - onMouseUp={(e) => dropPiece(e)} - id='chessboard' - ref={chessboardRef} - > - {board} - -
- - ) +
+ {renderBoard()} +
+ ); } diff --git a/src/components/PlayerName/PlayerName.css b/src/components/PlayerName/PlayerName.css deleted file mode 100644 index 670357b..0000000 --- a/src/components/PlayerName/PlayerName.css +++ /dev/null @@ -1,39 +0,0 @@ -.playerName { - padding: 15px; - border-radius: 20px; - font-weight: bold; - position: absolute; - opacity: 0.4; -} - -.team-blue { - margin-top: 100px; - margin-left: 0; - background-color: rgb(96, 96, 253); -} - -.team-yellow { - margin-top: 0; - margin-left: 550px; - background-color: rgb(255, 255, 88); -} - -.team-red { - margin-top: 652px; - margin-left: 36px; - background-color: rgb(254, 81, 81); -} - -.team-green { - margin-top: 550px; - margin-left: 565px; - background-color: rgb(85, 249, 85); -} - -.player-active { - opacity: 1; -} - -.player-lost { - background-color: #eeeeee; -} diff --git a/src/components/PlayerName/PlayerName.tsx b/src/components/PlayerName/PlayerName.tsx deleted file mode 100644 index 1a3a38f..0000000 --- a/src/components/PlayerName/PlayerName.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { TeamType } from '../../Types' -import './PlayerName.css' - -interface Props { - whoseTurn: TeamType - lostTeams: TeamType[] -} -// Color names for the different players -export default function PlayerName({ whoseTurn, lostTeams }: Props) { - return ( - <> -
x) - .join(' ')} - > - Player RED -
-
x) - .join(' ')} - > - Player BLUE -
-
x) - .join(' ')} - > - Player YELLOW -
-
x) - .join(' ')} - > - Player GREEN -
- - ) -} diff --git a/src/components/Tile/Tile.css b/src/components/Tile/Tile.css index b67e655..edd9f9b 100644 --- a/src/components/Tile/Tile.css +++ b/src/components/Tile/Tile.css @@ -1,21 +1,20 @@ .tile { display: grid; place-content: center; - width: 50px; - height: 50px; + width: var(--tile-size); + height: var(--tile-size); } .tile img { - width: 50px; + width: var(--tile-size); } .tile .chess-piece { - width: 43px; - height: 43px; + width: var(--chess-piece-size); + height: var(--chess-piece-size); background-repeat: no-repeat; background-position: center; - background-size: 43px; - z-index: 1; + background-size: var(--chess-piece-size); } .tile .chess-piece:hover { @@ -40,17 +39,17 @@ .tile-highlight:not(.chess-piece-tile)::before { content: ' '; - width: 12px; - height: 12px; + width: var(--tile-highlight-size); + height: var(--tile-highlight-size); border-radius: 50%; background-color: rgba(0, 0, 0, 0.4); } .tile-highlight.chess-piece-tile::before { - position: absolute; + position: fixed; content: ' '; - width: 45px; - height: 45px; + width: var(--tile-highlight-chess-piece-size); + height: var(--tile-highlight-chess-piece-size); border: 2.5px solid rgba(0, 0, 0); border-radius: 50%; z-index: 2; diff --git a/src/components/context/ChessContext.tsx b/src/components/context/ChessContext.tsx new file mode 100644 index 0000000..d6dbe98 --- /dev/null +++ b/src/components/context/ChessContext.tsx @@ -0,0 +1,43 @@ +import { createContext, useCallback, useContext, useEffect, useState } from "react"; +import { TeamType } from "../../Types"; + +interface ChessContextProps { + currentPlayer: TeamType; + setCurrentPlayerByWhoseTurn: (whoseTurn: TeamType) => void; +} + +export const ChessContext = createContext({ + currentPlayer: TeamType.RED, + setCurrentPlayerByWhoseTurn: (whoseTurn) => {}, +}); + +/** Provider component for the chess context, provides currentPlayer, interactionMode */ +export function ChessProvider({ whoseTurn, children }: { whoseTurn: TeamType, children: React.ReactNode }) { + const [currentPlayer, setCurrentPlayer] = useState(whoseTurn); + + /** memoized function to set the current player by whose turn as input */ + // fixes the function behavior, function doesnt change on re-render, only changes when input differs + const setCurrentPlayerByWhoseTurn = useCallback((whoseTurn: TeamType) => { + setCurrentPlayer(whoseTurn); + }, []); + + useEffect(() => { + setCurrentPlayerByWhoseTurn(whoseTurn) + }, [whoseTurn, setCurrentPlayerByWhoseTurn]); + + return ( + + {children} + + ); +} + +/** custom chess context hook, provides easy access to the chess context */ +export function useChessContext() { + return useContext(ChessContext); +} diff --git a/src/index.css b/src/index.css index ec2585e..522997b 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,23 @@ +:root { + /* strictly global css variables only */ + --tile-size: 3rem; + + /* size */ + --chess-piece-size: calc(var(--tile-size) * 0.85); + --tile-highlight-size: calc(var(--tile-size) * 0.25); + --tile-highlight-chess-piece-size: calc(var(--tile-size) * 0.9); + + /* color palette */ + --dark-tile-color: #ababab; + --light-tile-color: #dcdcdc; + --useless-tile-color: black; + --checked-tile-gradient: radial-gradient(circle, red 20%, white 80%); + --primary-text-color: black; + --secondary-text-color: white; + --app-background-color: black; + --button-background-color: #779556; +} + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', diff --git a/src/mediaQueries.css b/src/mediaQueries.css new file mode 100644 index 0000000..bad1fcd --- /dev/null +++ b/src/mediaQueries.css @@ -0,0 +1,13 @@ +@media all and (min-width: 700px) { + /* tablet landscape and desktop styles here */ + :root { + --tile-size: 2.5rem; + } +} + +@media all and (max-width: 700px) { + /* phone styles here */ + :root { + --tile-size: calc(80vw / 14); + } +}