Merge pull request #3 from jhalitaksoy/feature/pit-animator
Feature/pit animator
This commit is contained in:
commit
5209c45bee
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mancala-frontend",
|
||||
"version": "0.1.3-beta.2",
|
||||
"version": "0.1.3-beta.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -14,9 +14,11 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/uuid": "^8.3.4",
|
||||
"mancala.js": "^0.0.2-beta.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.11",
|
||||
|
||||
83
src/Home.tsx
83
src/Home.tsx
@ -6,6 +6,8 @@ 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,
|
||||
} from "./channel_names";
|
||||
@ -13,6 +15,8 @@ import Button from "./components/Button";
|
||||
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";
|
||||
|
||||
type ConnectionState = "connecting" | "error" | "connected" | "reconnecting";
|
||||
|
||||
@ -25,19 +29,16 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
const [searchingOpponent, setSearchingOpponent] = useState<boolean>(false);
|
||||
|
||||
const [game, setGame] = useState<CommonMancalaGame>(undefined);
|
||||
const gameRef = React.useRef<CommonMancalaGame>(game);
|
||||
|
||||
const [crashMessage, setCrashMessage] = useState<string>(undefined);
|
||||
|
||||
const [userKeyWhoLeave, setUserKeyWhoLeave] = useState<string>(undefined);
|
||||
|
||||
const [animationPitIndex, setAnimationPitIndex] = useState<number>(-1);
|
||||
const [boardViewModel, setBoardViewModel] = useState<BoardViewModel>(null);
|
||||
|
||||
const [intervalId, setIntervalId] = useState<number>(-1);
|
||||
const [boardId, setBoardId] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
gameRef.current = game;
|
||||
});
|
||||
const [pitAnimator, setPitAnimator] = useState<PitAnimator>();
|
||||
|
||||
const onConnectionDone = () => {
|
||||
setConnetionState("connected");
|
||||
@ -69,44 +70,23 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const listenMessages = () => {
|
||||
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);
|
||||
});
|
||||
|
||||
context.rtmt.listenMessage(channel_on_game_update, (message: Object) => {
|
||||
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
||||
const mancalaGame = MancalaGame.createFromMancalaGame(newGame);
|
||||
if (gameRef.current && mancalaGame.history.length > 0) {
|
||||
const lastHistoryItem =
|
||||
mancalaGame.history[mancalaGame.history.length - 1];
|
||||
if (lastHistoryItem.gameSteps.length > 0) {
|
||||
let stepIndex = 0;
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
const id = setInterval(() => {
|
||||
if (stepIndex === lastHistoryItem.gameSteps.length) {
|
||||
clearInterval(id);
|
||||
setAnimationPitIndex(-1);
|
||||
setGame(mancalaGame);
|
||||
} else {
|
||||
const gameStep = lastHistoryItem.gameSteps[stepIndex];
|
||||
const index = mancalaGame.board.getPitIndexCircularly(
|
||||
gameStep.index
|
||||
);
|
||||
setAnimationPitIndex(index);
|
||||
}
|
||||
stepIndex++;
|
||||
}, 250);
|
||||
setIntervalId(intervalId);
|
||||
}
|
||||
}
|
||||
setGame(mancalaGame);
|
||||
pitAnimator.setUpdatedGame(mancalaGame);
|
||||
});
|
||||
|
||||
context.rtmt.listenMessage("on_game_start", (message: Object) => {
|
||||
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
||||
setSearchingOpponent(false);
|
||||
setGame(MancalaGame.createFromMancalaGame(newGame));
|
||||
});
|
||||
|
||||
context.rtmt.listenMessage("on_game_crashed", (message: any) => {
|
||||
context.rtmt.listenMessage(channel_on_game_crashed, (message: any) => {
|
||||
const newCrashMessage = message as string;
|
||||
console.error("on_game_crash");
|
||||
console.error(newCrashMessage);
|
||||
@ -119,9 +99,19 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateBoardViewModel = (boardViewModel: BoardViewModel) => {
|
||||
setBoardId(boardViewModel.id);
|
||||
setBoardViewModel(boardViewModel);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
listenMessages();
|
||||
const pitAnimator = new PitAnimator(context, updateBoardViewModel);
|
||||
setPitAnimator(pitAnimator);
|
||||
listenMessages(pitAnimator);
|
||||
connectToServer("connecting");
|
||||
return () => {
|
||||
pitAnimator.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const resetGameState = () => {
|
||||
@ -140,7 +130,12 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
context.rtmt.sendMessage(channel_leave_game, {});
|
||||
};
|
||||
|
||||
const onHoleSelect = (index: number, hole: Pit) => {
|
||||
const onHoleSelect = (index: number, pit: Pit) => {
|
||||
//TODO : stoneCount comes from view model!
|
||||
if (pit.stoneCount === 0) {
|
||||
//TODO : warn user
|
||||
return;
|
||||
}
|
||||
const gameMove: GameMove = { index: index };
|
||||
context.rtmt.sendMessage(channel_game_move, gameMove);
|
||||
};
|
||||
@ -240,12 +235,14 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
userKeyWhoLeave={userKeyWhoLeave}
|
||||
searchingOpponent={searchingOpponent}
|
||||
/>
|
||||
{game && (
|
||||
{game && boardViewModel && (
|
||||
<BoardView
|
||||
userKey={userKey}
|
||||
game={game}
|
||||
boardId={boardId}
|
||||
boardViewModel={boardViewModel}
|
||||
context={context}
|
||||
onHoleSelect={onHoleSelect}
|
||||
animationPitIndex={animationPitIndex}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
194
src/animation/PitAnimator.ts
Normal file
194
src/animation/PitAnimator.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import {
|
||||
MancalaGame,
|
||||
GameStep,
|
||||
HistoryItem,
|
||||
GAME_STEP_GAME_MOVE,
|
||||
GAME_STEP_LAST_STONE_IN_EMPTY_PIT,
|
||||
GAME_STEP_BOARD_CLEARED,
|
||||
GAME_STEP_LAST_STONE_IN_BANK,
|
||||
} from "mancala.js";
|
||||
import { v4 } from "uuid";
|
||||
import { Context } from "../context";
|
||||
import BoardViewModelFactory from "../factory/BoardViewModelFactory";
|
||||
import { PitViewModelFactory } from "../factory/PitViewModelFactory";
|
||||
import BoardViewModel from "../viewmodel/BoardViewModel";
|
||||
|
||||
const animationUpdateInterval = 300;
|
||||
|
||||
export default class PitAnimator {
|
||||
context: Context;
|
||||
game: MancalaGame;
|
||||
oldGame: MancalaGame;
|
||||
currentIntervalID: number;
|
||||
onBoardViewModelUpdate: (boardViewModel: BoardViewModel) => void;
|
||||
boardViewModel: BoardViewModel;
|
||||
oldBoardViewModel: BoardViewModel;
|
||||
animationIndex: number = 0;
|
||||
currentHistoryItem: HistoryItem;
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
onBoardViewModelUpdate: (boardViewModel: BoardViewModel) => void
|
||||
) {
|
||||
this.context = context;
|
||||
this.onBoardViewModelUpdate = onBoardViewModelUpdate;
|
||||
}
|
||||
|
||||
public setNewGame(game: MancalaGame) {
|
||||
this.reset();
|
||||
this.game = game;
|
||||
this.updateBoardViewModel(this.getBoardViewModelFromGame(this.game));
|
||||
}
|
||||
|
||||
public setUpdatedGame(game: MancalaGame, forceClear = false) {
|
||||
this.resetAnimationState();
|
||||
if (!this.game) {
|
||||
this.setNewGame(game);
|
||||
} else {
|
||||
this.oldGame = this.game;
|
||||
this.game = game;
|
||||
this.onGameMoveAnimationStart();
|
||||
}
|
||||
}
|
||||
|
||||
onGameMoveAnimationStart() {
|
||||
this.stopCurrentAnimation();
|
||||
if (this.game.history.length > 0) {
|
||||
const lastHistoryItem = this.game.history[this.game.history.length - 1];
|
||||
if (lastHistoryItem.gameSteps.length > 0) {
|
||||
this.animationIndex = 0;
|
||||
this.currentHistoryItem = lastHistoryItem;
|
||||
this.boardViewModel = this.getBoardViewModelFromGame(this.game);
|
||||
this.oldBoardViewModel = this.getBoardViewModelFromGame(this.oldGame);
|
||||
this.startAnimationUpdateCyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAnimate() {
|
||||
if (this.animationIndex === this.currentHistoryItem.gameSteps.length) {
|
||||
this.clearCurrentInterval();
|
||||
this.updateBoardViewModel(this.getBoardViewModelFromGame(this.game));
|
||||
} else {
|
||||
const gameStep = this.currentHistoryItem.gameSteps[this.animationIndex];
|
||||
const index = this.game.board.getPitIndexCircularly(gameStep.index);
|
||||
this.animatePit(index, this.oldBoardViewModel, gameStep);
|
||||
this.updateBoardViewModel(this.oldBoardViewModel);
|
||||
}
|
||||
this.animationIndex++;
|
||||
}
|
||||
|
||||
getGameMoveStepCount(historyItem: HistoryItem) {
|
||||
return historyItem.gameSteps.filter(
|
||||
(gameStep) => gameStep.type === GAME_STEP_GAME_MOVE
|
||||
).length;
|
||||
}
|
||||
|
||||
animatePit(
|
||||
index: number,
|
||||
boardViewModel: BoardViewModel,
|
||||
gameStep: GameStep
|
||||
) {
|
||||
const pitViewModel = boardViewModel.pits[index];
|
||||
if (this.animationIndex === 0) {
|
||||
//This one stone move case, TODO : beautify it later
|
||||
if (this.getGameMoveStepCount(this.currentHistoryItem) === 1) {
|
||||
const previousPitIndex = gameStep.index - 1;
|
||||
if (previousPitIndex > 0) {
|
||||
boardViewModel.pits[previousPitIndex].stoneCount = 0;
|
||||
}
|
||||
} else {
|
||||
pitViewModel.stoneCount = 0;
|
||||
}
|
||||
}
|
||||
const theme = this.context.themeManager.theme;
|
||||
if (gameStep.type === GAME_STEP_GAME_MOVE) {
|
||||
pitViewModel.stoneCount += 1;
|
||||
pitViewModel.pitColor = theme.pitGameMoveAnimateColor;
|
||||
} else if (gameStep.type === GAME_STEP_LAST_STONE_IN_EMPTY_PIT) {
|
||||
pitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor;
|
||||
pitViewModel.stoneCount = 0;
|
||||
const oppositeIndex = this.game.board.getPitIndexCircularly(
|
||||
gameStep.data.oppositeIndex
|
||||
);
|
||||
const oppositePitViewModel = boardViewModel.pits[oppositeIndex];
|
||||
oppositePitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor;
|
||||
oppositePitViewModel.stoneCount = 0;
|
||||
} else if (gameStep.type === GAME_STEP_LAST_STONE_IN_BANK) {
|
||||
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 oppositePitViewModel = boardViewModel.pits[oppositeIndex];
|
||||
oppositePitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor;
|
||||
oppositePitViewModel.stoneCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startAnimationUpdateCyle() {
|
||||
this.clearCurrentInterval();
|
||||
this.currentIntervalID = setInterval(
|
||||
() => this.onAnimate(),
|
||||
animationUpdateInterval
|
||||
);
|
||||
}
|
||||
|
||||
stopCurrentAnimation() {
|
||||
this.clearCurrentInterval();
|
||||
if (this.oldGame) {
|
||||
this.updateBoardViewModel(this.getBoardViewModelFromGame(this.oldGame));
|
||||
}
|
||||
this.resetAnimationState();
|
||||
}
|
||||
|
||||
clearCurrentInterval() {
|
||||
if (this.currentIntervalID) {
|
||||
clearInterval(this.currentIntervalID);
|
||||
}
|
||||
}
|
||||
|
||||
updateBoardViewModel(boardViewModel: BoardViewModel) {
|
||||
boardViewModel.id = v4();
|
||||
this.onBoardViewModelUpdate?.(boardViewModel);
|
||||
}
|
||||
|
||||
private getBoardViewModelFromGame(game: MancalaGame): BoardViewModel {
|
||||
const pitViewModels = this.createPitViewModelsFromGame(game);
|
||||
return BoardViewModelFactory.create(v4(), pitViewModels);
|
||||
}
|
||||
|
||||
private createPitViewModelsFromGame(game: MancalaGame) {
|
||||
return game.board.pits.map((pit) => {
|
||||
const theme = this.context.themeManager.theme;
|
||||
const stoneCount = pit.stoneCount;
|
||||
const stoneColor = theme.ballColor;
|
||||
const pitColor = theme.holeColor;
|
||||
const id = pit.index.toString();
|
||||
return PitViewModelFactory.create({
|
||||
id,
|
||||
stoneCount,
|
||||
stoneColor,
|
||||
pitColor,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public resetAnimationState() {
|
||||
this.animationIndex = -1;
|
||||
this.currentHistoryItem = null;
|
||||
this.boardViewModel = null;
|
||||
this.oldBoardViewModel = null;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.resetAnimationState();
|
||||
this.game = null;
|
||||
this.oldGame = null;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.resetAnimationState();
|
||||
this.clearCurrentInterval();
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,9 @@
|
||||
import { Bank, MancalaGame, Pit } from "mancala.js";
|
||||
import * as React from "react";
|
||||
import { FunctionComponent, useState } from "react";
|
||||
|
||||
type Theme = {
|
||||
background: string;
|
||||
boardColor: string;
|
||||
boardColorWhenPlayerTurn: string;
|
||||
storeColor: string;
|
||||
storeColorWhenPlayerTurn: string;
|
||||
holeColor: string;
|
||||
ballColor: string;
|
||||
ballLightColor: string;
|
||||
holeAnimateColor: string;
|
||||
};
|
||||
|
||||
const theme: Theme = {
|
||||
background: "#EEEEEE",
|
||||
boardColor: "#4D606E",
|
||||
boardColorWhenPlayerTurn: "#84b8a6",
|
||||
storeColor: "#3FBAC2",
|
||||
storeColorWhenPlayerTurn: "#6cab94",
|
||||
holeColor: "#D3D4D8",
|
||||
ballColor: "#393E46",
|
||||
ballLightColor: "#393E46",
|
||||
holeAnimateColor: "#afb3a4",
|
||||
};
|
||||
import { Context } from "../context";
|
||||
import BoardViewModel from "../viewmodel/BoardViewModel";
|
||||
import PitViewModel from "../viewmodel/PitViewModel";
|
||||
|
||||
const BallView: FunctionComponent<{ color: string }> = ({ color }) => {
|
||||
return (
|
||||
@ -48,41 +27,19 @@ function range(size: number) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
const PitContainer: FunctionComponent<{
|
||||
pit: Pit;
|
||||
isAnimating: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ pit, isAnimating, onClick }) => {
|
||||
if (isAnimating) {
|
||||
pit.stoneCount += 1;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<HoleView
|
||||
hole={pit}
|
||||
color={isAnimating ? theme.holeAnimateColor : theme.holeColor}
|
||||
stoneColor={isAnimating ? theme.ballLightColor : theme.ballColor}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const HoleView: FunctionComponent<{
|
||||
hole: Pit;
|
||||
color: string;
|
||||
stoneColor: string;
|
||||
pitViewModel: PitViewModel;
|
||||
onClick: () => void;
|
||||
}> = ({ hole, color, stoneColor, onClick }) => {
|
||||
const balls = [...range(hole.stoneCount)].map((i) => (
|
||||
<BallView color={stoneColor} />
|
||||
}> = ({ pitViewModel, onClick }) => {
|
||||
const balls = [...range(pitViewModel.stoneCount)].map((i) => (
|
||||
<BallView color={pitViewModel.stoneColor} />
|
||||
));
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{
|
||||
background: color,
|
||||
background: pitViewModel.pitColor,
|
||||
margin: "5px",
|
||||
padding: "5px",
|
||||
borderRadius: "10vw",
|
||||
@ -100,21 +57,19 @@ const HoleView: FunctionComponent<{
|
||||
};
|
||||
|
||||
const StoreView: FunctionComponent<{
|
||||
store: Bank;
|
||||
color: string;
|
||||
stoneColor: string;
|
||||
pitViewModel: PitViewModel;
|
||||
gridColumn: string;
|
||||
gridRow: string;
|
||||
}> = ({ store, color, stoneColor, gridColumn, gridRow }) => {
|
||||
const balls = [...range(store.stoneCount)].map((i) => (
|
||||
<BallView color={stoneColor} />
|
||||
}> = ({ pitViewModel, gridColumn, gridRow }) => {
|
||||
const balls = [...range(pitViewModel.stoneCount)].map((i) => (
|
||||
<BallView color={pitViewModel.stoneColor} />
|
||||
));
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
gridColumn: gridColumn,
|
||||
gridRow: gridRow,
|
||||
background: color,
|
||||
background: pitViewModel.pitColor,
|
||||
margin: "5px",
|
||||
borderRadius: "10vw",
|
||||
display: "flex",
|
||||
@ -131,64 +86,49 @@ const StoreView: FunctionComponent<{
|
||||
|
||||
const BoardView: FunctionComponent<{
|
||||
game?: MancalaGame;
|
||||
context: Context;
|
||||
boardId: string;
|
||||
boardViewModel: BoardViewModel;
|
||||
userKey: string;
|
||||
onHoleSelect: (index: number, hole: Pit) => void;
|
||||
animationPitIndex: number;
|
||||
}> = ({ game, userKey, onHoleSelect, animationPitIndex }) => {
|
||||
}> = ({ game, context, boardId, boardViewModel, userKey, onHoleSelect }) => {
|
||||
const createPitView = (pitViewModel: PitViewModel, onClick: () => void) => {
|
||||
return <HoleView pitViewModel={pitViewModel} onClick={onClick} />;
|
||||
};
|
||||
const player1Pits = game?.board.player1Pits.map((pit) => {
|
||||
const isAnimating = pit.index === animationPitIndex;
|
||||
return (
|
||||
<PitContainer
|
||||
pit={pit}
|
||||
isAnimating={isAnimating}
|
||||
onClick={() => {
|
||||
if (game.turnPlayerId === game.player1Id)
|
||||
onHoleSelect(game.board.player1Pits.indexOf(pit), pit);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const pitViewModel = boardViewModel.pits[pit.index];
|
||||
return createPitView(pitViewModel, () => {
|
||||
if (game.turnPlayerId === game.player1Id)
|
||||
onHoleSelect(game.board.player1Pits.indexOf(pit), pit);
|
||||
});
|
||||
});
|
||||
|
||||
const player2Pits = game!!.board.player2Pits.map((pit) => {
|
||||
const isAnimating = pit.index === animationPitIndex;
|
||||
return (
|
||||
<PitContainer
|
||||
pit={pit}
|
||||
isAnimating={isAnimating}
|
||||
onClick={() => {
|
||||
if (game.turnPlayerId === game.player2Id)
|
||||
onHoleSelect(game.board.player2Pits.indexOf(pit), pit);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const pitViewModel = boardViewModel.pits[pit.index];
|
||||
return createPitView(pitViewModel, () => {
|
||||
if (game.turnPlayerId === game.player2Id)
|
||||
onHoleSelect(game.board.player2Pits.indexOf(pit), pit);
|
||||
});
|
||||
});
|
||||
|
||||
const isUserTurn = game.checkIsPlayerTurn(userKey);
|
||||
|
||||
const animatingPlayer1Bank =
|
||||
game.board.player1Bank.index === animationPitIndex;
|
||||
const animatingPlayer2Bank =
|
||||
game.board.player2Bank.index === animationPitIndex;
|
||||
|
||||
const storeColorPlayer1 = animatingPlayer1Bank
|
||||
? theme.holeAnimateColor
|
||||
: isUserTurn
|
||||
? theme.storeColor
|
||||
: theme.storeColorWhenPlayerTurn;
|
||||
|
||||
const storeStoneColorPlayer1 = animatingPlayer1Bank
|
||||
? theme.ballLightColor
|
||||
: theme.ballColor;
|
||||
|
||||
const storeColorPlayer2 = animatingPlayer2Bank
|
||||
? theme.holeAnimateColor
|
||||
: isUserTurn
|
||||
? theme.storeColor
|
||||
: theme.storeColorWhenPlayerTurn;
|
||||
|
||||
const storeStoneColorPlayer2 = animatingPlayer2Bank
|
||||
? theme.ballLightColor
|
||||
: theme.ballColor;
|
||||
const theme = context.themeManager.theme;
|
||||
const player1BankViewModel =
|
||||
boardViewModel.pits[game.board.player1BankIndex()];
|
||||
const player2BankViewModel =
|
||||
boardViewModel.pits[game.board.player2BankIndex()];
|
||||
const player1Bank = (
|
||||
<StoreView
|
||||
pitViewModel={player1BankViewModel}
|
||||
gridColumn="1 / 2"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
);
|
||||
const player2Bank = (
|
||||
<StoreView
|
||||
pitViewModel={player2BankViewModel}
|
||||
gridColumn="8 / 9"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -206,16 +146,12 @@ const BoardView: FunctionComponent<{
|
||||
{userKey === game.player2Id ? (
|
||||
<>
|
||||
<StoreView
|
||||
store={game!!.board.player1Bank}
|
||||
color={storeColorPlayer1}
|
||||
stoneColor={storeStoneColorPlayer1}
|
||||
pitViewModel={player1BankViewModel}
|
||||
gridColumn="1 / 2"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
<StoreView
|
||||
store={game!!.board.player2Bank}
|
||||
color={storeColorPlayer2}
|
||||
stoneColor={storeStoneColorPlayer2}
|
||||
pitViewModel={player2BankViewModel}
|
||||
gridColumn="8 / 9"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
@ -225,17 +161,13 @@ const BoardView: FunctionComponent<{
|
||||
) : (
|
||||
<>
|
||||
<StoreView
|
||||
store={game!!.board.player2Bank}
|
||||
color={storeColorPlayer2}
|
||||
stoneColor={storeStoneColorPlayer2}
|
||||
gridColumn="1 / 2"
|
||||
pitViewModel={player1BankViewModel}
|
||||
gridColumn="8 / 9"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
<StoreView
|
||||
store={game!!.board.player1Bank}
|
||||
color={storeColorPlayer1}
|
||||
stoneColor={storeStoneColorPlayer1}
|
||||
gridColumn="8 / 9"
|
||||
pitViewModel={player2BankViewModel}
|
||||
gridColumn="1 / 2"
|
||||
gridRow="1 / 3"
|
||||
/>
|
||||
{player2Pits.reverse()}
|
||||
|
||||
@ -1,23 +1,28 @@
|
||||
import { Texts, TrTr } from "./const/texts"
|
||||
import { RTMT } from "./rtmt/rtmt"
|
||||
import { RTMTWS } from "./rtmt/rtmt_websocket"
|
||||
import { UserKeyStore, UserKeyStoreImpl } from "./store/key_store"
|
||||
import { Texts, TrTr } from "./const/texts";
|
||||
import { RTMT } from "./rtmt/rtmt";
|
||||
import { RTMTWS } from "./rtmt/rtmt_websocket";
|
||||
import { UserKeyStore, UserKeyStoreImpl } from "./store/key_store";
|
||||
import defaultTheme from "./theme/DefaultTheme";
|
||||
import ThemeManager from "./theme/ThemeManager";
|
||||
|
||||
type Context = {
|
||||
rtmt : RTMT
|
||||
userKeyStore : UserKeyStore
|
||||
texts : Texts
|
||||
}
|
||||
export type Context = {
|
||||
rtmt: RTMT;
|
||||
userKeyStore: UserKeyStore;
|
||||
texts: Texts;
|
||||
themeManager: ThemeManager;
|
||||
};
|
||||
|
||||
export const initContext = ()=> {
|
||||
const rtmt = new RTMTWS()
|
||||
const userKeyStore = new UserKeyStoreImpl()
|
||||
const texts = TrTr
|
||||
return {
|
||||
rtmt : rtmt,
|
||||
userKeyStore : userKeyStore,
|
||||
texts : texts,
|
||||
}
|
||||
}
|
||||
export const initContext = () => {
|
||||
const rtmt = new RTMTWS();
|
||||
const userKeyStore = new UserKeyStoreImpl();
|
||||
const texts = TrTr;
|
||||
const themeManager = new ThemeManager(defaultTheme);
|
||||
return {
|
||||
rtmt: rtmt,
|
||||
userKeyStore: userKeyStore,
|
||||
texts: texts,
|
||||
themeManager,
|
||||
};
|
||||
};
|
||||
|
||||
export const context : Context = initContext()
|
||||
export const context: Context = initContext();
|
||||
|
||||
11
src/factory/BoardViewModelFactory.ts
Normal file
11
src/factory/BoardViewModelFactory.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import BoardViewModel from "../viewmodel/BoardViewModel";
|
||||
import PitViewModel from "../viewmodel/PitViewModel";
|
||||
|
||||
export default class BoardViewModelFactory {
|
||||
public static create(
|
||||
id: string,
|
||||
pitViewModels: PitViewModel[]
|
||||
): BoardViewModel {
|
||||
return new BoardViewModel(id, pitViewModels);
|
||||
}
|
||||
}
|
||||
13
src/factory/PitViewModelFactory.ts
Normal file
13
src/factory/PitViewModelFactory.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import PitViewModel from "../viewmodel/PitViewModel";
|
||||
|
||||
export class PitViewModelFactory {
|
||||
public static create(params: {
|
||||
id: string;
|
||||
stoneCount: number;
|
||||
stoneColor: string;
|
||||
pitColor: string;
|
||||
}): PitViewModel {
|
||||
const { id, stoneCount, stoneColor, pitColor } = params;
|
||||
return new PitViewModel(id, stoneCount, stoneColor, pitColor);
|
||||
}
|
||||
}
|
||||
18
src/theme/DefaultTheme.ts
Normal file
18
src/theme/DefaultTheme.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
const defaultTheme: Theme = {
|
||||
background: "#EEEEEE",
|
||||
boardColor: "#4D606E",
|
||||
boardColorWhenPlayerTurn: "#84b8a6",
|
||||
storeColor: "#3FBAC2",
|
||||
storeColorWhenPlayerTurn: "#6cab94",
|
||||
holeColor: "#D3D4D8",
|
||||
ballColor: "#393E46",
|
||||
ballLightColor: "#393E46",
|
||||
pitGameMoveAnimateColor: "#c9b43c",
|
||||
pitEmptyPitAnimateColor: "#5d7322",
|
||||
pitLastStoneInBankPitAnimateColor: "#9463f7",
|
||||
pitGetRivalStonePitAnimateColor: "#ff3d44",
|
||||
};
|
||||
|
||||
export default defaultTheme;
|
||||
14
src/theme/Theme.ts
Normal file
14
src/theme/Theme.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export type Theme = {
|
||||
background: string;
|
||||
boardColor: string;
|
||||
boardColorWhenPlayerTurn: string;
|
||||
storeColor: string;
|
||||
storeColorWhenPlayerTurn: string;
|
||||
holeColor: string;
|
||||
ballColor: string;
|
||||
ballLightColor: string;
|
||||
pitGameMoveAnimateColor: string;
|
||||
pitEmptyPitAnimateColor: string;
|
||||
pitLastStoneInBankPitAnimateColor: string;
|
||||
pitGetRivalStonePitAnimateColor: string;
|
||||
};
|
||||
18
src/theme/ThemeManager.ts
Normal file
18
src/theme/ThemeManager.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
export default class ThemeManager {
|
||||
_theme: Theme;
|
||||
onThemeChange: (theme: Theme) => void;
|
||||
constructor(theme: Theme) {
|
||||
this._theme = theme;
|
||||
}
|
||||
|
||||
public get theme() {
|
||||
return this._theme;
|
||||
}
|
||||
|
||||
public set theme(value) {
|
||||
this._theme = value;
|
||||
this.onThemeChange?.(value);
|
||||
}
|
||||
}
|
||||
10
src/viewmodel/BoardViewModel.ts
Normal file
10
src/viewmodel/BoardViewModel.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import PitViewModel from "./PitViewModel";
|
||||
|
||||
export default class BoardViewModel {
|
||||
id: string;
|
||||
pits: PitViewModel[];
|
||||
constructor(id: string, pits: PitViewModel[]) {
|
||||
this.id = id;
|
||||
this.pits = pits;
|
||||
}
|
||||
}
|
||||
18
src/viewmodel/PitViewModel.ts
Normal file
18
src/viewmodel/PitViewModel.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export default class PitViewModel {
|
||||
id: string;
|
||||
stoneCount: number;
|
||||
stoneColor: string;
|
||||
pitColor: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
stoneCount: number,
|
||||
stoneColor: string,
|
||||
pitColor: string
|
||||
) {
|
||||
this.id = id;
|
||||
this.stoneCount = stoneCount;
|
||||
this.stoneColor = stoneColor;
|
||||
this.pitColor = pitColor;
|
||||
}
|
||||
}
|
||||
10
yarn.lock
10
yarn.lock
@ -1042,6 +1042,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz"
|
||||
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
|
||||
|
||||
"@types/uuid@^8.3.4":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
abab@^2.0.0:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz"
|
||||
@ -5377,6 +5382,11 @@ uuid@^3.3.2:
|
||||
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v8-compile-cache@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user