Added Probability for doudizhu, added transition animation for doudizhu probability

This commit is contained in:
songyih 2020-02-09 22:22:57 -08:00
parent 19fafcb832
commit c47eac7e5a
7 changed files with 254 additions and 39 deletions

View File

@ -3,12 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/icons": "^4.9.1",
"@material-ui/core": "^4.9.0",
"@material-ui/icons": "^4.9.1",
"element-react": "^1.4.34",
"element-theme-default": "^1.4.13",
"node-sass": "^4.13.0",
"react": "^16.12.0",
"react-addons-css-transition-group": "^15.6.2",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.12.19",
"react-router-dom": "^5.1.2",

View File

@ -24,23 +24,49 @@
"moveHistory": [
{
"playerIdx": 2,
"move": "H3 S3 D3 D5"
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probabilities": [
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
}
]
},
{
"playerIdx": 0,
"move": "S9 H9 D9 S3"
"move": "S2 H2 HK DK HQ CQ DQ CJ S9 H9 D9 C7 S6 H6 C4 D4",
"probabilities": [
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
},
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
},
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
}
]
},
{
"playerIdx": 1,
"move": "P"
},
{
"playerIdx": 2,
"move": "SJ HJ DJ D7"
},
{
"playerIdx": 0,
"move": "P"
"move": "C2 HA CA DA SQ ST HT D8 S7 H7 C6 D6 S5 H5 C5 S4",
"probabilities": [
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
},
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
},
{
"move": "RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3",
"probability": 1
}
]
}
]
}

View File

@ -87,7 +87,7 @@
.played-card-area {
position: relative;
left: 100px;
left: 20px;
top: 20px;
}
}
@ -131,7 +131,7 @@
.played-card-area {
position: relative;
right: 100px;
right: 20px;
top: 20px;
}
}

View File

