format leduc

This commit is contained in:
lpan 2020-02-18 12:23:29 -08:00
parent ee0c08c224
commit d9aefc102e
2 changed files with 337 additions and 112 deletions

View File

@ -74,7 +74,7 @@ class DoudizhuGameView extends React.Component {
console.log("Cannot find cards in move from player's hand"); console.log("Cannot find cards in move from player's hand");
} }
gameInfo.considerationTime = this.initConsiderationTime; gameInfo.considerationTime = this.initConsiderationTime;
gameInfo.completedPercent = (this.state.gameInfo.turn + 1) * 100.0 / this.moveHistory.length; gameInfo.completedPercent += 100.0 / this.moveHistory.length;
}else { }else {
console.log("Mismatched current player index"); console.log("Mismatched current player index");
} }
@ -283,7 +283,7 @@ class DoudizhuGameView extends React.Component {
render(){ render(){
let sliderValueText = (value) => { let sliderValueText = (value) => {
return `${value}°C`; return value;
}; };
const gameSpeedMarks = [ const gameSpeedMarks = [
{ {

View File

@ -4,8 +4,20 @@ import '../assets/gameview.scss';
import {LeducHoldemGameBoard} from '../components/GameBoard'; import {LeducHoldemGameBoard} from '../components/GameBoard';
import {deepCopy} from "../utils"; import {deepCopy} from "../utils";
import { Button, Layout } from 'element-react'; import { Layout } from 'element-react';
import Slider from '@material-ui/core/Slider'; import Slider from '@material-ui/core/Slider';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import Divider from '@material-ui/core/Divider';
import LinearProgress from '@material-ui/core/LinearProgress';
import ButtonGroup from '@material-ui/core/ButtonGroup';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded';
import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded';
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 LeducHoldemGameView extends React.Component { class LeducHoldemGameView extends React.Component {
constructor(props) { constructor(props) {
@ -17,8 +29,10 @@ class LeducHoldemGameView extends React.Component {
this.gameStateTimeout = null; this.gameStateTimeout = null;
this.apiUrl = window.g.apiUrl; this.apiUrl = window.g.apiUrl;
this.moveHistory = []; this.moveHistory = [];
this.moveHistoryTotalLength = null;
this.gameStateHistory = [[],[]];
this.initGameState = { this.initGameState = {
gameStatus: "ready", // "ready", "playing", "paused", "over"
playerInfo: [], playerInfo: [],
hands: [], hands: [],
latestAction: ["", ""], latestAction: ["", ""],
@ -28,78 +42,40 @@ class LeducHoldemGameView extends React.Component {
pot: [1, 1], pot: [1, 1],
publicCard: "", publicCard: "",
currentPlayer: null, currentPlayer: null,
considerationTime: this.initConsiderationTime considerationTime: this.initConsiderationTime,
completedPercent: 0,
}; };
this.state = { this.state = {
gameInfo: this.initGameState, gameInfo: this.initGameState,
gameStateLoop: null, gameStateLoop: null,
considerationTimeSetting: this.initConsiderationTime gameSpeed: 0
} }
} }
retrieveReplayData(){ generateNewState(){
// for test use // console.log(this.state.gameInfo.latestAction);
const replayId = 0;
// reset game state
this.setState({gameInfo: this.initGameState});
axios.get(`${this.apiUrl}/replay/leduc_holdem/${replayId}`)
.then(res => {
res = res.data;
this.moveHistory = res.moveHistory;
let gameInfo = deepCopy(this.state.gameInfo); let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.hands = res.initHands;
gameInfo.playerInfo = res.playerInfo;
gameInfo.currentPlayer = res.moveHistory[0][0].playerIdx;
gameInfo.publicCard = res.publicCard;
this.setState({gameInfo: gameInfo}, ()=>{
this.startReplay();
});
})
.catch(err=>{
console.log("err", err);
})
}
startReplay(){
if(this.gameStateTimeout){
window.clearTimeout(this.gameStateTimeout);
this.gameStateTimeout = null;
}
// loop to update game state
this.gameStateTimer();
}
gameStateTimer(){
this.gameStateTimeout = setTimeout(()=>{
let currentConsiderationTime = this.state.gameInfo.considerationTime;
if(currentConsiderationTime > 0) {
currentConsiderationTime -= this.considerationTimeDeduction;
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.considerationTime = currentConsiderationTime;
this.setState({gameInfo: gameInfo});
this.gameStateTimer();
}else{
console.log(this.state.gameInfo.latestAction);
const turn = this.state.gameInfo.turn; const turn = this.state.gameInfo.turn;
if(turn >= this.moveHistory[this.state.gameInfo.round].length){ if(turn >= this.moveHistory[this.state.gameInfo.round].length){
if(this.state.gameInfo.round === 0){ if(this.state.gameInfo.round === 0){
// todo: if it's the first round, then reveal the public card and start the second round // todo: if it's the first round, then reveal the public card and start the second round
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.turn = 0; gameInfo.turn = 0;
gameInfo.round = 1; gameInfo.round = 1;
gameInfo.latestAction = ["", ""]; gameInfo.latestAction = ["", ""];
gameInfo.currentPlayer = this.moveHistory[1][0].playerIdx; gameInfo.currentPlayer = this.moveHistory[1][0].playerIdx;
gameInfo.considerationTime = this.state.considerationTimeSetting; gameInfo.considerationTime = this.initConsiderationTime;
this.setState({gameInfo: gameInfo}, ()=>{ // gameInfo.completedPercent += 100.0 / this.moveHistoryTotalLength;
this.gameStateTimer(); this.setState({ gameInfo: gameInfo });
});
}else{ }else{
alert('Game Ends!'); gameInfo.gameStatus = "over";
this.setState({ gameInfo: gameInfo });
setTimeout(()=>{
// TODO: show winner
alert("Game Ends!");
}, 200);
return gameInfo;
} }
}else{ }else{
let gameInfo = deepCopy(this.state.gameInfo);
if(gameInfo.currentPlayer === this.moveHistory[gameInfo.round][gameInfo.turn].playerIdx){ if(gameInfo.currentPlayer === this.moveHistory[gameInfo.round][gameInfo.turn].playerIdx){
gameInfo.latestAction[gameInfo.currentPlayer] = this.moveHistory[gameInfo.round][gameInfo.turn].move; gameInfo.latestAction[gameInfo.currentPlayer] = this.moveHistory[gameInfo.round][gameInfo.turn].move;
switch (gameInfo.latestAction[gameInfo.currentPlayer]) { switch (gameInfo.latestAction[gameInfo.currentPlayer]) {
@ -122,29 +98,200 @@ class LeducHoldemGameView extends React.Component {
const foldedId = foldedFound ? foldedFound.id : -1; const foldedId = foldedFound ? foldedFound.id : -1;
const winnerFound = gameInfo.playerInfo.find(element=>{return element.index === (gameInfo.currentPlayer+1)%2}); const winnerFound = gameInfo.playerInfo.find(element=>{return element.index === (gameInfo.currentPlayer+1)%2});
const winnerId = winnerFound ? winnerFound.id : -1; const winnerId = winnerFound ? winnerFound.id : -1;
gameInfo.gameStatus = "over";
this.setState({ gameInfo: gameInfo });
setTimeout(()=>{
alert(`Player ${foldedId} folded, player ${winnerId} wins!`); alert(`Player ${foldedId} folded, player ${winnerId} wins!`);
return ; }, 200);
return gameInfo;
default: default:
console.log("Error in player's latest action"); console.log("Error in player's latest action");
} }
gameInfo.turn++; gameInfo.turn++;
gameInfo.currentPlayer = (gameInfo.currentPlayer+1)%2; gameInfo.currentPlayer = (gameInfo.currentPlayer+1)%2;
gameInfo.considerationTime = this.state.considerationTimeSetting; gameInfo.considerationTime = this.initConsiderationTime;
this.setState({gameInfo: gameInfo}, ()=>{ gameInfo.completedPercent += 100.0 / this.moveHistoryTotalLength;
this.gameStateTimer(); this.setState({ gameInfo: gameInfo });
});
}else{ }else{
console.log("Mismatch in current player & move history"); console.log("Mismatch in current player & move history");
} }
} }
// if current state is new to game state history, push it to the game state history array
if(gameInfo.turn === this.gameStateHistory[gameInfo.round].length){
this.gameStateHistory[gameInfo.round].push(gameInfo);
}else{
console.log("inconsistent game state history length and turn number");
}
return gameInfo;
}
gameStateTimer(){
this.gameStateTimeout = setTimeout(()=>{
let currentConsiderationTime = this.state.gameInfo.considerationTime;
if(currentConsiderationTime > 0) {
currentConsiderationTime -= this.considerationTimeDeduction * Math.pow(2, this.state.gameSpeed);
currentConsiderationTime = currentConsiderationTime < 0 ? 0 : currentConsiderationTime;
if(currentConsiderationTime === 0 && this.state.gameSpeed < 2){
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.toggleFade = "fade-out";
this.setState({gameInfo: gameInfo});
}
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.considerationTime = currentConsiderationTime;
this.setState({gameInfo: gameInfo});
this.gameStateTimer();
}else{
let gameInfo = this.generateNewState();
if(gameInfo.gameStatus == "over") return;
this.gameStateTimer();
gameInfo.gameStatus = "playing";
if(this.state.gameInfo.toggleFade === "fade-out") {
gameInfo.toggleFade = "fade-in";
}
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);
} }
startReplay() {
// for test use
const replayId = 0;
axios.get(`${this.apiUrl}/replay/leduc_holdem/${replayId}`)
.then(res => {
res = res.data;
// init replay info
this.moveHistory = res.moveHistory;
this.moveHistoryTotalLength = this.moveHistory.reduce((count, round) => count + round.length, 0);
let gameInfo = deepCopy(this.initGameState);
gameInfo.gameStatus = "playing";
gameInfo.playerInfo = res.playerInfo;
gameInfo.hands = res.initHands;
gameInfo.currentPlayer = res.moveHistory[0][0].playerIdx;
gameInfo.publicCard = res.publicCard;
this.gameStateHistory[gameInfo.round].push(gameInfo);
this.setState({gameInfo: gameInfo}, ()=>{
if(this.gameStateTimeout){
window.clearTimeout(this.gameStateTimeout);
this.gameStateTimeout = null;
}
// loop to update game state
this.gameStateTimer();
});
});
};
pauseReplay(){
if(this.gameStateTimeout){
window.clearTimeout(this.gameStateTimeout);
this.gameStateTimeout = null;
}
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.gameStatus = "paused";
this.setState({ gameInfo: gameInfo });
}
resumeReplay(){
this.gameStateTimer();
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.gameStatus = "playing";
this.setState({ gameInfo: gameInfo });
}
changeGameSpeed(newVal){
this.setState({gameSpeed: newVal});
}
gameStatusButton(status){
switch (status) {
case "ready":
return <Button className={"status-button"} variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Start</Button>;
case "playing":
return <Button className={"status-button"} variant={"contained"} startIcon={<PauseCircleOutlineRoundedIcon />} color="secondary" onClick={()=>{this.pauseReplay()}}>Pause</Button>;
case "paused":
return <Button className={"status-button"} variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.resumeReplay()}}>Resume</Button>;
case "over":
return <Button className={"status-button"} variant={"contained"} startIcon={<ReplayRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Restart</Button>;
default:
alert(`undefined game status: ${status}`);
}
}
go2PrevGameState() {
let gameInfo = null;
if(this.state.gameInfo.turn === 0 && this.state.gameInfo.round !== 0){
let prevRound = this.gameStateHistory[this.state.gameInfo.round-1];
gameInfo = deepCopy(prevRound[prevRound.length-1]);
}else{
gameInfo = deepCopy(this.gameStateHistory[this.state.gameInfo.round][this.state.gameInfo.turn - 1]);
}
gameInfo.gameStatus = "paused";
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}
go2NextGameState() {
let gameInfo = this.generateNewState();
if(gameInfo.gameStatus === "over") {
this.setState({gameInfo: gameInfo});
}else{
gameInfo.gameStatus = "paused";
gameInfo.toggleFade = "";
this.setState({gameInfo: gameInfo});
}
}
render(){ render(){
let sliderValueText = (value) => {
return value;
};
const gameSpeedMarks = [
{
value: -3,
label: 'x0.125',
},
{
value: -2,
label: 'x0.25',
},
{
value: -1,
label: 'x0.5',
},
{
value: 0,
label: 'x1',
},
{
value: 1,
label: 'x2',
},
{
value: 2,
label: 'x4',
},
{
value: 3,
label: 'x8',
}
];
return ( return (
<div> <div className={"leduc-view-container"}>
<div style={{width: "960px", height: "540px"}}> <Layout.Row style={{"height": "540px"}}>
<Layout.Col style={{"height": "100%"}} span="17">
<div style={{"height": "100%"}}>
<Paper className={"leduc-gameboard-paper"} elevation={3}>
<LeducHoldemGameBoard <LeducHoldemGameBoard
playerInfo={this.state.gameInfo.playerInfo} playerInfo={this.state.gameInfo.playerInfo}
hands={this.state.gameInfo.hands} hands={this.state.gameInfo.hands}
@ -157,13 +304,91 @@ class LeducHoldemGameView extends React.Component {
pot={this.state.gameInfo.pot} pot={this.state.gameInfo.pot}
publicCard={this.state.gameInfo.publicCard} publicCard={this.state.gameInfo.publicCard}
/> />
</Paper>
</div> </div>
<div className="game-controller"> </Layout.Col>
<Layout.Row> <Layout.Col span="7" style={{"height": "100%"}}>
<Layout.Col span="24"> <Paper className={"leduc-probability-paper"} elevation={3}>
<Button type="primary" onClick={()=>{this.retrieveReplayData()}}>Start</Button> <div className={"probability-player"}>
{
this.state.gameInfo.playerInfo.length > 0 ?
<span>Current Player: {this.state.gameInfo.currentPlayer}</span>
:
<span>Waiting...</span>
}
</div>
<Divider />
<div className={"probability-table"}>
<div className={"probability-item"}>
{/* {this.computeProbabilityItem(0)} */}
</div>
<div className={"probability-item"}>
{/* {this.computeProbabilityItem(1)} */}
</div>
<div className={"probability-item"}>
{/* {this.computeProbabilityItem(2)} */}
</div>
</div>
</Paper>
</Layout.Col> </Layout.Col>
</Layout.Row> </Layout.Row>
<div className="progress-bar">
<LinearProgress variant="determinate" value={this.state.gameInfo.completedPercent} />
</div>
<div className="game-controller">
<Paper className={"game-controller-paper"} elevation={3}>
<Layout.Row style={{"height": "51px"}}>
<Layout.Col span="7" style={{"height": "51px", "lineHeight": "48px"}}>
<div>
<Button
variant="contained"
color="primary"
disabled={this.state.gameInfo.gameStatus !== "paused" || (this.state.gameInfo.round === 0 && 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>
</div>
</Layout.Col>
<Layout.Col span="1" style={{"height": "100%", "width": "1px"}}>
<Divider orientation="vertical" />
</Layout.Col>
<Layout.Col span="3" style={{"height": "51px", "lineHeight": "51px", "marginLeft": "-1px", "marginRight": "-1px"}}>
<div style={{"textAlign": "center"}}>{`Turn: ${this.state.gameInfo.turn}`}</div>
</Layout.Col>
<Layout.Col span="1" style={{"height": "100%", "width": "1px"}}>
<Divider orientation="vertical" />
</Layout.Col>
<Layout.Col span="14">
<div>
<label className={"form-label-left"}>Game Speed</label>
<div style={{"marginLeft": "100px", "marginRight": "10px"}}>
<Slider
value={this.state.gameSpeed}
getAriaValueText={sliderValueText}
onChange={(e, newVal)=>{this.changeGameSpeed(newVal)}}
aria-labelledby="discrete-slider-custom"
step={1}
min={-3}
max={3}
track={false}
valueLabelDisplay="off"
marks={gameSpeedMarks}
/>
</div>
</div>
</Layout.Col>
</Layout.Row>
</Paper>
</div> </div>
</div> </div>
); );