2024-03-24 14:23:08 +03:00
|
|
|
import * as React from 'react';
|
2024-03-31 17:08:59 +03:00
|
|
|
import { View, Text, useWindowDimensions, Alert, Pressable } from 'react-native';
|
2024-03-24 14:23:08 +03:00
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
|
import { GameScreenProps } from '../types';
|
2024-03-27 21:48:48 +03:00
|
|
|
import { useState } from 'react';
|
|
|
|
|
import { Game, GameUsersConnectionInfo } from '../models/Game';
|
|
|
|
|
import BoardViewModel from '../viewmodel/BoardViewModel';
|
|
|
|
|
import { MancalaGame, Pit } from 'mancala.js';
|
|
|
|
|
import { v4 } from 'uuid';
|
|
|
|
|
import PitAnimator from '../animation/PitAnimator';
|
|
|
|
|
import { channel_on_game_update, channel_on_game_crashed, channel_on_game_user_leave, channel_on_user_connection_change, channel_listen_game_events, channel_unlisten_game_events, channel_leave_game, channel_game_move } from '../const/channel_names';
|
|
|
|
|
import { GameMove } from '../models/GameMove';
|
|
|
|
|
import { LoadingState } from '../models/LoadingState';
|
|
|
|
|
import { getColorByBrightness } from '../util/ColorUtil';
|
|
|
|
|
import Util from '../util/Util';
|
|
|
|
|
import Snackbar from 'react-native-snackbar';
|
|
|
|
|
import PageContainer from '../components/PageContainer';
|
|
|
|
|
import Center from '../components/Center';
|
|
|
|
|
import InfoPanel from '../components/InfoPanel';
|
|
|
|
|
import LoadingComponent from '../components/LoadingComponent';
|
|
|
|
|
import UserStatus from '../components/UserStatus';
|
|
|
|
|
import BoardToolbar from '../components/board/BoardToolbar';
|
|
|
|
|
import BoardView from '../components/board/BoardView';
|
2024-03-31 17:08:59 +03:00
|
|
|
import CircularPanel from '../components/CircularPanel';
|
2024-03-24 14:23:08 +03:00
|
|
|
export function GameScreen({ navigation, route }: GameScreenProps) {
|
2024-03-27 21:48:48 +03:00
|
|
|
|
|
|
|
|
const { context, gameId, userKey } = route.params;
|
2024-03-24 14:23:08 +03:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
2024-03-27 21:48:48 +03:00
|
|
|
const [game, setGame] = useState<Game | 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 [gameUsersConnectionInfo, setGameUsersConnectionInfo] = useState<GameUsersConnectionInfo | undefined>();
|
|
|
|
|
|
|
|
|
|
const { height, width } = useWindowDimensions();
|
|
|
|
|
|
|
|
|
|
const [gameLoadingState, setLoadingStateGame] = useState<LoadingState<Game>>(LoadingState.Unset());
|
|
|
|
|
|
|
|
|
|
const checkIsSpectator = (game: Game) => userKey !== game.mancalaGame.player1Id && userKey !== game.mancalaGame.player2Id;
|
|
|
|
|
|
|
|
|
|
const mancalaGame: MancalaGame | undefined = game?.mancalaGame;
|
|
|
|
|
const isSpectator = game ? checkIsSpectator(game) : undefined;
|
|
|
|
|
const isPlayer2 = !isSpectator && userKey === mancalaGame?.player2Id;
|
|
|
|
|
|
|
|
|
|
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;
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("InternalErrorOccurred") });
|
2024-03-27 21:48:48 +03:00
|
|
|
console.error("on_game_crash");
|
|
|
|
|
console.error(newCrashMessage);
|
|
|
|
|
}
|
|
|
|
|
const onGameUserLeave = (message: any) => {
|
|
|
|
|
const userKeyWhoLeave = message;
|
|
|
|
|
setUserKeyWhoLeave(userKeyWhoLeave);
|
|
|
|
|
setHasOngoingAction(false);
|
|
|
|
|
};
|
|
|
|
|
const onUserConnectionChange = (message: any) => {
|
|
|
|
|
const gameUsersConnectionInfo = message as GameUsersConnectionInfo;
|
|
|
|
|
setGameUsersConnectionInfo(gameUsersConnectionInfo);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const listenMessages = (game: Game, pitAnimator: PitAnimator): () => void => {
|
|
|
|
|
const _onGameUpdate = (message: object) => onGameUpdateEvent(pitAnimator, message);
|
|
|
|
|
context.rtmt.addMessageListener(channel_on_game_update, _onGameUpdate);
|
|
|
|
|
context.rtmt.addMessageListener(channel_on_game_crashed, onGameCrashed);
|
|
|
|
|
context.rtmt.addMessageListener(channel_on_game_user_leave, onGameUserLeave);
|
|
|
|
|
context.rtmt.addMessageListener(channel_on_user_connection_change, onUserConnectionChange);
|
|
|
|
|
checkIsSpectator(game) && userKey && context.rtmt.sendMessage(channel_listen_game_events, game.id);
|
|
|
|
|
return () => {
|
|
|
|
|
checkIsSpectator(game) && userKey && context.rtmt.sendMessage(channel_unlisten_game_events, game.id);
|
|
|
|
|
context.rtmt.removeMessageListener(channel_on_game_update, _onGameUpdate);
|
|
|
|
|
context.rtmt.removeMessageListener(channel_on_game_crashed, onGameCrashed);
|
|
|
|
|
context.rtmt.removeMessageListener(channel_on_game_user_leave, onGameUserLeave);
|
|
|
|
|
context.rtmt.removeMessageListener(channel_on_user_connection_change, onUserConnectionChange);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateBoardViewModel = (boardViewModel: BoardViewModel) => {
|
|
|
|
|
boardViewModel.id = v4();
|
|
|
|
|
setBoardId(boardViewModel.id);
|
|
|
|
|
setBoardViewModel(boardViewModel);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getBoardIndex = (index: number) => {
|
|
|
|
|
if (!game || !mancalaGame) return -1;
|
|
|
|
|
const pitsLenght = mancalaGame.board.pits.length;
|
|
|
|
|
if (userKey === mancalaGame.player2Id) return index + pitsLenght / 2;
|
|
|
|
|
return index;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getOpponentId = () => mancalaGame?.player1Id === userKey ? mancalaGame?.player2Id : mancalaGame?.player1Id;
|
|
|
|
|
|
|
|
|
|
const checkHasAnOngoingAction = () => hasOngoingAction;
|
|
|
|
|
|
|
|
|
|
const onLeaveGameClick = () => {
|
|
|
|
|
if (Util.checkConnectionAndMaybeAlert(context, t("ConnectionLost"))) return;
|
2024-03-31 16:39:39 +03:00
|
|
|
Alert.alert(t('AreYouSureToLeaveGame'), "", [
|
|
|
|
|
{
|
|
|
|
|
text: t('Cancel'),
|
|
|
|
|
style: 'cancel',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text: t('Yes'),
|
|
|
|
|
onPress: () => {
|
|
|
|
|
context.rtmt.sendMessage(channel_leave_game, {});
|
|
|
|
|
updateHeaderButton();
|
|
|
|
|
},
|
|
|
|
|
style: 'default',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
cancelable: true,
|
|
|
|
|
onDismiss: () => { }
|
|
|
|
|
});
|
2024-03-27 21:48:48 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onNewGameClick = () => {
|
|
|
|
|
if (Util.checkConnectionAndMaybeAlert(context, t("ConnectionLost"))) return;
|
2024-03-31 16:39:39 +03:00
|
|
|
navigation.replace("Loby", { context })
|
2024-03-27 21:48:48 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onPitSelect = (index: number, pit: Pit) => {
|
|
|
|
|
if (!game || isSpectator || !userKey) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (userKeyWhoLeave) {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("GameEnded") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (game.mancalaGame.state === "ended") {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("GameEnded") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (Util.checkConnectionAndMaybeAlert(context, t("ConnectionLost"))) return;
|
|
|
|
|
if (game.mancalaGame.getPlayerIdByIndex(index) !== userKey) {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("UCanOnlyPlayYourOwnPits") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const pitIndexForUser = index % (game.mancalaGame.board.totalPitCount() / 2);
|
|
|
|
|
if (!game.mancalaGame.canPlayerMove(userKey, pitIndexForUser)) {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("OpponentTurn") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (checkHasAnOngoingAction()) {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("UMustWaitUntilCurrentMoveComplete") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!boardViewModel) return;
|
|
|
|
|
//TODO: this check should be in mancala.js
|
|
|
|
|
if (pit.stoneCount === 0) {
|
2024-03-31 16:39:39 +03:00
|
|
|
Snackbar.show({ text: t("UCanNotPlayEmptyPit") });
|
2024-03-27 21:48:48 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setHasOngoingAction(true);
|
|
|
|
|
boardViewModel.pits[getBoardIndex(pitIndexForUser)].pitColor =
|
|
|
|
|
context.themeManager.theme.pitSelectedColor;
|
|
|
|
|
updateBoardViewModel(boardViewModel);
|
|
|
|
|
const gameMove: GameMove = { index: pitIndexForUser };
|
|
|
|
|
context.rtmt.sendMessage(channel_game_move, gameMove);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
let pitAnimator: PitAnimator | undefined;
|
|
|
|
|
let unlistenMessages: () => void;
|
|
|
|
|
setLoadingStateGame(LoadingState.Loading())
|
|
|
|
|
context.gameStore.get(gameId!!).then((game) => {
|
|
|
|
|
if (game) {
|
|
|
|
|
pitAnimator = new PitAnimator(context, updateBoardViewModel);
|
|
|
|
|
setPitAnimator(pitAnimator);
|
|
|
|
|
onGameUpdate(pitAnimator, game);
|
|
|
|
|
unlistenMessages = listenMessages(game, pitAnimator);
|
|
|
|
|
setLoadingStateGame(LoadingState.Loaded({ value: game }))
|
|
|
|
|
} else {
|
|
|
|
|
setLoadingStateGame(LoadingState.Error({ errorMessage: t('GameNotFound') }))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return () => {
|
|
|
|
|
unlistenMessages?.();
|
|
|
|
|
pitAnimator?.dispose();
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
2024-03-31 16:39:39 +03:00
|
|
|
const updateHeaderButton = () => {
|
2024-03-31 17:08:59 +03:00
|
|
|
const isGameEnded = game?.mancalaGame.state === "ended" || leftPlayer;
|
2024-03-31 16:39:39 +03:00
|
|
|
navigation.setOptions({
|
|
|
|
|
headerRight: () => (
|
2024-03-31 17:08:59 +03:00
|
|
|
<Pressable onPress={() => {
|
|
|
|
|
if (isGameEnded) {
|
2024-03-31 16:39:39 +03:00
|
|
|
onNewGameClick();
|
2024-03-31 17:08:59 +03:00
|
|
|
} else {
|
|
|
|
|
onLeaveGameClick();
|
2024-03-31 16:39:39 +03:00
|
|
|
}
|
2024-03-31 17:08:59 +03:00
|
|
|
}}>
|
|
|
|
|
<CircularPanel color={context.themeManager.theme?.background} children={<Text style={{ color: context.themeManager.theme.textColor }}>{isGameEnded ? t('NewGame') : t('Leave')}</Text>} />
|
|
|
|
|
</Pressable>
|
2024-03-31 16:39:39 +03:00
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
updateHeaderButton();
|
2024-03-31 17:08:59 +03:00
|
|
|
}, [navigation, game, userKeyWhoLeave]);
|
2024-03-31 16:39:39 +03:00
|
|
|
|
2024-03-27 21:48:48 +03:00
|
|
|
const textColorOnAppBar = getColorByBrightness(
|
|
|
|
|
context.themeManager.theme.appBarBgColor,
|
|
|
|
|
context.themeManager.theme.textColor,
|
|
|
|
|
context.themeManager.theme.textLightColor
|
|
|
|
|
);
|
|
|
|
|
const isMobile = width < 600;
|
|
|
|
|
|
|
|
|
|
const renderNewGameBtn = isSpectator || (userKeyWhoLeave || !game || (game && game.mancalaGame.state == "ended"));
|
|
|
|
|
const showBoardView = game && boardViewModel && userKey && true;
|
|
|
|
|
const topLocatedUserId = (isSpectator ? mancalaGame?.player2Id : getOpponentId()) || "0";
|
|
|
|
|
const bottomLocatedUserId = (isSpectator ? mancalaGame?.player1Id : userKey) || "1";
|
|
|
|
|
const topLocatedUser = {
|
|
|
|
|
id: topLocatedUserId,
|
|
|
|
|
name: "Anonymous",
|
|
|
|
|
isOnline: isUserOnline(topLocatedUserId),
|
|
|
|
|
isAnonymous: true
|
|
|
|
|
};
|
|
|
|
|
const bottomLocatedUser = {
|
|
|
|
|
id: bottomLocatedUserId,
|
|
|
|
|
name: "Anonymous",
|
|
|
|
|
isOnline: isSpectator ? isUserOnline(bottomLocatedUserId) : context.rtmt.connectionState === "connected",
|
|
|
|
|
isAnonymous: true
|
|
|
|
|
};
|
|
|
|
|
const currentUser = isSpectator ? {
|
|
|
|
|
id: "2",
|
|
|
|
|
name: "Anonymous",
|
|
|
|
|
isOnline: context.rtmt.connectionState === "connected",
|
|
|
|
|
isAnonymous: true
|
|
|
|
|
} : bottomLocatedUser;
|
|
|
|
|
const leftPlayer = userKeyWhoLeave ? (userKeyWhoLeave === topLocatedUser.id ? topLocatedUser : bottomLocatedUser) : undefined;
|
2024-03-24 14:23:08 +03:00
|
|
|
return (
|
2024-03-27 21:48:48 +03:00
|
|
|
<PageContainer context={context}>
|
|
|
|
|
{/* {renderHeaderBar()} */}
|
|
|
|
|
{renderMobileBoardToolbar()}
|
|
|
|
|
{buildBoardTopToolbar()}
|
|
|
|
|
{showBoardView && (
|
|
|
|
|
<BoardView
|
|
|
|
|
game={game}
|
|
|
|
|
boardId={boardId}
|
|
|
|
|
boardViewModel={boardViewModel}
|
|
|
|
|
context={context}
|
|
|
|
|
onPitSelect={onPitSelect}
|
|
|
|
|
revert={isPlayer2} />
|
|
|
|
|
)}
|
|
|
|
|
<Center>
|
|
|
|
|
<LoadingComponent context={context} loadingState={gameLoadingState}></LoadingComponent>
|
|
|
|
|
</Center>
|
|
|
|
|
</PageContainer>
|
2024-03-24 14:23:08 +03:00
|
|
|
);
|
2024-03-27 21:48:48 +03:00
|
|
|
|
|
|
|
|
//function renderHeaderBar() {
|
|
|
|
|
// return <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>;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
function renderMobileBoardToolbar() {
|
|
|
|
|
return <BoardToolbar style={{ justifyContent: "center" }} visible={showBoardView && isMobile || false}>
|
2024-03-31 16:39:39 +03:00
|
|
|
<View />
|
2024-03-27 21:48:48 +03:00
|
|
|
{buildInfoPanel({ visible: isMobile })}
|
2024-03-31 16:39:39 +03:00
|
|
|
<View />
|
2024-03-27 21:48:48 +03:00
|
|
|
</BoardToolbar>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildBoardTopToolbar() {
|
|
|
|
|
return <BoardToolbar style={{ alignItems: "flex-end" }} visible={showBoardView || false}>
|
2024-03-31 16:39:39 +03:00
|
|
|
<UserStatus style={{ marginLeft: 10, width: 100 }} context={context}
|
|
|
|
|
layoutMode="left" user={topLocatedUser} visible={showBoardView || false} />
|
2024-03-27 21:48:48 +03:00
|
|
|
{buildInfoPanel({ visible: !isMobile })}
|
2024-03-31 16:39:39 +03:00
|
|
|
<UserStatus style={{ marginLeft: 10, width: 100 }} context={context} layoutMode="right" user={bottomLocatedUser} visible={showBoardView || false} />
|
2024-03-27 21:48:48 +03:00
|
|
|
</BoardToolbar>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildInfoPanel(params: { visible: boolean }) {
|
|
|
|
|
return (
|
|
|
|
|
<InfoPanel
|
|
|
|
|
style={{ marginTop: 10, marginBottom: 10 }}
|
|
|
|
|
context={context}
|
|
|
|
|
game={game}
|
|
|
|
|
currentUser={currentUser}
|
|
|
|
|
whitePlayer={topLocatedUser}
|
|
|
|
|
blackPlayer={bottomLocatedUser}
|
|
|
|
|
leftPlayer={leftPlayer}
|
|
|
|
|
visible={params.visible}
|
|
|
|
|
isSpectator={isSpectator} />
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-24 14:23:08 +03:00
|
|
|
}
|