added game state traveler

This commit is contained in:
Songyi Huang 2020-02-11 13:21:55 -08:00
parent 6e101bea02
commit 1468cf5e24
7 changed files with 114 additions and 50 deletions

View File

@ -32,7 +32,7 @@ function getGameHistory(){
console.log(testDoudizhuData); console.log(testDoudizhuData);
}); });
fs.readFile("./sample_data/sample_leduc_holdem-test.json", (err, data) => { fs.readFile("./sample_data/sample_leduc_holdem.json", (err, data) => {
if (err) throw err; if (err) throw err;
testLeducHoldemData = JSON.parse(data); testLeducHoldemData = JSON.parse(data);
console.log(testLeducHoldemData); console.log(testLeducHoldemData);

View File

@ -269,11 +269,11 @@
"probability": 0.5 "probability": 0.5
}, },
{ {
"move": "S7 H7", "move": "CT DT",
"probability": 0.1 "probability": 0.1
}, },
{ {
"move": "HA CA", "move": "RJ BJ",
"probability": 0.05 "probability": 0.05
} }
] ]

View File

@ -168,11 +168,15 @@
.played-card-area { .played-card-area {
width: 100%; width: 100%;
position: relative; position: relative;
transform: translateY(25px); //transform: translateY(25px);
.playingCards ul.hand {
margin-bottom: 0;
}
.non-card { .non-card {
height: 138px; height: 138px;
transform: translateY(-25px); //transform: translateY(-25px);
} }
} }
} }

View File

@ -1,9 +1,15 @@
.game-controller { .game-controller {
width: 500px; width: 100%;
padding: 20px; padding: 20px;
.el-row { .el-row {
margin-bottom: 10px; margin-bottom: 10px;
} }
.status-button {
margin-left: 5px;
margin-right: 5px;
width: 125px;
}
} }
.doudizhu-view-container { .doudizhu-view-container {
@ -82,7 +88,7 @@
} }
.non-card.hide { .non-card.hide {
visibility: hidden; visibility: hidden;
transition: visibility 0.1s, opacity 0.05s; transition: visibility 0.2s, opacity 0.15s;
opacity: 0; opacity: 0;
pointer-events:none; pointer-events:none;
} }

View File

