added pause game function in doudizhu, optimize changing game speed

This commit is contained in:
songyih 2020-02-05 23:12:27 -08:00
parent 29c709d749
commit a9f234fc43
5 changed files with 486 additions and 54 deletions

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/icons": "^4.9.1",
"@material-ui/core": "^4.9.0", "@material-ui/core": "^4.9.0",
"element-react": "^1.4.34", "element-react": "^1.4.34",
"element-theme-default": "^1.4.13", "element-theme-default": "^1.4.13",

View File

@ -11,6 +11,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@material-ui/icons": "^4.9.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.17.1", "express": "^4.17.1",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",

View File

@ -8,143 +8,469 @@
{ {
"id": 0, "id": 0,
"index": 0, "index": 0,
"role": "peasant" "role": "peasant",
"agentInfo": {
"name": "random",
"winRate": 0.333
}
}, },
{ {
"id": 1, "id": 1,
"index": 1, "index": 1,
"role": "peasant" "role": "peasant",
"agentInfo": {
"name": "DQN",
"winRate": 0.555
}
}, },
{ {
"id": 2, "id": 2,
"index": 2, "index": 2,
"role": "landlord" "role": "landlord",
"agentInfo": {
"name": "CFR",
"winRate": 0.666
}
} }
], ],
"moveHistory": [ "moveHistory": [
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "H3 S3 D3 D5" "move": "H3 S3 D3 D5",
"probabilities": [
{
"move": "H3 S3 D3 D5",
"probability": 0.9
},
{
"move": "H3",
"probability": 0.05
},
{
"move": "D5",
"probability": 0.02
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "S9 H9 D9 S3" "move": "S9 H9 D9 S3",
"probabilities": [
{
"move": "S9 H9 D9 S3",
"probability": 0.9
},
{
"move": "HQ CQ DQ S3",
"probability": 0.05
},
{
"move": "HQ CQ DQ C3",
"probability": 0.02
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "SJ HJ DJ D7" "move": "SJ HJ DJ D7",
"probabilities": [
{
"move": "SJ HJ DJ D7",
"probability": 0.9
},
{
"move": "SJ HJ DJ C9",
"probability": 0.05
},
{
"move": "BJ RJ",
"probability": 0.02
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "HQ CQ DQ C7" "move": "HQ CQ DQ C7",
"probabilities": [
{
"move": "HQ CQ DQ C7",
"probability": 0.9
},
{
"move": "HQ CQ DQ CJ",
"probability": 0.05
},
{
"move": "P",
"probability": 0.02
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "C4 D4" "move": "C4 D4",
"probabilities": [
{
"move": "HK DK",
"probability": 0.5
},
{
"move": "C4 D4",
"probability": 0.2
},
{
"move": "S2 H2",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "ST HT" "move": "ST HT",
"probabilities": [
{
"move": "S6 D6",
"probability": 0.5
},
{
"move": "ST HT",
"probability": 0.2
},
{
"move": "HA CA",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "SK CK" "move": "SK CK",
"probabilities": [
{
"move": "P",
"probability": 0.5
},
{
"move": "RJ BJ",
"probability": 0.3
},
{
"move": "SK CK",
"probability": 0.2
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "S2 H2" "move": "S2 H2",
"probabilities": [
{
"move": "P",
"probability": 0.5
},
{
"move": "HK DK",
"probability": 0.5
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "S6 H6" "move": "S6 H6",
"probabilities": [
{
"move": "S6 H6",
"probability": 0.6
},
{
"move": "HK DK",
"probability": 0.2
},
{
"move": "CJ",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 0.8
},
{
"move": "S7 H7",
"probability": 0.1
},
{
"move": "HA CA",
"probability": 0.05
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "CT DT" "move": "CT DT",
"probabilities": [
{
"move": "P",
"probability": 0.5
},
{
"move": "S7 H7",
"probability": 0.1
},
{
"move": "HA CA",
"probability": 0.05
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "HK DK" "move": "HK DK",
"probabilities": [
{
"move": "HK DK",
"probability": 0.9
},
{
"move": "P",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 0.9
},
{
"move": "HA CA",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "RJ BJ" "move": "RJ BJ",
"probabilities": [
{
"move": "RJ BJ",
"probability": 0.9
},
{
"move": "P",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "S8 H8 C8 C9" "move": "S8 H8 C8 C9",
"probabilities": [
{
"move": "S8 H8 C8 C9",
"probability": 0.7
},
{
"move": "SA",
"probability": 0.1
},
{
"move": "D2",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "HA CA DA H5" "move": "HA CA DA H5",
"probabilities": [
{
"move": "HA CA DA H5",
"probability": 0.5
},
{
"move": "P",
"probability": 0.1
},
{
"move": "HA CA DA D6",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "SQ" "move": "SQ",
"probabilities": [
{
"move": "SQ",
"probability": 0.5
},
{
"move": "S4 H4",
"probability": 0.2
},
{
"move": "D6",
"probability": 0.1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "D2" "move": "D2",
"probabilities": [
{
"move": "D2",
"probability": 0.5
},
{
"move": "SA",
"probability": 0.3
},
{
"move": "P",
"probability": 0.2
}
]
}, },
{ {
"playerIdx": 0, "playerIdx": 0,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 1, "playerIdx": 1,
"move": "P" "move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
}, },
{ {
"playerIdx": 2, "playerIdx": 2,
"move": "SA" "move": "SA",
"probabilities": [
{
"move": "SA",
"probability": 1
}
]
} }
] ]
} }

View File

@ -74,4 +74,17 @@ export function millisecond2Second(t){
return Math.ceil(t/1000); return Math.ceil(t/1000);
} }
export { suitMap, suitMapSymbol }; export function debounce(func, wait, immediate) {
let timeout;
return function() {
const context = this, args = arguments;
const later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}

View File

@ -2,21 +2,26 @@ import React from 'react';
import '../assets/gameview.scss'; import '../assets/gameview.scss';
import { DoudizhuGameBoard } from '../components/GameBoard'; import { DoudizhuGameBoard } from '../components/GameBoard';
import webSocket from "socket.io-client"; import webSocket from "socket.io-client";
import { removeCards, doubleRaf, deepCopy } from "../utils"; import { removeCards, doubleRaf, deepCopy, debounce } 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 PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded';
import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded';
class DoudizhuGameView extends React.Component { class DoudizhuGameView extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
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 = 1000; this.initConsiderationTime = 2000;
this.considerationTimeDeduction = 100; this.considerationTimeDeduction = 100;
this.gameStateTimeout = null; this.gameStateTimeout = null;
this.initGameState = { this.initGameState = {
gameStatus: "ready", // "ready", "playing", "paused", "over"
playerInfo: [], playerInfo: [],
hands: [], hands: [],
latestAction: [[], [], []], latestAction: [[], [], []],
@ -30,15 +35,21 @@ class DoudizhuGameView extends React.Component {
ws: null, ws: null,
gameInfo: this.initGameState, gameInfo: this.initGameState,
gameStateLoop: null, gameStateLoop: null,
considerationTimeSetting: this.initConsiderationTime gameSpeed: 0
}; };
} }
gameStateTimer() { gameStateTimer() {
this.gameStateTimeout = setTimeout(()=>{ this.gameStateTimeout = setTimeout(()=>{
let currentConsiderationTime = this.state.gameInfo.considerationTime; let currentConsiderationTime = this.state.gameInfo.considerationTime;
// for test use
// console.log(currentConsiderationTime);
// if(currentConsiderationTime === 1000){
// debugger;
// }
if(currentConsiderationTime > 0) { if(currentConsiderationTime > 0) {
currentConsiderationTime -= this.considerationTimeDeduction; currentConsiderationTime -= this.considerationTimeDeduction * Math.pow(2, this.state.gameSpeed);
currentConsiderationTime = currentConsiderationTime < 0 ? 0 : currentConsiderationTime;
let gameInfo = deepCopy(this.state.gameInfo); let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.considerationTime = currentConsiderationTime; gameInfo.considerationTime = currentConsiderationTime;
this.setState({gameInfo: gameInfo}); this.setState({gameInfo: gameInfo});
@ -53,7 +64,7 @@ class DoudizhuGameView extends React.Component {
this.setState({gameInfo: gameInfo}); this.setState({gameInfo: gameInfo});
this.state.ws.emit("getMessage", gameStateReq); this.state.ws.emit("getMessage", gameStateReq);
} }
}, this.considerationTimeDeduction); }, 100);
} }
startReplay() { startReplay() {
@ -61,7 +72,11 @@ class DoudizhuGameView extends React.Component {
const replayReq = {type: 0}; const replayReq = {type: 0};
this.state.ws.emit("getMessage", replayReq); this.state.ws.emit("getMessage", replayReq);
// init game state // init game state
this.setState({gameInfo: this.initGameState}); let initGameState = deepCopy(this.initGameState);
// set game status to playing
initGameState.gameStatus = "playing";
this.setState({gameInfo: initGameState});
if(this.gameStateTimeout){ if(this.gameStateTimeout){
window.clearTimeout(this.gameStateTimeout); window.clearTimeout(this.gameStateTimeout);
this.gameStateTimeout = null; this.gameStateTimeout = null;
@ -104,7 +119,7 @@ class DoudizhuGameView extends React.Component {
}else{ }else{
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.state.considerationTimeSetting; gameInfo.considerationTime = this.initConsiderationTime;
this.setState({gameInfo: gameInfo}); this.setState({gameInfo: gameInfo});
}else{ }else{
console.log("Mismatched game turn or current player index", message); console.log("Mismatched game turn or current player index", message);
@ -127,6 +142,9 @@ class DoudizhuGameView extends React.Component {
return element.index === prevTurn.currentPlayer; return element.index === prevTurn.currentPlayer;
}); });
if(winner){ if(winner){
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.gameStatus = "over";
this.setState({ gameInfo: gameInfo });
if(winner.role === "landlord") if(winner.role === "landlord")
alert("Landlord Wins"); alert("Landlord Wins");
else else
@ -139,8 +157,79 @@ class DoudizhuGameView extends React.Component {
this.gameStateTimer(); 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){
console.log('wdnmd');
this.setState({gameSpeed: newVal});
}
gameStatusButton(status){
switch (status) {
case "ready":
return <Button variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Start Replay</Button>;
case "playing":
return <Button variant={"contained"} startIcon={<PauseCircleOutlineRoundedIcon />} color="secondary" onClick={()=>{this.pauseReplay()}}>Pause</Button>;
case "paused":
return <Button variant={"contained"} startIcon={<PlayArrowRoundedIcon />} color="primary" onClick={()=>{this.resumeReplay()}}>Resume</Button>;
case "over":
return <Button variant={"contained"} startIcon={<ReplayRoundedIcon />} color="primary" onClick={()=>{this.startReplay()}}>Resume</Button>;
default:
alert(`undefined game status: ${status}`);
}
return ;
}
render(){ render(){
// todo: reset game state timer when considerationTimeSetting changes let sliderValueText = (value) => {
return `${value}°C`;
};
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>
<div style={{width: "960px", height: "540px"}}> <div style={{width: "960px", height: "540px"}}>
@ -158,8 +247,8 @@ class DoudizhuGameView extends React.Component {
<div className="game-controller"> <div className="game-controller">
<Layout.Row> <Layout.Row>
<Layout.Col span="24"> <Layout.Col span="24">
<Button type="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button> <Button variant={"contained"} color="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button>
<Button type="primary" onClick={()=>{this.startReplay()}}>Start Replay</Button> { this.gameStatusButton(this.state.gameInfo.gameStatus) }
</Layout.Col> </Layout.Col>
</Layout.Row> </Layout.Row>
<Layout.Row style={{height: "31px"}}> <Layout.Row style={{height: "31px"}}>
@ -170,14 +259,16 @@ class DoudizhuGameView extends React.Component {
</Layout.Col> </Layout.Col>
<Layout.Col span="16"> <Layout.Col span="16">
<Slider <Slider
value={this.state.considerationTimeSetting} value={this.state.gameSpeed}
onChange={(e, newVal)=>{console.log('slider val', newVal);this.setState({considerationTimeSetting: newVal})}} getAriaValueText={sliderValueText}
aria-labelledby="discrete-slider" onChange={(e, newVal)=>{this.changeGameSpeed(newVal)}}
valueLabelDisplay="auto" aria-labelledby="discrete-slider-custom"
step={1000} step={1}
marks min={-3}
min={0} max={3}
max={10000} track={false}
valueLabelDisplay="off"
marks={gameSpeedMarks}
/> />
</Layout.Col> </Layout.Col>
</Layout.Row> </Layout.Row>