import * as React from "react"; import { FunctionComponent, useEffect, useState } from "react"; import BoardView from "../components/board/BoardView"; import { RTMTWS } from "../rtmt/rtmt_websocket"; import { channel_game_move, channel_leave_game, channel_on_game_crashed, channel_on_game_start, channel_on_game_update, channel_on_game_user_leave, channel_on_user_connection_change, } from "../const/channel_names"; import InfoPanel from "../components/InfoPanel"; import { CommonMancalaGame, MancalaGame, Pit } from "mancala.js"; import { GameMove } from "../models/GameMove"; import PitAnimator from "../animation/PitAnimator"; import BoardViewModel from "../viewmodel/BoardViewModel"; import { v4 } from "uuid"; import { getColorByBrightness } from "../util/ColorUtil"; import { Theme } from "../theme/Theme"; import HeaderBar from "../components/headerbar/HeaderBar"; import PageContainer from "../components/PageContainer"; import Row from "../components/Row"; import HeaderbarIcon from "../components/headerbar/HeaderbarIcon"; import HeaderbarTitle from "../components/headerbar/HeaderbarTitle"; import ThemeSwitchMenu from "../components/headerbar/ThemeSwitchMenu"; import Button from "../components/Button"; import BoardToolbar from "../components/board/BoardToolbar"; import UserStatus from "../components/UserStatus"; import Center from "../components/Center"; import CircularPanel from "../components/CircularPanel"; import useWindowDimensions from "../hooks/useWindowDimensions"; import { UserConnectionInfo } from "../models/UserConnectionInfo"; import { Context } from "../context/context"; import { ConnectionState } from "../models/ConnectionState"; const Home: FunctionComponent<{ context: Context, userKey?: string, connectionState: ConnectionState }> = ({ context, userKey, connectionState }) => { const [searchingOpponent, setSearchingOpponent] = useState(false); const [game, setGame] = useState(undefined); const [crashMessage, setCrashMessage] = useState(undefined); const [userKeyWhoLeave, setUserKeyWhoLeave] = useState(undefined); const [boardViewModel, setBoardViewModel] = useState(undefined); const [boardId, setBoardId] = useState("-1"); const [pitAnimator, setPitAnimator] = useState(undefined); const [theme, setTheme] = useState(undefined); // It is a flag for ongoing action such as send game move. // We have to block future actions if there is an ongoing action. const [hasOngoingAction, setHasOngoingAction] = useState(false); const { height, width } = useWindowDimensions(); const [isOpponentOnline, setIsOpponentOnline] = useState(false); const listenMessages = (pitAnimator: PitAnimator) => { context.rtmt.listenMessage(channel_on_game_start, (message: Object) => { const newGame: CommonMancalaGame = message as CommonMancalaGame; const mancalaGame = MancalaGame.createFromMancalaGame(newGame); setSearchingOpponent(false); setGame(mancalaGame); pitAnimator.setNewGame(mancalaGame); setHasOngoingAction(false); }); context.rtmt.listenMessage(channel_on_game_update, (message: Object) => { const newGame: CommonMancalaGame = message as CommonMancalaGame; const mancalaGame = MancalaGame.createFromMancalaGame(newGame); setGame(mancalaGame); pitAnimator.setUpdatedGame(mancalaGame); setHasOngoingAction(false); }); context.rtmt.listenMessage(channel_on_game_crashed, (message: any) => { const newCrashMessage = message as string; console.error("on_game_crash"); console.error(newCrashMessage); setCrashMessage(newCrashMessage); }); context.rtmt.listenMessage(channel_on_game_user_leave, (message: any) => { const userKeyWhoLeave = message; setUserKeyWhoLeave(userKeyWhoLeave); setHasOngoingAction(false); }); context.rtmt.listenMessage(channel_on_user_connection_change, (message: any) => { const userConnectionInfo = message as UserConnectionInfo; //todo: change this when implementing watch the game feature setIsOpponentOnline(userConnectionInfo.isOnline); }) }; const updateBoardViewModel = (boardViewModel: BoardViewModel) => { boardViewModel.id = v4(); setBoardId(boardViewModel.id); setBoardViewModel(boardViewModel); }; const resetGameState = () => { setGame(undefined); setCrashMessage(undefined); setUserKeyWhoLeave(undefined); setHasOngoingAction(false); }; const getBoardIndex = (index: number) => { if (!game) return -1; if (userKey === game.player2Id) return index + game.board.pits.length / 2; return index; }; const getOpponentId = () => game?.player1Id === userKey ? game?.player2Id : game?.player1Id; const checkHasAnOngoingAction = () => hasOngoingAction; React.useEffect(() => { setTheme(context.themeManager.theme); const pitAnimator = new PitAnimator(context, updateBoardViewModel); setPitAnimator(pitAnimator); listenMessages(pitAnimator); return () => { pitAnimator.dispose(); }; }, []); React.useEffect(() => { context.themeManager.onThemeChange = (theme) => { setTheme(theme); pitAnimator && game && updateBoardViewModel(pitAnimator.getBoardViewModelFromGame(game)); }; }, [boardViewModel]); const onNewGameClick = () => { resetGameState(); setSearchingOpponent(true); context.rtmt.sendMessage("new_game", {}); }; const onLeaveGameClick = () => { context.rtmt.sendMessage(channel_leave_game, {}); }; const onPitSelect = (index: number, pit: Pit) => { if (checkHasAnOngoingAction()) { return; } setHasOngoingAction(true); if (!boardViewModel) return; //TODO : stoneCount comes from view model! if (pit.stoneCount === 0) { //TODO : warn user return; } boardViewModel.pits[getBoardIndex(index)].pitColor = context.themeManager.theme.pitSelectedColor; updateBoardViewModel(boardViewModel); const gameMove: GameMove = { index: index }; context.rtmt.sendMessage(channel_game_move, gameMove); }; const textColorOnBoard = getColorByBrightness( context.themeManager.theme.boardColor, context.themeManager.theme.textColor, context.themeManager.theme.textLightColor ); const textColorOnAppBar = getColorByBrightness( context.themeManager.theme.appBarBgColor, context.themeManager.theme.textColor, context.themeManager.theme.textLightColor ); const renderNewGameBtn = userKeyWhoLeave || !game || (game && game.state == "ended"); const showBoardView = game && boardViewModel && userKey && true; const opponentUser = { id: getOpponentId() || "0", name: "Anonymous", isOnline: isOpponentOnline, isAnonymous: true }; const user = { id: userKey || "1", name: "Anonymous", isOnline: connectionState === "connected", isAnonymous: true }; const isMobile = width < 600; return (