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",
"private": true,
"dependencies": {
"@material-ui/icons": "^4.9.1",
"@material-ui/core": "^4.9.0",
"element-react": "^1.4.34",
"element-theme-default": "^1.4.13",

View File

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

View File

@ -8,143 +8,469 @@
{
"id": 0,
"index": 0,
"role": "peasant"
"role": "peasant",
"agentInfo": {
"name": "random",
"winRate": 0.333
}
},
{
"id": 1,
"index": 1,
"role": "peasant"
"role": "peasant",
"agentInfo": {
"name": "DQN",
"winRate": 0.555
}
},
{
"id": 2,
"index": 2,
"role": "landlord"
"role": "landlord",
"agentInfo": {
"name": "CFR",
"winRate": 0.666
}
}
],
"moveHistory": [
{
"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,
"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,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"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,
"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,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"playerIdx": 2,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"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,
"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,
"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,
"move": "S2 H2"
"move": "S2 H2",
"probabilities": [
{
"move": "P",
"probability": 0.5
},
{
"move": "HK DK",
"probability": 0.5
}
]
},
{
"playerIdx": 1,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"playerIdx": 2,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"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,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 0.8
},
{
"move": "S7 H7",
"probability": 0.1
},
{
"move": "HA CA",
"probability": 0.05
}
]
},
{
"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,
"move": "HK DK"
"move": "HK DK",
"probabilities": [
{
"move": "HK DK",
"probability": 0.9
},
{
"move": "P",
"probability": 0.1
}
]
},
{
"playerIdx": 1,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 0.9
},
{
"move": "HA CA",
"probability": 0.1
}
]
},
{
"playerIdx": 2,
"move": "RJ BJ"
"move": "RJ BJ",
"probabilities": [
{
"move": "RJ BJ",
"probability": 0.9
},
{
"move": "P",
"probability": 0.1
}
]
},
{
"playerIdx": 0,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"playerIdx": 1,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"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,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 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,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"playerIdx": 0,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 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,
"move": "D2"
"move": "D2",
"probabilities": [
{
"move": "D2",
"probability": 0.5
},
{
"move": "SA",
"probability": 0.3
},
{
"move": "P",
"probability": 0.2
}
]
},
{
"playerIdx": 0,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"playerIdx": 1,
"move": "P"
"move": "P",
"probabilities": [
{
"move": "P",
"probability": 1
}
]
},
{
"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);
}
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 { DoudizhuGameBoard } from '../components/GameBoard';
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 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 {
constructor(props) {
super(props);
const mainViewerId = 0; // Id of the player at the bottom of screen
this.initConsiderationTime = 1000;
this.initConsiderationTime = 2000;
this.considerationTimeDeduction = 100;
this.gameStateTimeout = null;
this.initGameState = {
gameStatus: "ready", // "ready", "playing", "paused", "over"
playerInfo: [],
hands: [],
latestAction: [[], [], []],
@ -30,15 +35,21 @@ class DoudizhuGameView extends React.Component {
ws: null,
gameInfo: this.initGameState,
gameStateLoop: null,
considerationTimeSetting: this.initConsiderationTime
gameSpeed: 0
};
}
gameStateTimer() {
this.gameStateTimeout = setTimeout(()=>{
let currentConsiderationTime = this.state.gameInfo.considerationTime;
// for test use
// console.log(currentConsiderationTime);
// if(currentConsiderationTime === 1000){
// debugger;
// }
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);
gameInfo.considerationTime = currentConsiderationTime;
this.setState({gameInfo: gameInfo});
@ -53,7 +64,7 @@ class DoudizhuGameView extends React.Component {
this.setState({gameInfo: gameInfo});
this.state.ws.emit("getMessage", gameStateReq);
}
}, this.considerationTimeDeduction);
}, 100);
}
startReplay() {
@ -61,7 +72,11 @@ class DoudizhuGameView extends React.Component {
const replayReq = {type: 0};
this.state.ws.emit("getMessage", replayReq);
// 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){
window.clearTimeout(this.gameStateTimeout);
this.gameStateTimeout = null;
@ -104,7 +119,7 @@ class DoudizhuGameView extends React.Component {
}else{
console.log("Cannot find cards in move from player's hand");
}
gameInfo.considerationTime = this.state.considerationTimeSetting;
gameInfo.considerationTime = this.initConsiderationTime;
this.setState({gameInfo: gameInfo});
}else{
console.log("Mismatched game turn or current player index", message);
@ -127,6 +142,9 @@ class DoudizhuGameView extends React.Component {
return element.index === prevTurn.currentPlayer;
});
if(winner){
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.gameStatus = "over";
this.setState({ gameInfo: gameInfo });
if(winner.role === "landlord")
alert("Landlord Wins");
else
@ -139,8 +157,79 @@ class DoudizhuGameView extends React.Component {
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(){
// 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 (
<div>
<div style={{width: "960px", height: "540px"}}>
@ -158,8 +247,8 @@ class DoudizhuGameView extends React.Component {
<div className="game-controller">
<Layout.Row>
<Layout.Col span="24">
<Button type="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button>
<Button type="primary" onClick={()=>{this.startReplay()}}>Start Replay</Button>
<Button variant={"contained"} color="primary" onClick={()=>{this.connectWebSocket()}}>Connect</Button>
{ this.gameStatusButton(this.state.gameInfo.gameStatus) }
</Layout.Col>
</Layout.Row>
<Layout.Row style={{height: "31px"}}>
@ -170,14 +259,16 @@ class DoudizhuGameView extends React.Component {
</Layout.Col>
<Layout.Col span="16">
<Slider
value={this.state.considerationTimeSetting}
onChange={(e, newVal)=>{console.log('slider val', newVal);this.setState({considerationTimeSetting: newVal})}}
aria-labelledby="discrete-slider"
valueLabelDisplay="auto"
step={1000}
marks
min={0}
max={10000}
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}
/>
</Layout.Col>
</Layout.Row>