diff --git a/src/animation/PitAnimator.ts b/src/animation/PitAnimator.ts index 2f4d442..4db6dc3 100644 --- a/src/animation/PitAnimator.ts +++ b/src/animation/PitAnimator.ts @@ -1,5 +1,4 @@ import { - MancalaGame, GameStep, HistoryItem, GAME_STEP_GAME_MOVE, @@ -7,11 +6,13 @@ import { GAME_STEP_BOARD_CLEARED, GAME_STEP_LAST_STONE_IN_BANK, GAME_STEP_DOUBLE_STONE_IN_PIT, + MancalaGame, } from "mancala.js"; import { v4 } from "uuid"; import { Context } from "../context/context"; import BoardViewModelFactory from "../factory/BoardViewModelFactory"; import { PitViewModelFactory } from "../factory/PitViewModelFactory"; +import { Game } from "../models/Game"; import { getColorByBrightness } from "../util/ColorUtil"; import BoardViewModel from "../viewmodel/BoardViewModel"; @@ -19,8 +20,8 @@ const animationUpdateInterval = 300; export default class PitAnimator { context: Context; - game: MancalaGame | undefined; - oldGame: MancalaGame | undefined; + game: Game | undefined; + oldGame: Game | undefined; currentIntervalID: number; onBoardViewModelUpdate: (boardViewModel: BoardViewModel) => void; boardViewModel: BoardViewModel | undefined; @@ -36,13 +37,17 @@ export default class PitAnimator { this.onBoardViewModelUpdate = onBoardViewModelUpdate; } - public setNewGame(game: MancalaGame) { + get mancalaGame(): MancalaGame | undefined { + return this.game?.mancalaGame; + } + + public setNewGame(game: Game) { this.reset(); this.game = game; this.onBoardViewModelUpdate?.(this.getBoardViewModelFromGame(this.game)); } - public setUpdatedGame(game: MancalaGame) { + public setUpdatedGame(game: Game) { this.resetAnimationState(); if (!this.game) { this.setNewGame(game); @@ -55,8 +60,8 @@ export default class PitAnimator { onGameMoveAnimationStart() { this.stopCurrentAnimation(); - if (this.game && this.oldGame && this.game.history.length > 0) { - const lastHistoryItem = this.game.history[this.game.history.length - 1]; + if (this.game && this.oldGame && this.mancalaGame && this.mancalaGame?.history.length > 0) { + const lastHistoryItem = this.mancalaGame.history[this.mancalaGame.history.length - 1]; if (lastHistoryItem.gameSteps.length > 0) { this.animationIndex = 0; this.currentHistoryItem = lastHistoryItem; @@ -68,13 +73,13 @@ export default class PitAnimator { } onAnimate() { - if (!this.currentHistoryItem || !this.game || !this.oldBoardViewModel) return; + if (!this.currentHistoryItem || !this.game || !this.oldBoardViewModel || !this.mancalaGame) return; if (this.animationIndex === this.currentHistoryItem.gameSteps.length) { this.clearCurrentInterval(); this.onBoardViewModelUpdate?.(this.getBoardViewModelFromGame(this.game)); } else { const gameStep = this.currentHistoryItem.gameSteps[this.animationIndex]; - const index = this.game.board.getPitIndexCircularly(gameStep.index); + const index = this.mancalaGame.board.getPitIndexCircularly(gameStep.index); this.animatePit(index, this.oldBoardViewModel, gameStep); this.onBoardViewModelUpdate?.(this.oldBoardViewModel); } @@ -92,7 +97,7 @@ export default class PitAnimator { boardViewModel: BoardViewModel, gameStep: GameStep ) { - if (!this.currentHistoryItem || !this.game) return; + if (!this.currentHistoryItem || !this.game || !this.mancalaGame) return; const pitViewModel = boardViewModel.pits[index]; if (this.animationIndex === 0) { //This is one stone move case, TODO: beautify it later @@ -112,7 +117,7 @@ export default class PitAnimator { } else if (gameStep.type === GAME_STEP_LAST_STONE_IN_EMPTY_PIT) { pitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor; pitViewModel.stoneCount = 0; - const oppositeIndex = this.game.board.getPitIndexCircularly( + const oppositeIndex = this.mancalaGame.board.getPitIndexCircularly( gameStep.data.oppositeIndex ); const oppositePitViewModel = boardViewModel.pits[oppositeIndex]; @@ -122,13 +127,13 @@ export default class PitAnimator { pitViewModel.pitColor = theme.pitLastStoneInBankPitAnimateColor; } else if (gameStep.type === GAME_STEP_BOARD_CLEARED) { for (const index of gameStep.data.pitIndexesThatHasStone) { - const oppositeIndex = this.game.board.getPitIndexCircularly(index); + const oppositeIndex = this.mancalaGame.board.getPitIndexCircularly(index); const oppositePitViewModel = boardViewModel.pits[oppositeIndex]; oppositePitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor; oppositePitViewModel.stoneCount = 0; } } else if (gameStep.type === GAME_STEP_DOUBLE_STONE_IN_PIT) { - const _index = this.game.board.getPitIndexCircularly(index); + const _index = this.mancalaGame.board.getPitIndexCircularly(index); const pitViewModel = boardViewModel.pits[_index]; pitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor; pitViewModel.stoneCount = 0; @@ -164,13 +169,13 @@ export default class PitAnimator { } } - public getBoardViewModelFromGame(game: MancalaGame): BoardViewModel { + public getBoardViewModelFromGame(game: Game): BoardViewModel { const pitViewModels = this.createPitViewModelsFromGame(game); return BoardViewModelFactory.create(v4(), pitViewModels); } - private createPitViewModelsFromGame(game: MancalaGame) { - return game.board.pits.map((pit) => { + private createPitViewModelsFromGame(game: Game) { + return game.mancalaGame.board.pits.map((pit) => { const theme = this.context.themeManager.theme; const stoneCount = pit.stoneCount; const stoneColor = theme.stoneColor; diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx index 1bb966b..850414b 100644 --- a/src/components/InfoPanel.tsx +++ b/src/components/InfoPanel.tsx @@ -1,13 +1,13 @@ -import { MancalaGame } from "mancala.js"; import * as React from "react"; import { FunctionComponent } from "react"; import { Context } from "../context/context"; +import { Game } from "../models/Game"; import { getColorByBrightness } from "../util/ColorUtil"; import CircularPanel from "./CircularPanel"; function getInfoPanelTextByGameState(params: { context: Context; - game?: MancalaGame; + game?: Game; crashMessage?: string; userKey?: string; userKeyWhoLeave?: string; @@ -27,10 +27,10 @@ function getInfoPanelTextByGameState(params: { message = context.texts.YouLeftTheGame; } return message; - } else if (game?.state == "ended") { - const wonPlayer = game.getWonPlayerId(); + } else if (game?.mancalaGame.state == "ended") { + const wonPlayer = game.mancalaGame.getWonPlayerId(); let whoWon = - game.getWonPlayerId() === userKey + game.mancalaGame.getWonPlayerId() === userKey ? context.texts.YouWon : context.texts.YouLost; if (!wonPlayer) { @@ -39,7 +39,7 @@ function getInfoPanelTextByGameState(params: { return context.texts.GameEnded + " " + whoWon; } else { if (game) { - return userKey ? game.checkIsPlayerTurn(userKey) + return userKey ? game.mancalaGame.checkIsPlayerTurn(userKey) ? context.texts.YourTurn : context.texts.OpponentTurn : undefined; } @@ -49,7 +49,7 @@ function getInfoPanelTextByGameState(params: { const InfoPanel: FunctionComponent<{ context: Context; - game?: MancalaGame; + game?: Game; crashMessage?: string; userKey?: string; userKeyWhoLeave?: string; @@ -65,7 +65,7 @@ const InfoPanel: FunctionComponent<{ visible }) => { if (visible === false) return <>; - const isUserTurn = userKey ? game?.checkIsPlayerTurn(userKey) : false; + const isUserTurn = userKey ? game?.mancalaGame.checkIsPlayerTurn(userKey) : false; const containerColor = isUserTurn ? context.themeManager.theme.playerTurnColor : context.themeManager.theme.boardColor; diff --git a/src/components/board/BoardView.tsx b/src/components/board/BoardView.tsx index eb3af16..3da5925 100644 --- a/src/components/board/BoardView.tsx +++ b/src/components/board/BoardView.tsx @@ -1,43 +1,45 @@ import * as React from "react"; -import { MancalaGame, Pit } from "mancala.js"; import { FunctionComponent } from "react"; import { Context } from "../../context/context"; import BoardViewModel from "../../viewmodel/BoardViewModel"; import PitViewModel from "../../viewmodel/PitViewModel"; import PitView from "./PitView"; import StoreView from "./StoreView"; +import { Game } from "../../models/Game"; +import { Pit } from "mancala.js"; const BoardView: FunctionComponent<{ - game: MancalaGame; + game: Game; context: Context; boardId: string; boardViewModel: BoardViewModel; userKey: string; onPitSelect: (index: number, pit: Pit) => void; }> = ({ game, context, boardId, boardViewModel, userKey, onPitSelect: onPitSelect }) => { + const mancalaGame = game?.mancalaGame; const createPitView = (key: any, pitViewModel: PitViewModel, onClick: () => void) => { return ; }; - const player1Pits = game?.board.player1Pits.map((pit, index) => { + const player1Pits = mancalaGame?.board.player1Pits.map((pit, index) => { const pitViewModel = boardViewModel.pits[pit.index]; return createPitView(index, pitViewModel, () => { - if (game.turnPlayerId === game.player1Id && userKey === game.player1Id) - onPitSelect(game.board.player1Pits.indexOf(pit), pit); + if (mancalaGame?.turnPlayerId === mancalaGame?.player1Id && userKey === mancalaGame?.player1Id) + onPitSelect(mancalaGame?.board.player1Pits.indexOf(pit), pit); }); }); - const player2Pits = game?.board.player2Pits.map((pit, index) => { + const player2Pits = mancalaGame?.board.player2Pits.map((pit, index) => { const pitViewModel = boardViewModel.pits[pit.index]; return createPitView(index, pitViewModel, () => { - if (game.turnPlayerId === game.player2Id && userKey === game.player2Id) - onPitSelect(game.board.player2Pits.indexOf(pit), pit); + if (mancalaGame?.turnPlayerId === mancalaGame?.player2Id && userKey === mancalaGame?.player2Id) + onPitSelect(mancalaGame?.board.player2Pits.indexOf(pit), pit); }); }); const theme = context.themeManager.theme; const player1BankViewModel = - boardViewModel.pits[game.board.player1BankIndex()]; + boardViewModel.pits[mancalaGame?.board.player1BankIndex()]; const player2BankViewModel = - boardViewModel.pits[game.board.player2BankIndex()]; - const isPlayer2 = userKey === game?.player2Id; + boardViewModel.pits[mancalaGame?.board.player2BankIndex()]; + const isPlayer2 = userKey === mancalaGame?.player2Id; return (
= ({ context, userKey, theme, connectionState }) => { let params = useParams<{ gameId: string }>(); - const [game, setGame] = useState(undefined); + const [game, setGame] = useState(undefined); const [crashMessage, setCrashMessage] = useState(undefined); @@ -53,20 +53,36 @@ const GamePage: FunctionComponent<{ // We have to block future actions if there is an ongoing action. const [hasOngoingAction, setHasOngoingAction] = useState(false); - const [isOpponentOnline, setIsOpponentOnline] = useState(false); + const [gameUsersConnectionInfo, setGameUsersConnectionInfo] = useState(); const { height, width } = useWindowDimensions(); const navigate = useNavigate(); - const [gameLoadingState, setLoadingStateGame] = useState>(LoadingState.Unset()); + const [gameLoadingState, setLoadingStateGame] = useState>(LoadingState.Unset()); - const onGameUpdate = (pitAnimator: PitAnimator, message: Object) => { - const newGame: CommonMancalaGame = message as CommonMancalaGame; - const mancalaGame = MancalaGame.createFromMancalaGame(newGame); - setGame(mancalaGame); - pitAnimator.setUpdatedGame(mancalaGame); + const mancalaGame: MancalaGame | undefined = game?.mancalaGame; + + const onGameUpdate = (pitAnimator: PitAnimator, newGame: Game) => { + setGame(newGame); + pitAnimator.setUpdatedGame(newGame); setHasOngoingAction(false); + setGameUsersConnectionInfo(newGame.gameUsersConnectionInfo); + } + + const isUserOnline = (userId: string) => { + if (!gameUsersConnectionInfo) return false; + const user1ConnectionInfo = gameUsersConnectionInfo.user1ConnectionInfo; + const user2ConnectionInfo = gameUsersConnectionInfo.user2ConnectionInfo; + if (user1ConnectionInfo.userId === userId) return user1ConnectionInfo.isOnline; + if (user2ConnectionInfo.userId === userId) return user2ConnectionInfo.isOnline; + return false; + } + + const onGameUpdateEvent = (pitAnimator: PitAnimator, message: Object) => { + const newGame: Game = message as Game; + newGame.mancalaGame = MancalaGame.createFromMancalaGame(newGame.mancalaGame); + onGameUpdate(pitAnimator, newGame); } const onGameCrashed = (message: any) => { const newCrashMessage = message as string; @@ -80,13 +96,12 @@ const GamePage: FunctionComponent<{ setHasOngoingAction(false); }; const onUserConnectionChange = (message: any) => { - const userConnectionInfo = message as UserConnectionInfo; - //todo: change this when implementing watch the game feature - setIsOpponentOnline(userConnectionInfo.isOnline); + const gameUsersConnectionInfo = message as GameUsersConnectionInfo; + setGameUsersConnectionInfo(gameUsersConnectionInfo); }; - const listenMessages = (pitAnimator: PitAnimator) : () => void => { - const _onGameUpdate = (message: object) => onGameUpdate(pitAnimator, message); + const listenMessages = (pitAnimator: PitAnimator): () => void => { + const _onGameUpdate = (message: object) => onGameUpdateEvent(pitAnimator, message); context.rtmt.listenMessage(channel_on_game_update, _onGameUpdate); context.rtmt.listenMessage(channel_on_game_crashed, onGameCrashed); context.rtmt.listenMessage(channel_on_game_user_leave, onGameUserLeave); @@ -106,12 +121,13 @@ const GamePage: FunctionComponent<{ }; const getBoardIndex = (index: number) => { - if (!game) return -1; - if (userKey === game.player2Id) return index + game.board.pits.length / 2; + if (!game || !mancalaGame) return -1; + const pitsLenght = mancalaGame.board.pits.length; + if (userKey === mancalaGame.player2Id) return index + pitsLenght / 2; return index; }; - const getOpponentId = () => game?.player1Id === userKey ? game?.player2Id : game?.player1Id; + const getOpponentId = () => mancalaGame?.player1Id === userKey ? mancalaGame?.player2Id : mancalaGame?.player1Id; const checkHasAnOngoingAction = () => hasOngoingAction; @@ -143,15 +159,13 @@ const GamePage: FunctionComponent<{ React.useEffect(() => { let pitAnimator: PitAnimator | undefined; - let unlistenMessages: ()=>void; + let unlistenMessages: () => void; setLoadingStateGame(LoadingState.Loading()) context.gameStore.get(params.gameId!!).then((game) => { if (game) { - setGame(game); - setHasOngoingAction(false); pitAnimator = new PitAnimator(context, updateBoardViewModel); - pitAnimator.setNewGame(game); setPitAnimator(pitAnimator); + onGameUpdate(pitAnimator, game); unlistenMessages = listenMessages(pitAnimator); setLoadingStateGame(LoadingState.Loaded({ value: game })) } else { @@ -169,9 +183,10 @@ const GamePage: FunctionComponent<{ context.themeManager.theme.textColor, context.themeManager.theme.textLightColor ); - const renderNewGameBtn = userKeyWhoLeave || !game || (game && game.state == "ended"); + const renderNewGameBtn = userKeyWhoLeave || !game || (game && game.mancalaGame.state == "ended"); const showBoardView = game && boardViewModel && userKey && true; - const opponentUser = { id: getOpponentId() || "0", name: "Anonymous", isOnline: isOpponentOnline, isAnonymous: true }; + const opponentId = getOpponentId(); + const opponentUser = { id: getOpponentId() || "0", name: "Anonymous", isOnline: opponentId ? isUserOnline(opponentId) : false, isAnonymous: true }; const user = { id: userKey || "1", name: "Anonymous", isOnline: connectionState === "connected", isAnonymous: true }; const isMobile = width < 600; diff --git a/src/store/GameStore.ts b/src/store/GameStore.ts index 66f4c6c..fc30033 100644 --- a/src/store/GameStore.ts +++ b/src/store/GameStore.ts @@ -1,8 +1,9 @@ import { CommonMancalaGame, MancalaGame } from "mancala.js"; +import { Game } from "../models/Game"; import { HttpService } from "../service/HttpService"; export interface GameStore { - get(id: string): Promise; + get(id: string): Promise; } export class GameStoreImpl implements GameStore { @@ -11,11 +12,13 @@ export class GameStoreImpl implements GameStore { this.httpService = props.httpService; } - async get(id: string): Promise { + async get(id: string): Promise { try { const response = await this.httpService.get(`/game/${id}`); const json = await response.json(); - return MancalaGame.createFromMancalaGame(json as CommonMancalaGame); + const game: Game = json as Game; + game.mancalaGame = MancalaGame.createFromMancalaGame(game.mancalaGame); + return game; } catch (error) { // todo check error Promise.resolve(undefined);