add GamePage
This commit is contained in:
parent
25fa771254
commit
44090516e2
@ -1,17 +1,243 @@
|
||||
import { CommonMancalaGame, MancalaGame, Pit } from 'mancala.js';
|
||||
import * as React from 'react';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { FunctionComponent, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { v4 } from 'uuid';
|
||||
import PitAnimator from '../animation/PitAnimator';
|
||||
import BoardToolbar from '../components/board/BoardToolbar';
|
||||
import BoardView from '../components/board/BoardView';
|
||||
import Button from '../components/Button';
|
||||
import HeaderBar from '../components/headerbar/HeaderBar';
|
||||
import HeaderbarIcon from '../components/headerbar/HeaderbarIcon';
|
||||
import HeaderbarTitle from '../components/headerbar/HeaderbarTitle';
|
||||
import ThemeSwitchMenu from '../components/headerbar/ThemeSwitchMenu';
|
||||
import InfoPanel from '../components/InfoPanel';
|
||||
import LoadingComponent from '../components/LoadingComponent';
|
||||
import PageContainer from '../components/PageContainer';
|
||||
import Row from '../components/Row';
|
||||
import UserStatus from '../components/UserStatus';
|
||||
import { channel_on_game_update, channel_on_game_crashed, channel_on_game_user_leave, channel_on_user_connection_change, channel_leave_game, channel_game_move } from '../const/channel_names';
|
||||
import { Context } from '../context/context';
|
||||
import useWindowDimensions from '../hooks/useWindowDimensions';
|
||||
import { ConnectionState } from '../models/ConnectionState';
|
||||
import { GameMove } from '../models/GameMove';
|
||||
import { LoadingState } from '../models/LoadingState';
|
||||
import { UserConnectionInfo } from '../models/UserConnectionInfo';
|
||||
import { Theme } from '../theme/Theme';
|
||||
import { getColorByBrightness } from '../util/ColorUtil';
|
||||
import BoardViewModel from '../viewmodel/BoardViewModel';
|
||||
import Center from '../components/Center';
|
||||
|
||||
const GamePage: FunctionComponent<{
|
||||
context: Context,
|
||||
userKey?: string,
|
||||
theme: Theme,
|
||||
connectionState: ConnectionState
|
||||
}> = ({ context, userKey, theme, connectionState }) => {
|
||||
let params = useParams<{ gameId: string }>();
|
||||
|
||||
const [game, setGame] = useState<MancalaGame | undefined>(undefined);
|
||||
|
||||
const [crashMessage, setCrashMessage] = useState<string | undefined>(undefined);
|
||||
|
||||
const [userKeyWhoLeave, setUserKeyWhoLeave] = useState<string | undefined>(undefined);
|
||||
|
||||
const [boardViewModel, setBoardViewModel] = useState<BoardViewModel | undefined>(undefined);
|
||||
|
||||
const [boardId, setBoardId] = useState<string>("-1");
|
||||
|
||||
const [pitAnimator, setPitAnimator] = useState<PitAnimator | undefined>(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<boolean>(false);
|
||||
|
||||
const [isOpponentOnline, setIsOpponentOnline] = useState<boolean>(false);
|
||||
|
||||
const { height, width } = useWindowDimensions();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [gameLoadingState, setLoadingStateGame] = useState<LoadingState<MancalaGame>>(LoadingState.Unset());
|
||||
|
||||
const onGameUpdate = (pitAnimator: PitAnimator, message: Object) => {
|
||||
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
||||
const mancalaGame = MancalaGame.createFromMancalaGame(newGame);
|
||||
setGame(mancalaGame);
|
||||
pitAnimator.setUpdatedGame(mancalaGame);
|
||||
setHasOngoingAction(false);
|
||||
}
|
||||
const onGameCrashed = (message: any) => {
|
||||
const newCrashMessage = message as string;
|
||||
console.error("on_game_crash");
|
||||
console.error(newCrashMessage);
|
||||
setCrashMessage(newCrashMessage);
|
||||
}
|
||||
const onGameUserLeave = (message: any) => {
|
||||
const userKeyWhoLeave = message;
|
||||
setUserKeyWhoLeave(userKeyWhoLeave);
|
||||
setHasOngoingAction(false);
|
||||
};
|
||||
const onUserConnectionChange = (message: any) => {
|
||||
const userConnectionInfo = message as UserConnectionInfo;
|
||||
//todo: change this when implementing watch the game feature
|
||||
setIsOpponentOnline(userConnectionInfo.isOnline);
|
||||
};
|
||||
|
||||
const listenMessages = (pitAnimator: PitAnimator) : () => void => {
|
||||
const _onGameUpdate = (message: object) => onGameUpdate(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);
|
||||
context.rtmt.listenMessage(channel_on_user_connection_change, onUserConnectionChange)
|
||||
return () => {
|
||||
context.rtmt.unlistenMessage(channel_on_game_update, _onGameUpdate);
|
||||
context.rtmt.unlistenMessage(channel_on_game_crashed, onGameCrashed);
|
||||
context.rtmt.unlistenMessage(channel_on_game_user_leave, onGameUserLeave);
|
||||
context.rtmt.unlistenMessage(channel_on_user_connection_change, onUserConnectionChange);
|
||||
}
|
||||
};
|
||||
|
||||
const updateBoardViewModel = (boardViewModel: BoardViewModel) => {
|
||||
boardViewModel.id = v4();
|
||||
setBoardId(boardViewModel.id);
|
||||
setBoardViewModel(boardViewModel);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
const onLeaveGameClick = () => {
|
||||
context.rtmt.sendMessage(channel_leave_game, {});
|
||||
};
|
||||
|
||||
const onNewGameClick = () => {
|
||||
navigate("/loby")
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
let pitAnimator: PitAnimator | undefined;
|
||||
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);
|
||||
unlistenMessages = listenMessages(pitAnimator);
|
||||
setLoadingStateGame(LoadingState.Loaded({ value: game }))
|
||||
} else {
|
||||
setLoadingStateGame(LoadingState.Error({ errorMessage: context.texts.GameNotFound }))
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
unlistenMessages?.();
|
||||
pitAnimator?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
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;
|
||||
|
||||
const GamePage: FunctionComponent<{ context: Context }> = ({ context }) => {
|
||||
let params = useParams<{gameId : string}>();
|
||||
return (
|
||||
<div>
|
||||
Game Route {params.gameId}
|
||||
<style jsx>{`
|
||||
|
||||
`}</style>
|
||||
</div>
|
||||
<PageContainer theme={theme!}>
|
||||
<HeaderBar color={theme?.appBarBgColor}>
|
||||
<Row>
|
||||
<Link style={{ textDecoration: 'none' }} to={"/"}>
|
||||
<HeaderbarIcon />
|
||||
</Link>
|
||||
<Link style={{ textDecoration: 'none' }} to={"/"}>
|
||||
<HeaderbarTitle title={context.texts.Mancala} color={textColorOnAppBar} />
|
||||
</Link>
|
||||
</Row>
|
||||
<Row>
|
||||
<ThemeSwitchMenu context={context} textColor={textColorOnAppBar} />
|
||||
<Button
|
||||
context={context}
|
||||
color={context.themeManager.theme.pitColor}
|
||||
text={renderNewGameBtn ? context.texts.NewGame : context.texts.Leave}
|
||||
onClick={renderNewGameBtn ? onNewGameClick : onLeaveGameClick} />
|
||||
</Row>
|
||||
</HeaderBar>
|
||||
<BoardToolbar style={{ justifyContent: "center" }} visible={showBoardView && isMobile || false}>
|
||||
<InfoPanel
|
||||
style={{ marginTop: "0.5rem", marginBottom: "0.5rem" }}
|
||||
context={context}
|
||||
game={game}
|
||||
crashMessage={crashMessage}
|
||||
userKey={userKey}
|
||||
userKeyWhoLeave={userKeyWhoLeave} />
|
||||
</BoardToolbar>
|
||||
<BoardToolbar style={{ alignItems: "flex-end" }} visible={showBoardView || false}>
|
||||
<UserStatus style={{
|
||||
marginBottom: "0.5rem", marginLeft: "6%", maxWidth: isMobile ? "40vw" : "30vw",
|
||||
width: isMobile ? "40vw" : "30vw"
|
||||
}} context={context} layoutMode="left" user={opponentUser} visible={showBoardView || false} />
|
||||
<InfoPanel
|
||||
style={{
|
||||
marginTop: "0.5rem", marginBottom: "0.5rem",
|
||||
}}
|
||||
context={context}
|
||||
game={game}
|
||||
crashMessage={crashMessage}
|
||||
userKey={userKey}
|
||||
userKeyWhoLeave={userKeyWhoLeave}
|
||||
visible={!isMobile} />
|
||||
<UserStatus style={{
|
||||
marginBottom: "0.5rem", marginRight: "6%", maxWidth: isMobile ? "40vw" : "30vw",
|
||||
width: isMobile ? "40vw" : "30vw"
|
||||
}} context={context} layoutMode="right" user={user} visible={showBoardView || false} />
|
||||
</BoardToolbar>
|
||||
{showBoardView && (
|
||||
<BoardView
|
||||
userKey={userKey}
|
||||
game={game}
|
||||
boardId={boardId}
|
||||
boardViewModel={boardViewModel}
|
||||
context={context}
|
||||
onPitSelect={onPitSelect} />
|
||||
)}
|
||||
<Center>
|
||||
<LoadingComponent context={context} loadingState={gameLoadingState}></LoadingComponent>
|
||||
</Center>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user