@ -2,6 +2,7 @@ import React from 'react';
import { translateCardData, millisecond2Second, computeHandCardsWidth } from '../../utils' import { translateCardData, millisecond2Second, computeHandCardsWidth } from '../../utils'
import '../../assets/doudizhu.scss'; import '../../assets/doudizhu.scss';
import {fade} from "@material-ui/core";
class DoudizhuGameBoard extends React.Component { class DoudizhuGameBoard extends React.Component {
constructor(props) { constructor(props) {
@ -97,7 +98,7 @@ class DoudizhuGameBoard extends React.Component {
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
if(prevProps.turn !== this.props.turn && this.props.turn !== 0){ if(prevProps.turn !== this.props.turn && this.props.turn !== 0 && this.props.gameStatus === "playing"){
// new turn starts // new turn starts
this.props.runNewTurn(prevProps); this.props.runNewTurn(prevProps);
} }

View File

@ -11,7 +11,7 @@ class LeducHoldemGameBoard extends React.Component {
computeHand(card) { computeHand(card) {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card); const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return ( return (
<div className="playingCards"> <div className="playingCards faceImages">
<div className={`card ${rankClass} ${suitClass}`}> <div className={`card ${rankClass} ${suitClass}`}>
<span className="rank">{rankText}</span> <span className="rank">{rankText}</span>
<span className="suit">{suitText}</span> <span className="suit">{suitText}</span>
@ -31,14 +31,14 @@ class LeducHoldemGameBoard extends React.Component {
displayPublicCard(){ displayPublicCard(){
if(this.props.round === 0){ if(this.props.round === 0){
return ( return (
<div className="playingCards"> <div className="playingCards faceImages">
<div className="card back">*</div> <div className="card back">*</div>
</div> </div>
) )
}else{ }else{
const [rankClass, suitClass, rankText, suitText] = translateCardData(this.props.publicCard); const [rankClass, suitClass, rankText, suitText] = translateCardData(this.props.publicCard);
return ( return (
<div className="playingCards"> <div className="playingCards faceImages">
<div className={`card ${rankClass} ${suitClass}`}> <div className={`card ${rankClass} ${suitClass}`}>
<span className="rank">{rankText}</span> <span className="rank">{rankText}</span>
<span className="suit">{suitText}</span> <span className="suit">{suitText}</span>

View File

@ -9,10 +9,14 @@ import Slider from '@material-ui/core/Slider';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import ButtonGroup from '@material-ui/core/ButtonGroup';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded'; import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded'; import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded';
import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded'; import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded';
import NotInterestedIcon from '@material-ui/icons/NotInterested'; import NotInterestedIcon from '@material-ui/icons/NotInterested';
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
import SkipNextIcon from '@material-ui/icons/SkipNext';
import SkipPreviousIcon from '@material-ui/icons/SkipPrevious';
class DoudizhuGameView extends React.Component { class DoudizhuGameView extends React.Component {
constructor(props) { constructor(props) {
@ -20,11 +24,11 @@ class DoudizhuGameView extends React.Component {
const mainViewerId = 0; // Id of the player at the bottom of screen const mainViewerId = 0; // Id of the player at the bottom of screen
this.initConsiderationTime = 2000; this.initConsiderationTime = 2000;
this.considerationTimeDeduction = 100; this.considerationTimeDeduction = 200;
this.gameStateTimeout = null; this.gameStateTimeout = null;
this.apiUrl = window.g.apiUrl; this.apiUrl = window.g.apiUrl;
this.moveHistory = []; this.moveHistory = [];
this.gameStateHistory = [];
this.initGameState = { this.initGameState = {
gameStatus: "ready", // "ready", "playing", "paused", "over" gameStatus: "ready", // "ready", "playing", "paused", "over"
playerInfo: [], playerInfo: [],
@ -49,6 +53,38 @@ class DoudizhuGameView extends React.Component {
return cardStr === "P" ? cardStr : cardStr.split(" "); return cardStr === "P" ? cardStr : cardStr.split(" ");
} }
generateNewState(){
let gameInfo = deepCopy(this.state.gameInfo);
// check if the game state of next turn is already in game state history
if(this.state.gameInfo.turn+1 < this.gameStateHistory.length){
gameInfo = deepCopy(this.gameStateHistory[this.state.gameInfo.turn+1]);
}else{
let newMove = this.moveHistory[this.state.gameInfo.turn];
if(newMove.playerIdx === this.state.gameInfo.currentPlayer) {
gameInfo.latestAction[newMove.playerIdx] = this.cardStr2Arr(newMove.move);
gameInfo.turn++;
gameInfo.currentPlayer = (gameInfo.currentPlayer + 1) % 3;
// take away played cards from player's hands
const remainedCards = removeCards(gameInfo.latestAction[newMove.playerIdx], gameInfo.hands[newMove.playerIdx]);
if (remainedCards !== false) {
gameInfo.hands[newMove.playerIdx] = remainedCards;
} else {
console.log("Cannot find cards in move from player's hand");
}
gameInfo.considerationTime = this.initConsiderationTime;
}else {
console.log("Mismatched current player index");
}
}
// if current state is new to game state history, push it to the game state history array
if(gameInfo.turn === this.gameStateHistory.length){
this.gameStateHistory.push(gameInfo);
}else{
console.log("inconsistent game state history length and turn number");
}
return gameInfo;
}
gameStateTimer() { gameStateTimer() {
this.gameStateTimeout = setTimeout(()=>{ this.gameStateTimeout = setTimeout(()=>{
let currentConsiderationTime = this.state.gameInfo.considerationTime; let currentConsiderationTime = this.state.gameInfo.considerationTime;
@ -65,39 +101,21 @@ class DoudizhuGameView extends React.Component {
this.setState({gameInfo: gameInfo}); this.setState({gameInfo: gameInfo});
this.gameStateTimer(); this.gameStateTimer();
}else{ }else{
let res = this.moveHistory[this.state.gameInfo.turn]; let gameInfo = this.generateNewState();
if(res.playerIdx === this.state.gameInfo.currentPlayer){ gameInfo.gameStatus = "playing";
let gameInfo = deepCopy(this.state.gameInfo); if(this.state.gameInfo.toggleFade === "fade-out") {
gameInfo.latestAction[res.playerIdx] = this.cardStr2Arr(res.move);
gameInfo.turn++;
gameInfo.toggleFade = "fade-in"; gameInfo.toggleFade = "fade-in";
gameInfo.currentPlayer = (gameInfo.currentPlayer+1)%3;
// take away played cards from player's hands
const remainedCards = removeCards(gameInfo.latestAction[res.playerIdx], gameInfo.hands[res.playerIdx]);
if(remainedCards !== false){
gameInfo.hands[res.playerIdx] = remainedCards;
}else{
console.log("Cannot find cards in move from player's hand");
}
gameInfo.considerationTime = this.initConsiderationTime;
this.setState({gameInfo: gameInfo}, ()=>{
// toggle fade in
if(this.state.gameInfo.toggleFade !== ""){
// doubleRaf(()=>{
// let gameInfo = deepCopy(this.state.gameInfo);
// gameInfo.toggleFade = "";
// this.setState({gameInfo: gameInfo});
// });
setTimeout(()=>{
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}, 200);
}
});
}else{
console.log("Mismatched current player index");
} }
this.setState({gameInfo: gameInfo}, ()=>{
// toggle fade in
if(this.state.gameInfo.toggleFade !== ""){
setTimeout(()=>{
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}, 200);
}
});
} }
}, this.considerationTimeDeduction); }, this.considerationTimeDeduction);
} }
@ -119,6 +137,7 @@ class DoudizhuGameView extends React.Component {
}); });
// the first player should be landlord // the first player should be landlord
gameInfo.currentPlayer = res.playerInfo.find(element=>{return element.role === "landlord"}).index; gameInfo.currentPlayer = res.playerInfo.find(element=>{return element.role === "landlord"}).index;
this.gameStateHistory.push(gameInfo);
this.setState({gameInfo: gameInfo}, ()=>{ this.setState({gameInfo: gameInfo}, ()=>{
if(this.gameStateTimeout){ if(this.gameStateTimeout){
window.clearTimeout(this.gameStateTimeout); window.clearTimeout(this.gameStateTimeout);
@ -182,13 +201,13 @@ class DoudizhuGameView extends React.Component {
gameStatusButton(status){ gameStatusButton(status){
switch (status) { switch (status) {
case "ready": case "ready":
return <Button variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Start Replay</Button>; return <Button className={"status-button"} variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Start</Button>;
case "playing": case "playing":
return <Button variant={"contained"} startIcon={<PauseCircleOutlineRoundedIcon />} color="secondary" onClick={()=>{this.pauseReplay()}}>Pause</Button>; return <Button className={"status-button"} variant={"contained"} startIcon={<PauseCircleOutlineRoundedIcon />} color="secondary" onClick={()=>{this.pauseReplay()}}>Pause</Button>;
case "paused": case "paused":
return <Button variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.resumeReplay()}}>Resume</Button>; return <Button className={"status-button"} variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.resumeReplay()}}>Resume</Button>;
case "over": case "over":
return <Button variant={"contained"} startIcon={<ReplayRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Restart</Button>; return <Button className={"status-button"} variant={"contained"} startIcon={<ReplayRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Restart</Button>;
default: default:
alert(`undefined game status: ${status}`); alert(`undefined game status: ${status}`);
} }
@ -245,6 +264,20 @@ class DoudizhuGameView extends React.Component {
} }
} }
go2PrevGameState() {
let gameInfo = deepCopy(this.gameStateHistory[this.state.gameInfo.turn - 1]);
gameInfo.gameStatus = "paused";
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}
go2NextGameState() {
let gameInfo = this.generateNewState();
gameInfo.gameStatus = "paused";
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}
render(){ render(){
let sliderValueText = (value) => { let sliderValueText = (value) => {
return `${value}°C`; return `${value}°C`;
@ -296,6 +329,7 @@ class DoudizhuGameView extends React.Component {
turn={this.state.gameInfo.turn} turn={this.state.gameInfo.turn}
runNewTurn={(prevTurn)=>this.runNewTurn(prevTurn)} runNewTurn={(prevTurn)=>this.runNewTurn(prevTurn)}
toggleFade={this.state.gameInfo.toggleFade} toggleFade={this.state.gameInfo.toggleFade}
gameStatus={this.state.gameInfo.gameStatus}
/> />
</Paper> </Paper>
</div> </div>
@ -320,9 +354,28 @@ class DoudizhuGameView extends React.Component {
</Layout.Row> </Layout.Row>
<div className="game-controller"> <div className="game-controller">
<Layout.Row> <Layout.Row>
<Layout.Col span="24"> <Layout.Col span="12">
{/*<Button variant={"contained"} color="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button>*/} {/*<Button variant={"contained"} color="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button>*/}
{ this.gameStatusButton(this.state.gameInfo.gameStatus) }
</Layout.Col>
<Layout.Col span="12">
<Button
variant="contained"
color="primary"
disabled={this.state.gameInfo.gameStatus !== "paused" || this.state.gameInfo.turn === 0}
onClick={()=>{this.go2PrevGameState()}}
>
<SkipPreviousIcon />
</Button>
{ this.gameStatusButton(this.state.gameInfo.gameStatus) }
<Button
variant="contained"
color="primary"
disabled={this.state.gameInfo.gameStatus !== "paused"}
onClick={()=>{this.go2NextGameState()}}
>
<SkipNextIcon />
</Button>
</Layout.Col> </Layout.Col>
</Layout.Row> </Layout.Row>
<Layout.Row style={{height: "31px"}}> <Layout.Row style={{height: "31px"}}>