@ -4,4 +4,100 @@
.el-row {
margin-bottom: 10px;
}
}
.doudizhu-view-container {
width: 1000px;
margin-left: auto;
margin-right: auto;
.doudizhu-gameboard-paper {
height: calc(100% - 7px*2);
margin: 5px;
padding: 2px;
}
.doudizhu-probability-paper {
height: calc(100% - 5px*2);
margin: 5px;
.probability-player {
height: calc(72px - 16px*2);
padding: 16px;
}
.probability-table {
display: table;
border-collapse: collapse;
width: 100%;
height: calc(100% - 72px);
.probability-item {
display: table-row;
&:not(:first-child) {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.waiting {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.playing {
-webkit-transition: background-color 250ms ease;
-ms-transition: background-color 250ms ease;
transition: background-color 250ms ease;
display: table-cell;
height: 152px;
vertical-align: middle;
.playingCards {
visibility: visible;
transition: visibility 0s, opacity 0.5s;
opacity: 1;
}
.playingCards.hide {
visibility: hidden;
transition: visibility 0.1s, opacity 0.05s;
opacity: 0;
pointer-events:none;
}
.playingCards > ul.hand {
margin-bottom: 0;
}
.probability-move {
font-size: 10px;
width: 100%;
display: flex;
justify-content: center;
}
.non-card {
visibility: visible;
transition: visibility 0s, opacity 0.5s;
opacity: 1;
display: table;
width: 100%;
margin-top: 5%;
height: 25px;
span {
display: table-cell;
vertical-align: middle;
text-align: center;
font-size: 16px;
}
}
.non-card.hide {
visibility: hidden;
transition: visibility 0.1s, opacity 0.05s;
opacity: 0;
pointer-events:none;
}
}
}
}
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { translateCardData, millisecond2Second } from '../../utils'
import { translateCardData, millisecond2Second, computeHandCardsWidth } from '../../utils'
import '../../assets/doudizhu.scss';
@ -15,7 +15,7 @@ class DoudizhuGameBoard extends React.Component {
}else{
return (
<div className="playingCards">
<ul className="hand" style={{width: this.computeHandCardsWidth(cards.length, 12)}}>
<ul className="hand" style={{width: computeHandCardsWidth(cards.length, 12)}}>
{cards.map(card=>{
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return (
@ -82,12 +82,6 @@ class DoudizhuGameBoard extends React.Component {
)
}
computeHandCardsWidth(num, emWidth) {
if(num === 0)
return 0;
return (num-1)*1.1*emWidth + 4.3*emWidth*1.2 + 2;
}
playerDecisionArea(playerIdx){
if(this.props.currentPlayer === playerIdx){
return <div className="non-card"><span>{`Consideration Time: ${millisecond2Second(this.props.considerationTime)}s`}</span></div>

View File

@ -87,4 +87,10 @@ export function debounce(func, wait, immediate) {
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
export function computeHandCardsWidth(num, emWidth) {
if(num === 0)
return 0;
return (num-1)*1.1*emWidth + 4.3*emWidth*1.2 + 2;
}

View File

@ -2,11 +2,13 @@ import React from 'react';
import axios from 'axios';
import '../assets/gameview.scss';
import { DoudizhuGameBoard } from '../components/GameBoard';
import { removeCards, doubleRaf, deepCopy } from "../utils";
import {removeCards, doubleRaf, deepCopy, computeHandCardsWidth, translateCardData} from "../utils";
import { Layout } from 'element-react';
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 PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import PauseCircleOutlineRoundedIcon from '@material-ui/icons/PauseCircleOutlineRounded';
import ReplayRoundedIcon from '@material-ui/icons/ReplayRounded';
@ -29,6 +31,7 @@ class DoudizhuGameView extends React.Component {
latestAction: [[], [], []],
mainViewerId: mainViewerId,
turn: 0,
toggleFadeIn: "",
currentPlayer: null,
considerationTime: this.initConsiderationTime,
};
@ -40,12 +43,21 @@ class DoudizhuGameView extends React.Component {
};
}
cardStr2Arr(cardStr){
return cardStr === "P" ? cardStr : cardStr.split(" ");
}
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.toggleFadeIn = "hide";
this.setState({gameInfo: gameInfo});
}
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.considerationTime = currentConsiderationTime;
this.setState({gameInfo: gameInfo});
@ -54,8 +66,9 @@ class DoudizhuGameView extends React.Component {
let res = this.moveHistory[this.state.gameInfo.turn];
if(res.playerIdx === this.state.gameInfo.currentPlayer){
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.latestAction[res.playerIdx] = res.move === "P" ? "P" : res.move.split(" ");
gameInfo.latestAction[res.playerIdx] = this.cardStr2Arr(res.move);
gameInfo.turn++;
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]);
@ -65,7 +78,16 @@ class DoudizhuGameView extends React.Component {
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
if(this.state.gameInfo.toggleFadeIn !== ""){
setTimeout(()=>{
let gameInfo = deepCopy(this.state.gameInfo);
gameInfo.toggleFadeIn = "";
this.setState({gameInfo: gameInfo});
}, 50);
}
});
}else{
console.log("Mismatched current player index");
}
@ -86,7 +108,7 @@ class DoudizhuGameView extends React.Component {
gameInfo.gameStatus = "playing";
gameInfo.playerInfo = res.playerInfo;
gameInfo.hands = res.initHands.map(element => {
return element.split(" ");
return this.cardStr2Arr(element);
});
// the first player should be landlord
gameInfo.currentPlayer = res.playerInfo.find(element=>{return element.role === "landlord"}).index;
@ -161,6 +183,53 @@ class DoudizhuGameView extends React.Component {
}
}
computeSingleLineHand(cards) {
if(cards === "P"){
return <div className={"non-card "+this.state.gameInfo.toggleFadeIn}><span>Pass</span></div>
}else{
return (
<div className={"playingCards "+this.state.gameInfo.toggleFadeIn}>
<ul className="hand" style={{width: computeHandCardsWidth(cards.length, 10)}}>
{cards.map(card=>{
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return (
<li key={`handCard-${card}`}>
<a className={`card ${rankClass} ${suitClass}`} href="/#">
<span className="rank">{rankText}</span>
<span className="suit">{suitText}</span>
</a>
</li>
);
})}
</ul>
</div>
)
}
}
computeProbabilityItem(idx){
if(this.state.gameInfo.gameStatus !== "ready" && this.state.gameInfo.turn < this.moveHistory.length){
let style = {};
style["backgroundColor"] = this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `rgba(255, 193, 7,${this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability})` : "#bdbdbd";
return (
<div className={"playing"} style={style}>
<div className="probability-move">
{this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ?
this.computeSingleLineHand(this.cardStr2Arr(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].move))
:
'\u00A0'
}
</div>
<div className={"non-card"}>
<span>{ this.moveHistory[this.state.gameInfo.turn].probabilities.length > idx ? `Probability: ${(this.moveHistory[this.state.gameInfo.turn].probabilities[idx].probability*100).toFixed(2)}%` : ""}</span>
</div>
</div>
)
}else {
return <span className={"waiting"}>Waiting...</span>
}
}
render(){
let sliderValueText = (value) => {
return `${value}°C`;
@ -197,19 +266,42 @@ class DoudizhuGameView extends React.Component {
];
return (
<div>
<div style={{width: "960px", height: "540px"}}>
<DoudizhuGameBoard
playerInfo={this.state.gameInfo.playerInfo}
hands={this.state.gameInfo.hands}
latestAction={this.state.gameInfo.latestAction}
mainPlayerId={this.state.gameInfo.mainViewerId}
currentPlayer={this.state.gameInfo.currentPlayer}
considerationTime={this.state.gameInfo.considerationTime}
turn={this.state.gameInfo.turn}
runNewTurn={(prevTurn)=>this.runNewTurn(prevTurn)}
/>
</div>
<div className={"doudizhu-view-container"}>
<Layout.Row style={{"height": "540px"}}>
<Layout.Col style={{"height": "100%"}} span="17">
<div style={{"height": "100%"}}>
<Paper className={"doudizhu-gameboard-paper"} elevation={3}>
<DoudizhuGameBoard
playerInfo={this.state.gameInfo.playerInfo}
hands={this.state.gameInfo.hands}
latestAction={this.state.gameInfo.latestAction}
mainPlayerId={this.state.gameInfo.mainViewerId}
currentPlayer={this.state.gameInfo.currentPlayer}
considerationTime={this.state.gameInfo.considerationTime}
turn={this.state.gameInfo.turn}
runNewTurn={(prevTurn)=>this.runNewTurn(prevTurn)}
/>
</Paper>
</div>
</Layout.Col>
<Layout.Col span="7" style={{"height": "100%"}}>
<Paper className={"doudizhu-probability-paper"} elevation={3}>
<div className={"probability-player"}>Current: 0</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.Row>
<div className="game-controller">
<Layout.Row>
<Layout.Col span="24">