diff --git a/package.json b/package.json index 5089c3a..a9587d3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "tic-tac-toe-world", - "version": "1.2.11", + "version": "1.3.11", "scripts": { "dev": "astro dev --host", "start:build": "astro check && astro build && astro preview --host", diff --git a/src/assets/react/contexts/GameContext.tsx b/src/assets/react/contexts/GameContext.tsx index 7a3f055..f09b0a4 100644 --- a/src/assets/react/contexts/GameContext.tsx +++ b/src/assets/react/contexts/GameContext.tsx @@ -1,20 +1,6 @@ import { createContext } from "react"; -import type { BoardType, PlayersType, WinnerType, WinningPositionsType } from "@/assets/types/types"; import { defaultPlayers } from "@/assets/ts/constants"; - -interface IGameContext { - turn: number - winningLineLength: number - winningPositions: WinningPositionsType - players: PlayersType - board: BoardType - winner: WinnerType - setTurn: (value: number) => void - setBoard: (value: BoardType) => void - setWinner: (value: WinnerType) => void - setWinningPositions: (value: WinningPositionsType) => void - resetGame: () => void -} +import type { IGameContext } from "@/assets/types/interfaces"; /** * The code is creating a context object called `GameContext` using the `createContext` function from @@ -28,6 +14,7 @@ const GameContext = createContext({ players: defaultPlayers, winner: null, board: [[]], + fallingPieceMode: false, setTurn: () => { }, setWinningPositions: () => { }, setWinner: () => { }, diff --git a/src/assets/react/hooks/useGameOptions.ts b/src/assets/react/hooks/useGameOptions.ts index ce54a5e..34b0e34 100644 --- a/src/assets/react/hooks/useGameOptions.ts +++ b/src/assets/react/hooks/useGameOptions.ts @@ -27,6 +27,12 @@ export type OptionsGroup = { options: OptionsToggle; handler: (index: number) => void; } + fallingPieceMode: { + title: string; + index: number; + options: OptionsToggle; + handler: (index: number) => void; + } } const useGameOptions = () => { @@ -35,14 +41,16 @@ const useGameOptions = () => { const [sizeIndex, setSizeIndex] = useState(0); const [playersIndex, setPlayersIndex] = useState(0); const [winningLineLengthIndex, setWinningLineLengthIndex] = useState(0); + const [fallingPieceModeIndex, setFallingPieceModeIndex] = useState(0); // create handlers const handleSizeToggle = (index: number) => { setSizeIndex(index) } const handlePlayersToggle = (index: number) => { setPlayersIndex(index) } const handleWinningLineLengthToggle = (index: number) => { setWinningLineLengthIndex(index) } + const handleFallingPieceModeToggle = (index: number) => { setFallingPieceModeIndex(index) } // initialize options - const initialGroupOptions = useMemo(() => generateOptions(playersIndex, sizeIndex, winningLineLengthIndex), []) + const initialGroupOptions = useMemo(() => generateOptions(playersIndex, sizeIndex, fallingPieceModeIndex, winningLineLengthIndex), []) const [sizeOptions, setSizeOptions] = useState( initialGroupOptions.valueSize @@ -54,24 +62,31 @@ const useGameOptions = () => { initialGroupOptions.valueWinningLineLength ); + const [fallingPieceModeOptions, setFallingPieceModeIndexOptions] = useState( + initialGroupOptions.valueFallingPieceMode + ); + // reset function const resetOptions = () => { handleSizeToggle(0); handlePlayersToggle(0); handleWinningLineLengthToggle(0); + handleFallingPieceModeToggle(0); setSizeOptions(initialGroupOptions.valueSize); setPlayersOptions(initialGroupOptions.valuePlayers); setWinningLineLengthOptions(initialGroupOptions.valueWinningLineLength); + setFallingPieceModeIndexOptions(initialGroupOptions.valueFallingPieceMode); }; // update available options useEffect(() => { - const options = generateOptions(playersIndex, sizeIndex, winningLineLengthIndex); + const options = generateOptions(playersIndex, sizeIndex, fallingPieceModeIndex, winningLineLengthIndex); setSizeOptions(options.valueSize); setPlayersOptions(options.valuePlayers); setWinningLineLengthOptions(options.valueWinningLineLength); - }, [sizeIndex, playersIndex, winningLineLengthIndex]); + setFallingPieceModeIndexOptions(options.valueFallingPieceMode); + }, [sizeIndex, playersIndex, winningLineLengthIndex, fallingPieceModeIndex]); // construct options group object const optionsGroup: OptionsGroup = { @@ -87,12 +102,17 @@ const useGameOptions = () => { options: playersOptions, handler: handlePlayersToggle, }, - winningLineLength: { title: "Cantidad de piezas conectadas para ganar:", index: winningLineLengthIndex, options: winningLineLengthOptions, handler: handleWinningLineLengthToggle, + }, + fallingPieceMode: { + title: "Caida de piezas:", + index: fallingPieceModeIndex, + options: fallingPieceModeOptions, + handler: handleFallingPieceModeToggle, } } diff --git a/src/assets/ts/functions.ts b/src/assets/ts/functions.ts index df6eaa7..797e617 100644 --- a/src/assets/ts/functions.ts +++ b/src/assets/ts/functions.ts @@ -29,17 +29,30 @@ const generateAllTrueOptions = () => { return [value, true] }) + options.valueFallingPieceMode = + ["si", "no"].map((value) => { + return [value, true] + }) + return options } //TODO: replace to use a Json or javascript object to make it easier to extend configurations // @ts-ignore -export const generateOptions = (playersIndex: number, sizeIndex: number, winningLineLengthIndex: number) => { +export const generateOptions = ( + playersIndex: number, + sizeIndex: number, + fallingPieceModeIndex: number, + winningLineLengthIndex: number +) => { const options = generateAllTrueOptions() const valuePlayers = Number(options.valuePlayers[playersIndex]![0]) const valueSize = Number(options.valueSize[sizeIndex]![0]) - //const valueWinningLineLength = Number(options.valueWinningLineLength[winningLineLengthIndex]![0]) + // @ts-ignore + const valueWinningLineLength = Number(options.valueWinningLineLength[winningLineLengthIndex]![0]) + // @ts-ignore + const valueFallingPieceMode = Boolean(options.valueFallingPieceMode[fallingPieceModeIndex]![0]) options.valueSize = [ ["3", valuePlayers <= 3], @@ -61,6 +74,11 @@ export const generateOptions = (playersIndex: number, sizeIndex: number, winning ["5", valueSize >= 5], ]; + options.valueFallingPieceMode = [ + ["si", true], + ["no", true], + ]; + return options; }; @@ -78,6 +96,27 @@ export const checkTie = (board: BoardType): boolean => { return board.flat().every(square => square !== null); }; +export const piecesFalling = ( + board: BoardType, + piecePositionAbs: SizeDeclarationBoard +): { board: BoardType, indexs: SizeDeclarationBoard } => { + const pieceMain = board[piecePositionAbs[0]]![piecePositionAbs[1]] + + let iters = 1 + + while ( + board[(piecePositionAbs[0] + iters)] !== undefined + && board[(piecePositionAbs[0] + iters)]![piecePositionAbs[1]] === null + ) { + iters = iters + 1; + } + + board[piecePositionAbs[0]]![piecePositionAbs[1]] = null + board[(piecePositionAbs[0] + iters - 1)]![piecePositionAbs[1]] = pieceMain ?? null + + return { board, indexs: [(piecePositionAbs[0] + iters - 1), piecePositionAbs[1]] } +} + function get3x3GridOfABoard( board: BoardType, [initialRow, initialCol]: SizeDeclarationBoard @@ -142,7 +181,7 @@ export const checkWinner = ( piecePositionAbs: SizeDeclarationBoard, winningLineLength: number ): SizeDeclarationBoard[][] | null => { - + const bucket: BucketTypeN = { top: [], bottom: [], @@ -154,7 +193,10 @@ export const checkWinner = ( rightBottom: [], } - const mainPieceIndex: number = board[piecePositionAbs[0]]![piecePositionAbs[1]]! + const mainPiece: number = board[piecePositionAbs[0]]![piecePositionAbs[1]]! + + if (mainPiece === null || mainPiece === undefined) return null + const grid = get3x3GridOfABoard(board, piecePositionAbs) if (!grid) return null; @@ -165,7 +207,7 @@ export const checkWinner = ( if (indexLocalCol === 1 && indexLocalRow === 1) return; - if (borderingPiece !== mainPieceIndex) return; + if (borderingPiece !== mainPiece) return; const orientation: keyof PosMovN | null = searchInSchemaMovement([indexLocalRow, indexLocalCol]); @@ -180,7 +222,7 @@ export const checkWinner = ( bucket[orientation].push(piecePositionAbs) - while (currentPiece === mainPieceIndex) { + while (currentPiece === mainPiece) { bucket[orientation].push(currentPiecePositionAbs) currentPiecePositionAbs = [ diff --git a/src/assets/types/interfaces.ts b/src/assets/types/interfaces.ts index 4388a48..0e137a3 100644 --- a/src/assets/types/interfaces.ts +++ b/src/assets/types/interfaces.ts @@ -1,9 +1,25 @@ -import type { BoardType, PlayersType, SizeDeclarationBoard } from "@/assets/types/types" +import type { BoardType, PlayersType, SizeDeclarationBoard, WinnerType, WinningPositionsType } from "@/assets/types/types" +export interface IGameContext { + turn: number + winningLineLength: number + fallingPieceMode: boolean + winningPositions: WinningPositionsType + players: PlayersType + board: BoardType + winner: WinnerType + setTurn: (value: number) => void + setBoard: (value: BoardType) => void + setWinner: (value: WinnerType) => void + setWinningPositions: (value: WinningPositionsType) => void + resetGame: () => void +} + export interface IGame { size?: SizeDeclarationBoard, winningLineLength?: number, + fallingPieceMode?: boolean, players?: PlayersType, initialTurn?: number, disabledReset?: boolean, diff --git a/src/assets/types/types.ts b/src/assets/types/types.ts index ee58964..a77a357 100644 --- a/src/assets/types/types.ts +++ b/src/assets/types/types.ts @@ -17,8 +17,6 @@ export type BoardType = (number | null)[][] export type GridOfBoardType = (number | null | undefined)[][] -export type UpdateBoardType = (index: SizeDeclarationBoard) => void - export type OptionsToggle = [string, boolean][]; export type PosMovN = { @@ -46,6 +44,7 @@ export type BucketTypeN = { export type GenerateOptionsParams = { valuePlayers: number; valueSize: number, + valueFallingPieceMode: number, valueWinningLineLength: number }; diff --git a/src/components/react/Board.tsx b/src/components/react/Board.tsx index 3cd9fa7..e607d85 100644 --- a/src/components/react/Board.tsx +++ b/src/components/react/Board.tsx @@ -1,8 +1,8 @@ import Square from './Square' import { useCallback, useContext } from 'react' import GameContext from '@/assets/react/contexts/GameContext' -import { checkTie, checkWinner, createCopyBoard, nextIndex } from '@/assets/ts/functions' -import type { BoardType, SizeDeclarationBoard, UpdateBoardType } from '@/assets/types/types' +import { checkTie, checkWinner, createCopyBoard, nextIndex, piecesFalling } from '@/assets/ts/functions' +import type { BoardType, SizeDeclarationBoard } from '@/assets/types/types' import { sizeBoardColumnsVarCSS, sizeBoardRowsVarCSS, tieValue } from '@/assets/ts/constants' import WinnerMessage from './WinnerMessage' @@ -12,6 +12,7 @@ export default function Board() { players, winner, board, + fallingPieceMode, winningLineLength, winningPositions, setWinningPositions, @@ -23,17 +24,17 @@ export default function Board() { const gridTemplateRows = `repeat(var(${sizeBoardRowsVarCSS}), 2fr` const gridTemplateColumns = `repeat(var(${sizeBoardColumnsVarCSS}), 2fr` - const checkBoard = (indexs: [number, number], board: BoardType) => { - const pieceswinning = checkWinner(board, indexs, winningLineLength) + const checkBoard = (indexs: SizeDeclarationBoard, newBoard: BoardType) => { + const piecesWinning = checkWinner(newBoard, indexs, winningLineLength) - if (pieceswinning !== null) { - setWinningPositions(pieceswinning) + if (piecesWinning !== null) { + setWinningPositions(piecesWinning) setWinner(turn); setTurn(turn); return; } - if (checkTie(board)) { + if (checkTie(newBoard)) { setWinner(tieValue); setTurn(tieValue); return; @@ -50,15 +51,20 @@ export default function Board() { ); }; - const updateBoard: UpdateBoardType = useCallback((indexs) => { - const boardPos = board[indexs[0]]?.[indexs[1]] + const updateBoard = useCallback((indexs: SizeDeclarationBoard) => { + let newIndexs = indexs + const boardCurrentPiece = board[newIndexs[0]]?.[newIndexs[1]] // evaluate if there is a piece in the square or if there is a winner - if (boardPos || boardPos === undefined || winner) return; - - const newBoard = createCopyBoard(board) - - newBoard[indexs[0]]![indexs[1]] = turn + if (boardCurrentPiece || boardCurrentPiece === undefined || winner) return; + + let newBoard = createCopyBoard(board) + + newBoard[newIndexs[0]]![newIndexs[1]] = turn + + if (fallingPieceMode) { + ({ board: newBoard, indexs: newIndexs } = piecesFalling(newBoard, newIndexs)); + } setBoard(newBoard) @@ -66,8 +72,8 @@ export default function Board() { nextIndex(turn, players) ) - checkBoard(indexs, newBoard) - }, [turn, winner, board, players]) + checkBoard(newIndexs, newBoard) + }, [turn, winner, board, players, checkBoard]) return (
size.handler(index); const handlePlayersToggle = (index: number) => players.handler(index); const handleWinningLineLengthToggle = (index: number) => winningLineLength.handler(index); + const handleFallingPieceModeToggle = (index: number) => fallingPieceMode.handler(index); const newOptionsGroup: OptionsGroup = { size: { ...size, handler: handleSizeToggle }, players: { ...players, handler: handlePlayersToggle }, winningLineLength: { ...winningLineLength, handler: handleWinningLineLengthToggle }, + fallingPieceMode: { ...fallingPieceMode, handler: handleFallingPieceModeToggle }, }; useEffect(() => { const invalidSize = !size.options[size.index]?.[1]; const invalidPlayers = !players.options[players.index]?.[1]; const invalidWinningLineLength = !winningLineLength.options[winningLineLength.index]?.[1]; + const invalidFallingPieceMode = !fallingPieceMode.options[fallingPieceMode.index]?.[1]; - if (invalidSize || invalidPlayers || invalidWinningLineLength) { + if (invalidSize || invalidPlayers || invalidWinningLineLength || invalidFallingPieceMode) { setButtonStart(true) } else { setButtonStart(false) @@ -39,7 +42,9 @@ export default function ConstructorGame() { JSON.stringify(players.options), players.index, JSON.stringify(winningLineLength.options), - winningLineLength.index + winningLineLength.index, + JSON.stringify(fallingPieceMode.options), + fallingPieceMode.index, ]); const onStart = () => { @@ -50,6 +55,7 @@ export default function ConstructorGame() { size: [Number(size.options[size.index]![0]), Number(size.options[size.index]![0])], players: defaultPlayers.slice(0, Number(players.options[players.index]![0])), winningLineLength: Number(winningLineLength.options[winningLineLength.index]![0]), + fallingPieceMode: fallingPieceMode.options[fallingPieceMode.index]![0] === "no" ? false : true }; return ( diff --git a/src/components/react/Game.tsx b/src/components/react/Game.tsx index 2158a49..01f1a11 100644 --- a/src/components/react/Game.tsx +++ b/src/components/react/Game.tsx @@ -14,6 +14,7 @@ export default function Game({ winningLineLength = 3, players = defaultPlayers, initialTurn = randomTurns(players), + fallingPieceMode = false, disabledReset = false, board }: IGame) { @@ -54,6 +55,7 @@ export default function Game({ setBoard, players, winningPositions, + fallingPieceMode, winner, winningLineLength, turn: turnState,