2022-05-12 23:41:24 +03:00
|
|
|
import * as React from "react";
|
|
|
|
|
import { FunctionComponent, useEffect, useState } from "react";
|
|
|
|
|
import BoardView from "./components/BoardView";
|
|
|
|
|
import { context } from "./context";
|
|
|
|
|
import { RTMTWS } from "./rtmt/rtmt_websocket";
|
|
|
|
|
import {
|
|
|
|
|
channel_game_move,
|
|
|
|
|
channel_leave_game,
|
2022-05-15 02:06:09 +03:00
|
|
|
channel_on_game_crashed,
|
|
|
|
|
channel_on_game_start,
|
2022-05-12 23:41:24 +03:00
|
|
|
channel_on_game_update,
|
|
|
|
|
channel_on_game_user_leave,
|
|
|
|
|
} from "./channel_names";
|
|
|
|
|
import Button from "./components/Button";
|
|
|
|
|
import InfoPanel from "./components/InfoPanel";
|
|
|
|
|
import { CommonMancalaGame, MancalaGame, Pit } from "mancala.js";
|
|
|
|
|
import { GameMove } from "./models/GameMove";
|
2022-05-15 02:01:51 +03:00
|
|
|
import PitAnimator from "./animation/PitAnimator";
|
|
|
|
|
import BoardViewModel from "./viewmodel/BoardViewModel";
|
2022-05-22 17:57:33 +03:00
|
|
|
import { v4 } from "uuid";
|
2022-06-04 20:54:19 +03:00
|
|
|
import { Menu, MenuButton, MenuItem } from "@szhsin/react-menu";
|
|
|
|
|
import "@szhsin/react-menu/dist/index.css";
|
|
|
|
|
import "@szhsin/react-menu/dist/transitions/slide.css";
|
|
|
|
|
import { getColorByLuminance } from "./util/ColorUtil";
|
2022-05-12 23:41:24 +03:00
|
|
|
|
|
|
|
|
type ConnectionState = "connecting" | "error" | "connected" | "reconnecting";
|
2021-07-02 23:13:38 +03:00
|
|
|
|
2021-06-29 03:29:46 +03:00
|
|
|
const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
|
|
|
|
const [userKey, setUserKey] = useState(undefined);
|
|
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const [connectionState, setConnetionState] =
|
|
|
|
|
useState<ConnectionState>("connecting");
|
2021-06-30 19:41:53 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const [searchingOpponent, setSearchingOpponent] = useState<boolean>(false);
|
2021-07-04 01:23:10 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const [game, setGame] = useState<CommonMancalaGame>(undefined);
|
2021-07-04 01:04:59 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const [crashMessage, setCrashMessage] = useState<string>(undefined);
|
2021-07-04 00:45:00 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const [userKeyWhoLeave, setUserKeyWhoLeave] = useState<string>(undefined);
|
|
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const [boardViewModel, setBoardViewModel] = useState<BoardViewModel>(null);
|
2022-05-12 23:41:24 +03:00
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const [boardId, setBoardId] = useState<string>();
|
2022-05-12 23:41:24 +03:00
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const [pitAnimator, setPitAnimator] = useState<PitAnimator>();
|
2021-07-04 01:04:59 +03:00
|
|
|
|
2021-07-02 23:13:38 +03:00
|
|
|
const onConnectionDone = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
setConnetionState("connected");
|
|
|
|
|
};
|
2021-07-02 23:13:38 +03:00
|
|
|
|
|
|
|
|
const onConnectionLost = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
connectToServer("reconnecting");
|
|
|
|
|
};
|
2021-06-30 19:41:53 +03:00
|
|
|
|
2021-07-02 23:13:38 +03:00
|
|
|
const onConnectionError = (event: Event) => {
|
2022-05-12 23:41:24 +03:00
|
|
|
setConnetionState("error");
|
|
|
|
|
};
|
2021-07-02 23:13:38 +03:00
|
|
|
|
|
|
|
|
const connectToServer = (connectionState: ConnectionState) => {
|
2022-05-12 23:41:24 +03:00
|
|
|
setConnetionState(connectionState);
|
2021-06-29 03:29:46 +03:00
|
|
|
context.userKeyStore.getUserKey((userKey: string) => {
|
2022-05-12 23:41:24 +03:00
|
|
|
setUserKey(userKey);
|
|
|
|
|
const rtmtws = context.rtmt as RTMTWS;
|
2021-06-29 03:29:46 +03:00
|
|
|
if (rtmtws) {
|
2022-05-12 23:41:24 +03:00
|
|
|
rtmtws.initWebSocket(
|
|
|
|
|
userKey,
|
|
|
|
|
onConnectionDone,
|
|
|
|
|
onConnectionLost,
|
|
|
|
|
onConnectionError
|
|
|
|
|
);
|
2021-06-29 03:29:46 +03:00
|
|
|
} else {
|
2022-05-04 23:41:04 +03:00
|
|
|
console.error("context.rtmt is not RTMTWS");
|
2021-06-29 03:29:46 +03:00
|
|
|
}
|
2022-05-12 23:41:24 +03:00
|
|
|
});
|
|
|
|
|
};
|
2021-06-29 03:29:46 +03:00
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const listenMessages = (pitAnimator: PitAnimator) => {
|
2022-05-15 02:06:09 +03:00
|
|
|
context.rtmt.listenMessage(channel_on_game_start, (message: Object) => {
|
2022-05-04 23:41:04 +03:00
|
|
|
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
2022-05-12 23:41:24 +03:00
|
|
|
const mancalaGame = MancalaGame.createFromMancalaGame(newGame);
|
2022-05-15 02:01:51 +03:00
|
|
|
setSearchingOpponent(false);
|
|
|
|
|
setGame(mancalaGame);
|
|
|
|
|
pitAnimator.setNewGame(mancalaGame);
|
2022-05-12 23:41:24 +03:00
|
|
|
});
|
2021-06-27 19:28:55 +03:00
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
context.rtmt.listenMessage(channel_on_game_update, (message: Object) => {
|
2022-05-04 23:41:04 +03:00
|
|
|
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
2022-05-15 02:01:51 +03:00
|
|
|
const mancalaGame = MancalaGame.createFromMancalaGame(newGame);
|
|
|
|
|
setGame(mancalaGame);
|
|
|
|
|
pitAnimator.setUpdatedGame(mancalaGame);
|
2022-05-12 23:41:24 +03:00
|
|
|
});
|
2021-07-04 00:45:00 +03:00
|
|
|
|
2022-05-15 02:06:09 +03:00
|
|
|
context.rtmt.listenMessage(channel_on_game_crashed, (message: any) => {
|
2022-05-12 23:41:24 +03:00
|
|
|
const newCrashMessage = message as string;
|
2022-05-04 23:41:04 +03:00
|
|
|
console.error("on_game_crash");
|
|
|
|
|
console.error(newCrashMessage);
|
2022-05-12 23:41:24 +03:00
|
|
|
setCrashMessage(newCrashMessage);
|
|
|
|
|
});
|
2021-07-04 01:04:59 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
context.rtmt.listenMessage(channel_on_game_user_leave, (message: any) => {
|
2022-04-23 12:53:08 +03:00
|
|
|
const userKeyWhoLeave = message;
|
2022-05-12 23:41:24 +03:00
|
|
|
setUserKeyWhoLeave(userKeyWhoLeave);
|
|
|
|
|
});
|
|
|
|
|
};
|
2021-06-27 19:28:55 +03:00
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const updateBoardViewModel = (boardViewModel: BoardViewModel) => {
|
2022-05-22 17:57:33 +03:00
|
|
|
boardViewModel.id = v4();
|
2022-05-15 02:01:51 +03:00
|
|
|
setBoardId(boardViewModel.id);
|
|
|
|
|
setBoardViewModel(boardViewModel);
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-02 23:13:38 +03:00
|
|
|
React.useEffect(() => {
|
2022-05-15 02:01:51 +03:00
|
|
|
const pitAnimator = new PitAnimator(context, updateBoardViewModel);
|
|
|
|
|
setPitAnimator(pitAnimator);
|
|
|
|
|
listenMessages(pitAnimator);
|
2022-05-12 23:41:24 +03:00
|
|
|
connectToServer("connecting");
|
2022-05-15 02:01:51 +03:00
|
|
|
return () => {
|
|
|
|
|
pitAnimator.dispose();
|
|
|
|
|
};
|
2022-05-12 23:41:24 +03:00
|
|
|
}, []);
|
2021-07-02 23:13:38 +03:00
|
|
|
|
2022-06-04 20:54:19 +03:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
context.themeManager.onThemeChange = () => {
|
|
|
|
|
updateBoardViewModel(pitAnimator.getBoardViewModelFromGame(game));
|
|
|
|
|
};
|
|
|
|
|
}, [boardViewModel]);
|
|
|
|
|
|
2021-07-04 01:04:59 +03:00
|
|
|
const resetGameState = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
setGame(undefined);
|
|
|
|
|
setCrashMessage(undefined);
|
|
|
|
|
setUserKeyWhoLeave(undefined);
|
|
|
|
|
};
|
2021-07-04 01:04:59 +03:00
|
|
|
|
2021-07-02 23:13:38 +03:00
|
|
|
const newGameClick = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
resetGameState();
|
|
|
|
|
setSearchingOpponent(true);
|
|
|
|
|
context.rtmt.sendMessage("new_game", {});
|
|
|
|
|
};
|
2021-07-02 23:13:38 +03:00
|
|
|
|
2021-06-29 03:29:46 +03:00
|
|
|
const leaveGame = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
context.rtmt.sendMessage(channel_leave_game, {});
|
|
|
|
|
};
|
2021-06-29 03:29:46 +03:00
|
|
|
|
2022-05-22 17:58:11 +03:00
|
|
|
const getBoardIndex = (index: number) => {
|
|
|
|
|
if (userKey === game.player2Id) return index + game.board.pits.length / 2;
|
|
|
|
|
return index;
|
|
|
|
|
};
|
|
|
|
|
|
2022-05-15 02:01:51 +03:00
|
|
|
const onHoleSelect = (index: number, pit: Pit) => {
|
|
|
|
|
//TODO : stoneCount comes from view model!
|
|
|
|
|
if (pit.stoneCount === 0) {
|
|
|
|
|
//TODO : warn user
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-22 17:58:11 +03:00
|
|
|
boardViewModel.pits[getBoardIndex(index)].pitColor =
|
|
|
|
|
context.themeManager.theme.pitSelectedColor;
|
|
|
|
|
updateBoardViewModel(boardViewModel);
|
2022-05-12 23:41:24 +03:00
|
|
|
const gameMove: GameMove = { index: index };
|
|
|
|
|
context.rtmt.sendMessage(channel_game_move, gameMove);
|
|
|
|
|
};
|
2021-06-29 03:29:46 +03:00
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
const showConnectionState = connectionState != "connected";
|
2021-07-02 23:13:38 +03:00
|
|
|
|
|
|
|
|
const connectionStateText = () => {
|
|
|
|
|
let map: { [key: string]: string } = {
|
2022-05-12 23:41:24 +03:00
|
|
|
connecting: context.texts.Connecting,
|
|
|
|
|
connected: context.texts.Connected,
|
|
|
|
|
error: context.texts.CannotConnect,
|
|
|
|
|
reconnecting: context.texts.ConnectingAgain,
|
2021-07-02 23:13:38 +03:00
|
|
|
};
|
|
|
|
|
|
2022-05-12 23:41:24 +03:00
|
|
|
return map[connectionState];
|
|
|
|
|
};
|
2021-07-02 23:13:38 +03:00
|
|
|
|
2021-07-04 00:45:00 +03:00
|
|
|
const renderNewGameButton = () => {
|
2022-05-12 23:41:24 +03:00
|
|
|
const newGame = (
|
|
|
|
|
<Button
|
2022-06-04 17:16:32 +03:00
|
|
|
context={context}
|
2022-05-12 23:41:24 +03:00
|
|
|
text={context.texts.NewGame}
|
2022-06-04 17:16:32 +03:00
|
|
|
color={context.themeManager.theme.primary}
|
2022-05-12 23:41:24 +03:00
|
|
|
onClick={newGameClick}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2021-07-04 01:04:59 +03:00
|
|
|
if (userKeyWhoLeave) {
|
2022-05-12 23:41:24 +03:00
|
|
|
return newGame;
|
2021-07-04 01:04:59 +03:00
|
|
|
}
|
2021-07-04 00:45:00 +03:00
|
|
|
if (crashMessage) {
|
2022-05-12 23:41:24 +03:00
|
|
|
return newGame;
|
2021-07-04 00:45:00 +03:00
|
|
|
}
|
|
|
|
|
if (!game) {
|
2022-05-12 23:41:24 +03:00
|
|
|
return newGame;
|
2021-07-04 00:45:00 +03:00
|
|
|
} else if (game.state == "ended") {
|
2022-05-12 23:41:24 +03:00
|
|
|
return newGame;
|
2021-07-04 00:45:00 +03:00
|
|
|
}
|
2022-05-12 23:41:24 +03:00
|
|
|
return <></>;
|
|
|
|
|
};
|
2022-06-04 20:54:19 +03:00
|
|
|
const menuTextColor = getColorByLuminance(
|
|
|
|
|
context.themeManager.theme.appBarBgColor,
|
|
|
|
|
context.themeManager.theme.primary,
|
|
|
|
|
context.themeManager.theme.primaryLight
|
|
|
|
|
);
|
2022-05-12 23:41:24 +03:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
alignItems: "center",
|
2022-06-04 17:16:32 +03:00
|
|
|
background: context.themeManager.theme.background,
|
2022-05-12 23:41:24 +03:00
|
|
|
flex: "1",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{showConnectionState && (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
bottom: "0px",
|
|
|
|
|
left: "0px",
|
|
|
|
|
padding: "15px ",
|
|
|
|
|
borderTopRightRadius: "1vw",
|
|
|
|
|
minWidth: "10vw",
|
|
|
|
|
minHeight: "1vw",
|
2022-06-04 17:16:32 +03:00
|
|
|
background: context.themeManager.theme.primary,
|
|
|
|
|
color: context.themeManager.theme.primaryLight,
|
2022-05-12 23:41:24 +03:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{connectionStateText()}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
2022-06-04 14:49:34 +03:00
|
|
|
padding: "0px 4vw",
|
2022-06-04 17:16:32 +03:00
|
|
|
background: context.themeManager.theme.appBarBgColor,
|
2022-05-12 23:41:24 +03:00
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
justifyContent: "space-between",
|
|
|
|
|
alignSelf: "stretch",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<h1 style={{ margin: "10px 0px" }}>{context.texts.Mancala}</h1>
|
2022-06-04 20:54:19 +03:00
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
justifyContent: "space-between",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
marginRight: "1vw",
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Menu
|
|
|
|
|
menuStyle={{
|
|
|
|
|
background: context.themeManager.theme.appBarBgColor,
|
|
|
|
|
}}
|
|
|
|
|
menuButton={
|
|
|
|
|
<span class="material-symbols-outlined">light_mode</span>
|
|
|
|
|
}
|
|
|
|
|
transition
|
|
|
|
|
align="end"
|
|
|
|
|
>
|
|
|
|
|
{context.themeManager.themes.map((theme) => {
|
|
|
|
|
return (
|
|
|
|
|
<MenuItem
|
|
|
|
|
style={{
|
|
|
|
|
color: menuTextColor,
|
|
|
|
|
}}
|
|
|
|
|
onClick={() => (context.themeManager.theme = theme)}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
borderRadius: "5vw",
|
|
|
|
|
background: theme.boardColor,
|
|
|
|
|
width: "1vw",
|
|
|
|
|
height: "1vw",
|
|
|
|
|
marginRight: "1vw",
|
|
|
|
|
}}
|
|
|
|
|
></div>
|
|
|
|
|
{theme.name}
|
|
|
|
|
</MenuItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Menu>
|
|
|
|
|
</div>
|
2022-05-12 23:41:24 +03:00
|
|
|
{renderNewGameButton()}
|
|
|
|
|
{game &&
|
|
|
|
|
!userKeyWhoLeave &&
|
|
|
|
|
!crashMessage &&
|
|
|
|
|
(game?.state === "playing" || game?.state === "initial") && (
|
|
|
|
|
<Button
|
2022-06-04 17:16:32 +03:00
|
|
|
context={context}
|
|
|
|
|
color={context.themeManager.theme.primary}
|
2022-05-12 23:41:24 +03:00
|
|
|
text={context.texts.Leave}
|
|
|
|
|
onClick={leaveGame}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2021-07-04 00:45:00 +03:00
|
|
|
</div>
|
2022-05-12 23:41:24 +03:00
|
|
|
<InfoPanel
|
2022-06-04 14:49:34 +03:00
|
|
|
context={context}
|
2022-05-12 23:41:24 +03:00
|
|
|
game={game}
|
|
|
|
|
crashMessage={crashMessage}
|
|
|
|
|
userKey={userKey}
|
|
|
|
|
userKeyWhoLeave={userKeyWhoLeave}
|
|
|
|
|
searchingOpponent={searchingOpponent}
|
|
|
|
|
/>
|
2022-05-15 02:01:51 +03:00
|
|
|
{game && boardViewModel && (
|
2022-05-12 23:41:24 +03:00
|
|
|
<BoardView
|
|
|
|
|
userKey={userKey}
|
|
|
|
|
game={game}
|
2022-05-15 02:01:51 +03:00
|
|
|
boardId={boardId}
|
|
|
|
|
boardViewModel={boardViewModel}
|
|
|
|
|
context={context}
|
2022-05-12 23:41:24 +03:00
|
|
|
onHoleSelect={onHoleSelect}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2021-07-04 00:45:00 +03:00
|
|
|
</div>
|
2022-05-12 23:41:24 +03:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Home;
|