added game state traveler
This commit is contained in:
parent
6e101bea02
commit
1468cf5e24
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,29 +101,14 @@ 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}, ()=>{
|
this.setState({gameInfo: gameInfo}, ()=>{
|
||||||
// toggle fade in
|
// toggle fade in
|
||||||
if(this.state.gameInfo.toggleFade !== ""){
|
if(this.state.gameInfo.toggleFade !== ""){
|
||||||
// doubleRaf(()=>{
|
|
||||||
// let gameInfo = deepCopy(this.state.gameInfo);
|
|
||||||
// gameInfo.toggleFade = "";
|
|
||||||
// this.setState({gameInfo: gameInfo});
|
|
||||||
// });
|
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
let gameInfo = deepCopy(this.state.gameInfo);
|
let gameInfo = deepCopy(this.state.gameInfo);
|
||||||
gameInfo.toggleFade = "";
|
gameInfo.toggleFade = "";
|
||||||
|
@ -95,9 +116,6 @@ class DoudizhuGameView extends React.Component {
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}else{
|
|
||||||
console.log("Mismatched current player index");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 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>*/}
|
||||||
|
|
||||||
|
</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) }
|
{ 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"}}>
|
||||||
|
|
Loading…
Reference in New Issue