commit
8be57eef48
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"prettier",
|
||||
"prettier/react"
|
||||
],
|
||||
"rules": {
|
||||
"no-redeclare": "off",
|
||||
"react/display-name": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# Folders
|
||||
dist/
|
||||
node_modules/
|
||||
src/public/lib/
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4
|
||||
}
|
29
src/App.js
29
src/App.js
|
@ -1,28 +1,33 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom";
|
||||
import LeaderBoard from './view/LeaderBoard';
|
||||
import { DoudizhuGameView, LeducHoldemGameView } from './view/GameView';
|
||||
import { DoudizhuReplayView, LeducHoldemReplayView } from './view/ReplayView';
|
||||
import { PvEDoudizhuDemoView } from './view/PvEView';
|
||||
import Navbar from "./components/Navbar";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
navBar: {
|
||||
zIndex: 1002
|
||||
}
|
||||
}));
|
||||
const navbarSubtitleMap = {
|
||||
"/leaderboard": "",
|
||||
"/replay/doudizhu": "Doudizhu",
|
||||
"/replay/leduc-holdem": "Leduc Hold'em",
|
||||
"/pve/doudizhu-demo": "Doudizhu PvE Demo"
|
||||
};
|
||||
|
||||
function App() {
|
||||
const classes = useStyles();
|
||||
// todo: add 404 page
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Navbar className={classes.navBar} gameName={""}/>
|
||||
<Navbar subtitleMap={navbarSubtitleMap}/>
|
||||
<div style={{marginTop: '75px'}}>
|
||||
<Route exact path="/">
|
||||
<Redirect to="/leaderboard?type=game&name=leduc-holdem" />
|
||||
{/* for test use */}
|
||||
{/* <Redirect to="/leaderboard?type=game&name=leduc-holdem" /> */}
|
||||
<Redirect to="/pve/doudizhu-demo" />
|
||||
</Route>
|
||||
<Route path="/leaderboard" component={LeaderBoard} />
|
||||
<Route path="/replay/doudizhu" component={DoudizhuGameView} />
|
||||
<Route path="/replay/leduc-holdem" component={LeducHoldemGameView} />
|
||||
<Route path="/replay/doudizhu" component={DoudizhuReplayView} />
|
||||
<Route path="/replay/leduc-holdem" component={LeducHoldemReplayView} />
|
||||
<Route path="/pve/doudizhu-demo" component={PvEDoudizhuDemoView} />
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
|
||||
.playingCards .card {
|
||||
display: inline-block;
|
||||
width: 3.3em;
|
||||
height: 4.6em;
|
||||
width: 2.5em;
|
||||
height: 3.5em;
|
||||
border: 1px solid #666;
|
||||
border-radius: .3em;
|
||||
-moz-border-radius: .3em;
|
||||
|
@ -47,7 +47,7 @@
|
|||
padding: .25em;
|
||||
margin: 0 .5em .5em 0;
|
||||
text-align: center;
|
||||
font-size: 1.2em; /* @change: adjust this value to make bigger or smaller cards */
|
||||
font-size: 1.5em; /* @change: adjust this value to make bigger or smaller cards */
|
||||
font-weight: normal;
|
||||
font-family: Arial, sans-serif;
|
||||
position: relative;
|
||||
|
@ -60,8 +60,28 @@
|
|||
.playingCards a.card {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.playingCards.selectable label.card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* override orignal hover style */
|
||||
.playingCards.selectable label.card:hover {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.playingCards.selectable label.card:active::after {
|
||||
background-color: rgba(23, 146, 210, 0.5);
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 3em;
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
/* selected and hover state */
|
||||
.playingCards a.card:hover, .playingCards a.card:active,
|
||||
.playingCards a.card:hover, .playingCards a.card:active, .playingCards.selectable label.card.selected,
|
||||
.playingCards label.card:hover,
|
||||
.playingCards strong .card {
|
||||
bottom: 1em;
|
||||
|
|
|
@ -26,6 +26,16 @@
|
|||
transition: visibility 0.1s, opacity 0.05s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.main-player-action-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
font-weight: bolder;
|
||||
border-radius: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.timer {
|
||||
visibility: visible;
|
||||
|
@ -35,7 +45,7 @@
|
|||
height: 60px;
|
||||
.timer-text {
|
||||
color: #303133;
|
||||
margin-top: 5px;
|
||||
transform: translateY(5px);
|
||||
font-size: 23px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 2px 2px #909399;
|
||||
|
|
|
@ -1,82 +1,84 @@
|
|||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import React from 'react';
|
||||
import { translateCardData, millisecond2Second, computeHandCardsWidth } from '../../utils'
|
||||
|
||||
import '../../assets/doudizhu.scss';
|
||||
import Landlord_wName from '../../assets/images/Portrait/Landlord_wName.png';
|
||||
import Peasant_wName from '../../assets/images/Portrait/Peasant_wName.png';
|
||||
import PlaceHolderPlayer from '../../assets/images/Portrait/Player.png';
|
||||
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import { computeHandCardsWidth, millisecond2Second, translateCardData } from '../../utils';
|
||||
|
||||
class DoudizhuGameBoard extends React.Component {
|
||||
computePlayerPortrait(playerId, playerIdx){
|
||||
if(this.props.playerInfo.length > 0){
|
||||
return this.props.playerInfo[playerIdx].role === "landlord" ?
|
||||
computePlayerPortrait(playerId, playerIdx) {
|
||||
if (this.props.playerInfo.length > 0) {
|
||||
return this.props.playerInfo[playerIdx].role === 'landlord' ? (
|
||||
<div>
|
||||
<img src={Landlord_wName} alt={"Landlord"} height="70%" width="70%" />
|
||||
<Chip
|
||||
avatar={<Avatar>ID</Avatar>}
|
||||
label={playerId}
|
||||
clickable
|
||||
color="primary"
|
||||
/>
|
||||
<img src={Landlord_wName} alt={'Landlord'} height="70%" width="70%" />
|
||||
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" />
|
||||
</div>
|
||||
:
|
||||
) : (
|
||||
<div>
|
||||
<img src={Peasant_wName} alt={"Peasant"} height="70%" width="70%" />
|
||||
<Chip
|
||||
avatar={<Avatar>ID</Avatar>}
|
||||
label={playerId}
|
||||
clickable
|
||||
color="primary"
|
||||
/>
|
||||
<img src={Peasant_wName} alt={'Peasant'} height="70%" width="70%" />
|
||||
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" />
|
||||
</div>
|
||||
}else
|
||||
);
|
||||
} else
|
||||
return (
|
||||
<div>
|
||||
<img src={PlaceHolderPlayer} alt={"Player"} height="70%" width="70%" />
|
||||
<Chip
|
||||
avatar={<Avatar>ID</Avatar>}
|
||||
label={playerId}
|
||||
clickable
|
||||
color="primary"
|
||||
/>
|
||||
<img src={PlaceHolderPlayer} alt={'Player'} height="70%" width="70%" />
|
||||
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
computeSingleLineHand(cards, fadeClassName="") {
|
||||
if(cards === "pass"){
|
||||
return <div className="non-card"><span>PASS</span></div>
|
||||
}else{
|
||||
computeSingleLineHand(cards, fadeClassName = '', cardSelectable = false) {
|
||||
if (cards === 'pass') {
|
||||
return (
|
||||
<div className={"playingCards unselectable loose "+fadeClassName}>
|
||||
<ul className="hand" style={{width: computeHandCardsWidth(cards.length, 12)}}>
|
||||
{cards.map(card=>{
|
||||
<div className="non-card">
|
||||
<span>PASS</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={`playingCards loose ${fadeClassName} ${
|
||||
this.props.gamePlayable && cardSelectable ? 'selectable' : 'unselectable'
|
||||
}`}
|
||||
>
|
||||
<ul className="hand" style={{ width: computeHandCardsWidth(cards.length, 12) }}>
|
||||
{cards.map((card) => {
|
||||
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
|
||||
let selected = false;
|
||||
if (this.props.gamePlayable && cardSelectable) {
|
||||
selected = this.props.selectedCards.indexOf(card) >= 0;
|
||||
}
|
||||
|
||||
// todo: right click and move to select multiple cards
|
||||
return (
|
||||
<li key={`handCard-${card}`}>
|
||||
<a className={`card ${rankClass} ${suitClass}`} href="/#">
|
||||
<label
|
||||
onClick={() => this.props.handleSelectedCards([card])}
|
||||
className={`card ${rankClass} ${suitClass} ${selected ? 'selected' : ''}`}
|
||||
>
|
||||
<span className="rank">{rankText}</span>
|
||||
<span className="suit">{suitText}</span>
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
computeSideHand(cards) {
|
||||
let upCards;
|
||||
let downCards = [];
|
||||
if(cards.length > 10){
|
||||
if (cards.length > 10) {
|
||||
upCards = cards.slice(0, 10);
|
||||
downCards = cards.slice(10, );
|
||||
}else{
|
||||
downCards = cards.slice(10);
|
||||
} else {
|
||||
upCards = cards;
|
||||
}
|
||||
return (
|
||||
|
@ -84,11 +86,11 @@ class DoudizhuGameBoard extends React.Component {
|
|||
<div className="player-hand-up">
|
||||
<div className="playingCards unselectable loose">
|
||||
<ul className="hand">
|
||||
{upCards.map(card => {
|
||||
{upCards.map((card) => {
|
||||
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
|
||||
return (
|
||||
<li key={`handCard-${card}`}>
|
||||
<a className={`card ${rankClass} ${suitClass}`} href="/#">
|
||||
<a className={`card ${rankClass} ${suitClass}`} href="javascript:void(0);">
|
||||
<span className="rank">{rankText}</span>
|
||||
<span className="suit">{suitText}</span>
|
||||
</a>
|
||||
|
@ -101,11 +103,11 @@ class DoudizhuGameBoard extends React.Component {
|
|||
<div className="player-hand-down">
|
||||
<div className="playingCards unselectable loose">
|
||||
<ul className="hand">
|
||||
{downCards.map(card => {
|
||||
{downCards.map((card) => {
|
||||
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
|
||||
return (
|
||||
<li key={`handCard-${card}`}>
|
||||
<a className={`card ${rankClass} ${suitClass}`} href="/#">
|
||||
<a className={`card ${rankClass} ${suitClass}`} href="javascript:void(0);">
|
||||
<span className="rank">{rankText}</span>
|
||||
<span className="suit">{suitText}</span>
|
||||
</a>
|
||||
|
@ -116,28 +118,69 @@ class DoudizhuGameBoard extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
playerDecisionArea(playerIdx){
|
||||
let fadeClassName = "";
|
||||
if(this.props.toggleFade === "fade-out" && (playerIdx+2)%3 === this.props.currentPlayer)
|
||||
fadeClassName = "fade-out";
|
||||
else if(this.props.toggleFade === "fade-in" && (playerIdx+1)%3 === this.props.currentPlayer)
|
||||
fadeClassName = "scale-fade-in";
|
||||
if(this.props.currentPlayer === playerIdx){
|
||||
return (
|
||||
<div className={"timer "+fadeClassName}>
|
||||
<div className="timer-text">{millisecond2Second(this.props.considerationTime)}</div>
|
||||
</div>
|
||||
)
|
||||
}else{
|
||||
return this.computeSingleLineHand(this.props.latestAction[playerIdx], fadeClassName)
|
||||
playerDecisionArea(playerIdx) {
|
||||
let fadeClassName = '';
|
||||
if (this.props.toggleFade === 'fade-out' && (playerIdx + 2) % 3 === this.props.currentPlayer)
|
||||
fadeClassName = 'fade-out';
|
||||
else if (this.props.toggleFade === 'fade-in' && (playerIdx + 1) % 3 === this.props.currentPlayer)
|
||||
fadeClassName = 'scale-fade-in';
|
||||
if (this.props.currentPlayer === playerIdx) {
|
||||
if (this.props.mainPlayerId === this.props.playerInfo[this.props.currentPlayer].id) {
|
||||
return (
|
||||
<div className={'main-player-action-wrapper'}>
|
||||
<div style={{ marginRight: '2em' }} className={'timer ' + fadeClassName}>
|
||||
<div className="timer-text">{millisecond2Second(this.props.considerationTime)}</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.handleMainPlayerAct('deselect');
|
||||
}}
|
||||
style={{ marginRight: '2em' }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Deselect
|
||||
</Button>
|
||||
<Button
|
||||
disabled={this.props.isPassDisabled}
|
||||
onClick={() => {
|
||||
this.props.handleMainPlayerAct('pass');
|
||||
}}
|
||||
style={{ marginRight: '2em' }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Pass
|
||||
</Button>
|
||||
<Button
|
||||
disabled={this.props.selectedCards.length === 0}
|
||||
onClick={() => {
|
||||
this.props.handleMainPlayerAct('play');
|
||||
}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Play
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={'timer ' + fadeClassName}>
|
||||
<div className="timer-text">{millisecond2Second(this.props.considerationTime)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return this.computeSingleLineHand(this.props.latestAction[playerIdx], fadeClassName);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if(prevProps.turn !== this.props.turn && this.props.turn !== 0 && this.props.gameStatus === "playing"){
|
||||
if (prevProps.turn !== this.props.turn && this.props.turn !== 0 && this.props.gameStatus === 'playing') {
|
||||
// new turn starts
|
||||
this.props.runNewTurn(prevProps);
|
||||
}
|
||||
|
@ -146,59 +189,65 @@ class DoudizhuGameBoard extends React.Component {
|
|||
render() {
|
||||
// compute the id as well as index in list for every player
|
||||
const bottomId = this.props.mainPlayerId;
|
||||
let found = this.props.playerInfo.find(element=>{
|
||||
let found = this.props.playerInfo.find((element) => {
|
||||
return element.id === bottomId;
|
||||
});
|
||||
const bottomIdx = found ? found.index : -1;
|
||||
const rightIdx = bottomIdx >= 0 ? (bottomIdx+1)%3 : -1;
|
||||
const leftIdx = rightIdx >= 0 ? (rightIdx+1)%3 : -1;
|
||||
const rightIdx = bottomIdx >= 0 ? (bottomIdx + 1) % 3 : -1;
|
||||
const leftIdx = rightIdx >= 0 ? (rightIdx + 1) % 3 : -1;
|
||||
let rightId = -1;
|
||||
let leftId = -1;
|
||||
if(rightIdx >= 0 && leftIdx >= 0){
|
||||
found = this.props.playerInfo.find(element=>{
|
||||
if (rightIdx >= 0 && leftIdx >= 0) {
|
||||
found = this.props.playerInfo.find((element) => {
|
||||
return element.index === rightIdx;
|
||||
});
|
||||
if(found)
|
||||
rightId = found.id;
|
||||
found = this.props.playerInfo.find(element=>{
|
||||
if (found) rightId = found.id;
|
||||
found = this.props.playerInfo.find((element) => {
|
||||
return element.index === leftIdx;
|
||||
});
|
||||
if(found)
|
||||
leftId = found.id;
|
||||
if (found) leftId = found.id;
|
||||
}
|
||||
return (
|
||||
<div className="doudizhu-wrapper" style={{}}>
|
||||
<div id={"left-player"}>
|
||||
<div id={'left-player'}>
|
||||
<div className="player-main-area">
|
||||
<div className="player-info">
|
||||
{this.computePlayerPortrait(leftId, leftIdx)}
|
||||
</div>
|
||||
{leftIdx >= 0 ? this.computeSideHand(this.props.hands[leftIdx]) : <div className="player-hand-placeholder"><span>Waiting...</span></div>}
|
||||
</div>
|
||||
<div className="played-card-area">
|
||||
{leftIdx >= 0 ? this.playerDecisionArea(leftIdx) : ""}
|
||||
<div className="player-info">{this.computePlayerPortrait(leftId, leftIdx)}</div>
|
||||
{leftIdx >= 0 ? (
|
||||
this.computeSideHand(this.props.hands[leftIdx])
|
||||
) : (
|
||||
<div className="player-hand-placeholder">
|
||||
<span>Waiting...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="played-card-area">{leftIdx >= 0 ? this.playerDecisionArea(leftIdx) : ''}</div>
|
||||
</div>
|
||||
<div id={"right-player"}>
|
||||
<div id={'right-player'}>
|
||||
<div className="player-main-area">
|
||||
<div className="player-info">
|
||||
{this.computePlayerPortrait(rightId, rightIdx)}
|
||||
</div>
|
||||
{rightIdx >= 0 ? this.computeSideHand(this.props.hands[rightIdx]) : <div className="player-hand-placeholder"><span>Waiting...</span></div>}
|
||||
</div>
|
||||
<div className="played-card-area">
|
||||
{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ""}
|
||||
<div className="player-info">{this.computePlayerPortrait(rightId, rightIdx)}</div>
|
||||
{rightIdx >= 0 ? (
|
||||
this.computeSideHand(this.props.hands[rightIdx])
|
||||
) : (
|
||||
<div className="player-hand-placeholder">
|
||||
<span>Waiting...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="played-card-area">{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}</div>
|
||||
</div>
|
||||
<div id={"bottom-player"}>
|
||||
<div className="played-card-area">
|
||||
{bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ""}
|
||||
</div>
|
||||
<div id={'bottom-player'}>
|
||||
<div className="played-card-area">{bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''}</div>
|
||||
<div className="player-main-area">
|
||||
<div className="player-info">
|
||||
{this.computePlayerPortrait(bottomId, bottomIdx)}
|
||||
</div>
|
||||
{bottomIdx >= 0 ? <div className="player-hand">{this.computeSingleLineHand(this.props.hands[bottomIdx])}</div> : <div className="player-hand-placeholder"><span>Waiting...</span></div>}
|
||||
<div className="player-info">{this.computePlayerPortrait(bottomId, bottomIdx)}</div>
|
||||
{bottomIdx >= 0 ? (
|
||||
<div className="player-hand">
|
||||
{this.computeSingleLineHand(this.props.hands[bottomIdx], '', true)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="player-hand-placeholder">
|
||||
<span>Waiting...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,39 +1,37 @@
|
|||
import React from "react";
|
||||
import { Link } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import logo_white from "../assets/images/logo_white.png";
|
||||
import GitHubIcon from "@material-ui/icons/GitHub";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import axios from 'axios';
|
||||
|
||||
class Navbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
function Navbar({subtitleMap}) {
|
||||
const [stars, setStars] = useState('...');
|
||||
let location = useLocation();
|
||||
console.log(location.pathname, subtitleMap);
|
||||
const subtitle = subtitleMap[location.pathname];
|
||||
|
||||
this.state = {stars: '...'};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
useEffect(() => {
|
||||
axios.get("https://api.github.com/repos/datamllab/rlcard")
|
||||
.then(res=>{
|
||||
this.setState({stars: res.data.stargazers_count});
|
||||
setStars(res.data.stargazers_count);
|
||||
});
|
||||
}
|
||||
}, [])
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AppBar position="fixed" className={"header-bar-wrapper"}>
|
||||
<div className={"header-bar"}>
|
||||
<Link to="/leaderboard"><img src={logo_white} alt={"Logo"} height="65px" /></Link>
|
||||
<div className={"title unselectable"}><div className={"title-text"}>Showdown<span className={"subtitle"}>{this.props.gameName === '' ? '' : '/ ' + this.props.gameName}</span></div></div>
|
||||
<div className={"stretch"} />
|
||||
<div className={"github-info"} onClick={()=>{window.location.href = 'https://github.com/datamllab/rlcard'}}>
|
||||
<div className={"github-icon"}><GitHubIcon /></div>
|
||||
<div className={"github-text"}>Github<br /><span>{this.state.stars} stars</span></div>
|
||||
</div>
|
||||
return (
|
||||
<AppBar position="fixed" className={"header-bar-wrapper"}>
|
||||
<div className={"header-bar"}>
|
||||
<Link to="/leaderboard"><img src={logo_white} alt={"Logo"} height="65px" /></Link>
|
||||
<div className={"title unselectable"}><div className={"title-text"}>Showdown<span className={"subtitle"}>{subtitle ? '/ ' + subtitle : ''}</span></div></div>
|
||||
<div className={"stretch"} />
|
||||
<div className={"github-info"} onClick={()=>{window.location.href = 'https://github.com/datamllab/rlcard'}}>
|
||||
<div className={"github-icon"}><GitHubIcon /></div>
|
||||
<div className={"github-text"}>Github<br /><span>{stars} stars</span></div>
|
||||
</div>
|
||||
</AppBar>
|
||||
</div>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Navbar;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import './assets/index.scss';
|
||||
import App from './App';
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const apiUrl = 'http://127.0.0.1:8000';
|
||||
const douzeroDemoUrl = 'http://127.0.0.1:5000';
|
||||
|
||||
export {apiUrl};
|
||||
export { apiUrl, douzeroDemoUrl };
|
||||
|
|
|
@ -1,66 +1,73 @@
|
|||
const suitMap = new Map(
|
||||
[["H", "hearts"], ["D", "diams"], ["S", "spades"], ["C", "clubs"]]
|
||||
);
|
||||
const suitMap = new Map([
|
||||
['H', 'hearts'],
|
||||
['D', 'diams'],
|
||||
['S', 'spades'],
|
||||
['C', 'clubs'],
|
||||
]);
|
||||
|
||||
const suitMapSymbol = new Map(
|
||||
[["H", "\u2665"], ["D", "\u2666"], ["S", "\u2660"], ["C", "\u2663"]]
|
||||
);
|
||||
const suitMapSymbol = new Map([
|
||||
['H', '\u2665'],
|
||||
['D', '\u2666'],
|
||||
['S', '\u2660'],
|
||||
['C', '\u2663'],
|
||||
]);
|
||||
|
||||
export function removeCards(cards, hands){ // remove cards from hands, return the remained hands
|
||||
export function removeCards(cards, hands) {
|
||||
// remove cards from hands, return the remained hands
|
||||
let remainedHands = deepCopy(hands);
|
||||
// if the player's action is pass then return the copy of original hands
|
||||
if(cards === "pass"){
|
||||
if (cards === 'pass') {
|
||||
return remainedHands;
|
||||
}
|
||||
let misMatch = false;
|
||||
cards.forEach(card => {
|
||||
let foundIdx = remainedHands.findIndex(element => {return element === card;});
|
||||
if(foundIdx > -1){
|
||||
cards.forEach((card) => {
|
||||
let foundIdx = remainedHands.findIndex((element) => {
|
||||
return element === card;
|
||||
});
|
||||
if (foundIdx > -1) {
|
||||
remainedHands.splice(foundIdx, 1);
|
||||
}else {
|
||||
} else {
|
||||
misMatch = true;
|
||||
}
|
||||
});
|
||||
if(misMatch)
|
||||
return false;
|
||||
else
|
||||
return remainedHands;
|
||||
if (misMatch) return false;
|
||||
else return remainedHands;
|
||||
}
|
||||
|
||||
export function doubleRaf(callback){
|
||||
export function doubleRaf(callback) {
|
||||
// secure all the animation got rendered before callback function gets executed
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(callback)
|
||||
})
|
||||
requestAnimationFrame(callback);
|
||||
});
|
||||
}
|
||||
|
||||
export function deepCopy(toCopy){
|
||||
export function deepCopy(toCopy) {
|
||||
return JSON.parse(JSON.stringify(toCopy));
|
||||
}
|
||||
|
||||
export function translateCardData(card) {
|
||||
let rankClass;
|
||||
let suitClass = "";
|
||||
let suitClass = '';
|
||||
let rankText;
|
||||
let suitText = "";
|
||||
let suitText = '';
|
||||
// translate rank
|
||||
if(card === "RJ"){
|
||||
rankClass = "big";
|
||||
rankText = "+";
|
||||
suitClass = "joker";
|
||||
suitText = "Joker";
|
||||
}else if(card === "BJ"){
|
||||
rankClass = "little";
|
||||
rankText = "-";
|
||||
suitClass = "joker";
|
||||
suitText = "Joker";
|
||||
}else{
|
||||
rankClass = card.charAt(1) === "T" ? `10` : card.charAt(1).toLowerCase();
|
||||
if (card === 'RJ') {
|
||||
rankClass = 'big';
|
||||
rankText = '+';
|
||||
suitClass = 'joker';
|
||||
suitText = 'Joker';
|
||||
} else if (card === 'BJ') {
|
||||
rankClass = 'little';
|
||||
rankText = '-';
|
||||
suitClass = 'joker';
|
||||
suitText = 'Joker';
|
||||
} else {
|
||||
rankClass = card.charAt(1) === 'T' ? `10` : card.charAt(1).toLowerCase();
|
||||
rankClass = `rank-${rankClass}`;
|
||||
rankText = card.charAt(1) === "T" ? `10` : card.charAt(1);
|
||||
rankText = card.charAt(1) === 'T' ? `10` : card.charAt(1);
|
||||
}
|
||||
// translate suitClass
|
||||
if(card !== "RJ" && card !== "BJ"){
|
||||
if (card !== 'RJ' && card !== 'BJ') {
|
||||
suitClass = suitMap.get(card.charAt(0));
|
||||
suitText = suitMapSymbol.get(card.charAt(0));
|
||||
}
|
||||
|
@ -68,14 +75,15 @@ export function translateCardData(card) {
|
|||
return [rankClass, suitClass, rankText, suitText];
|
||||
}
|
||||
|
||||
export function millisecond2Second(t){
|
||||
return Math.ceil(t/1000);
|
||||
export function millisecond2Second(t) {
|
||||
return Math.ceil(t / 1000);
|
||||
}
|
||||
|
||||
export function debounce(func, wait, immediate) {
|
||||
let timeout;
|
||||
return function() {
|
||||
const context = this, args = arguments;
|
||||
return function () {
|
||||
const context = this,
|
||||
args = arguments;
|
||||
const later = function () {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
|
@ -88,7 +96,157 @@ export function debounce(func, wait, immediate) {
|
|||
}
|
||||
|
||||
export function computeHandCardsWidth(num, emWidth) {
|
||||
if(num === 0)
|
||||
return 0;
|
||||
return (num-1)*1.1*emWidth + 4.3*emWidth*1.2 + 2;
|
||||
if (num === 0) return 0;
|
||||
return (num - 1) * 1.1 * emWidth + 4.3 * emWidth * 1.2 + 2;
|
||||
}
|
||||
|
||||
export function card2SuiteAndRank(card) {
|
||||
if (card === 'BJ') {
|
||||
return { suite: null, rank: 'X' };
|
||||
} else if (card === 'RJ') {
|
||||
return { suite: null, rank: 'D' };
|
||||
} else {
|
||||
return { suite: card[0], rank: card[1] };
|
||||
}
|
||||
}
|
||||
|
||||
export const fullDoudizhuDeck = [
|
||||
'RJ',
|
||||
'BJ',
|
||||
'S2',
|
||||
'C2',
|
||||
'H2',
|
||||
'D2',
|
||||
'SA',
|
||||
'CA',
|
||||
'HA',
|
||||
'DA',
|
||||
'SK',
|
||||
'CK',
|
||||
'HK',
|
||||
'DK',
|
||||
'SQ',
|
||||
'CQ',
|
||||
'HQ',
|
||||
'DQ',
|
||||
'SJ',
|
||||
'CJ',
|
||||
'HJ',
|
||||
'DJ',
|
||||
'ST',
|
||||
'CT',
|
||||
'HT',
|
||||
'DT',
|
||||
'S9',
|
||||
'C9',
|
||||
'H9',
|
||||
'D9',
|
||||
'S8',
|
||||
'C8',
|
||||
'H8',
|
||||
'D8',
|
||||
'S7',
|
||||
'C7',
|
||||
'H7',
|
||||
'D7',
|
||||
'S6',
|
||||
'C6',
|
||||
'H6',
|
||||
'D6',
|
||||
'S5',
|
||||
'C5',
|
||||
'H5',
|
||||
'D5',
|
||||
'S4',
|
||||
'C4',
|
||||
'H4',
|
||||
'D4',
|
||||
'S3',
|
||||
'C3',
|
||||
'H3',
|
||||
'D3',
|
||||
];
|
||||
|
||||
export const fullDoudizhuDeckIndex = {
|
||||
RJ: 54,
|
||||
BJ: 53,
|
||||
S2: 52,
|
||||
C2: 51,
|
||||
H2: 50,
|
||||
D2: 49,
|
||||
SA: 48,
|
||||
CA: 47,
|
||||
HA: 46,
|
||||
DA: 45,
|
||||
SK: 44,
|
||||
CK: 43,
|
||||
HK: 42,
|
||||
DK: 41,
|
||||
SQ: 40,
|
||||
CQ: 39,
|
||||
HQ: 38,
|
||||
DQ: 37,
|
||||
SJ: 36,
|
||||
CJ: 35,
|
||||
HJ: 34,
|
||||
DJ: 33,
|
||||
ST: 32,
|
||||
CT: 31,
|
||||
HT: 30,
|
||||
DT: 29,
|
||||
S9: 28,
|
||||
C9: 27,
|
||||
H9: 26,
|
||||
D9: 25,
|
||||
S8: 24,
|
||||
C8: 23,
|
||||
H8: 22,
|
||||
D8: 21,
|
||||
S7: 20,
|
||||
C7: 19,
|
||||
H7: 18,
|
||||
D7: 17,
|
||||
S6: 16,
|
||||
C6: 15,
|
||||
H6: 14,
|
||||
D6: 13,
|
||||
S5: 12,
|
||||
C5: 11,
|
||||
H5: 10,
|
||||
D5: 9,
|
||||
S4: 8,
|
||||
C4: 7,
|
||||
H4: 6,
|
||||
D4: 5,
|
||||
S3: 4,
|
||||
C3: 3,
|
||||
H3: 2,
|
||||
D3: 1,
|
||||
};
|
||||
|
||||
export function sortDoudizhuCards(cards, ascending = false) {
|
||||
const cardsCopy = cards.slice();
|
||||
return cardsCopy.sort((a, b) => {
|
||||
return ascending
|
||||
? fullDoudizhuDeckIndex[a] - fullDoudizhuDeckIndex[b]
|
||||
: fullDoudizhuDeckIndex[b] - fullDoudizhuDeckIndex[a];
|
||||
});
|
||||
}
|
||||
|
||||
export function isDoudizhuBomb(cards) {
|
||||
if (cards.length === 2) return (cards[0] === 'RJ' && cards[1] === 'BJ') || (cards[0] === 'BJ' && cards[1] === 'RJ');
|
||||
if (cards.length === 4)
|
||||
return cards[0][1] === cards[1][1] && cards[0][1] === cards[2][1] && cards[0][1] === cards[3][1];
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shuffleArray(inputArray) {
|
||||
let array = inputArray.slice();
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
const temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import DoudizhuGameView from "./DoudizhuGameView";
|
||||
import LeducHoldemGameView from "./LeducHoldemGameView";
|
||||
|
||||
export {DoudizhuGameView, LeducHoldemGameView};
|
|
@ -62,7 +62,14 @@ function LeaderBoard () {
|
|||
fetchModelData();
|
||||
}, [reloadMenu]);
|
||||
|
||||
const { type, name } = qs.parse(window.location.search);
|
||||
let { type, name } = qs.parse(window.location.search);
|
||||
// default value
|
||||
if (!type) {
|
||||
type = "game";
|
||||
}
|
||||
if (!name) {
|
||||
name = "leduc-holdem";
|
||||
}
|
||||
let requestUrl = `${apiUrl}/tournament/`;
|
||||
if (type === 'game') {
|
||||
requestUrl += `query_agent_payoff?name=${name}&elements_every_page=${rowsPerPage}&page_index=${page}`
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
import Paper from '@material-ui/core/Paper';
|
||||
import axios from 'axios';
|
||||
import { Layout, Message } from 'element-react';
|
||||
import qs from 'query-string';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { DoudizhuGameBoard } from '../../components/GameBoard';
|
||||
import {
|
||||
card2SuiteAndRank,
|
||||
deepCopy,
|
||||
fullDoudizhuDeck,
|
||||
isDoudizhuBomb,
|
||||
shuffleArray,
|
||||
sortDoudizhuCards,
|
||||
} from '../../utils';
|
||||
import { douzeroDemoUrl } from '../../utils/config';
|
||||
|
||||
const shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice());
|
||||
|
||||
const threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3)));
|
||||
|
||||
const initConsiderationTime = 30000;
|
||||
const considerationTimeDeduction = 1000;
|
||||
const apiPlayDelay = 3000;
|
||||
const mainPlayerId = 0; // index of main player (for the sake of simplify code logic)
|
||||
const playerInfo = [
|
||||
{
|
||||
id: 0,
|
||||
index: 0,
|
||||
role: 'peasant',
|
||||
douzeroPlayerPosition: 1,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
index: 1,
|
||||
role: 'peasant',
|
||||
douzeroPlayerPosition: 2,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
index: 2,
|
||||
role: 'landlord',
|
||||
douzeroPlayerPosition: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let initHands = [
|
||||
shuffledDoudizhuDeck.slice(3, 20),
|
||||
shuffledDoudizhuDeck.slice(20, 37),
|
||||
shuffledDoudizhuDeck.slice(37, 54),
|
||||
];
|
||||
console.log('init hands', initHands);
|
||||
console.log('three landlord card', threeLandlordCards);
|
||||
console.log('player info', playerInfo);
|
||||
const landlordIdx = playerInfo.find((player) => player.role === 'landlord').index;
|
||||
initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice());
|
||||
|
||||
let gameStateTimeout = null;
|
||||
|
||||
let gameHistory = [];
|
||||
let bombNum = 0;
|
||||
let lastMoveLandlord = [];
|
||||
let lastMoveLandlordDown = [];
|
||||
let lastMoveLandlordUp = [];
|
||||
let playedCardsLandlord = [];
|
||||
let playedCardsLandlordDown = [];
|
||||
let playedCardsLandlordUp = [];
|
||||
let legalActions = { turn: -1, actions: [] };
|
||||
|
||||
function PvEDoudizhuDemoView() {
|
||||
const [considerationTime, setConsiderationTime] = useState(initConsiderationTime);
|
||||
const [toggleFade, setToggleFade] = useState('');
|
||||
const [gameStatus, setGameStatus] = useState('ready'); // "ready", "playing", "paused", "over"
|
||||
const [gameState, setGameState] = useState({
|
||||
hands: [[], [], []],
|
||||
latestAction: [[], [], []],
|
||||
currentPlayer: null, // index of current player
|
||||
turn: 0,
|
||||
});
|
||||
const [selectedCards, setSelectedCards] = useState([]); // user selected hand card
|
||||
const [isPassDisabled, setIsPassDisabled] = useState(true);
|
||||
|
||||
const cardStr2Arr = (cardStr) => {
|
||||
return cardStr === 'pass' || cardStr === '' ? 'pass' : cardStr.split(' ');
|
||||
};
|
||||
|
||||
const cardArr2DouzeroFormat = (cards) => {
|
||||
return cards
|
||||
.map((card) => {
|
||||
if (card === 'RJ') return 'D';
|
||||
if (card === 'BJ') return 'X';
|
||||
return card[1];
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
// todo: generate inital player / hand states
|
||||
// for test use
|
||||
|
||||
function timeout(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const proceedNextTurn = async (playingCard, rankOnly = true) => {
|
||||
// if next player is user, get legal actions
|
||||
if ((gameState.currentPlayer + 1) % 3 === mainPlayerId) {
|
||||
const player_hand_cards = cardArr2DouzeroFormat(gameState.hands[mainPlayerId].slice().reverse());
|
||||
let rival_move = '';
|
||||
if (playingCard.length === 0) {
|
||||
rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 1], true));
|
||||
} else {
|
||||
rival_move = rankOnly ? playingCard.join('') : cardArr2DouzeroFormat(playingCard);
|
||||
}
|
||||
const requestBody = {
|
||||
player_hand_cards,
|
||||
rival_move,
|
||||
};
|
||||
const apiRes = await axios.post(`${douzeroDemoUrl}/legal`, qs.stringify(requestBody));
|
||||
const data = apiRes.data;
|
||||
legalActions = {
|
||||
turn: gameState.turn + 1,
|
||||
actions: data.legal_action.split(','),
|
||||
};
|
||||
|
||||
setIsPassDisabled(playingCard.length === 0 && gameHistory[gameHistory.length - 1].length === 0);
|
||||
}
|
||||
|
||||
// delay play for api player
|
||||
if (gameState.currentPlayer !== mainPlayerId && considerationTime > apiPlayDelay) {
|
||||
await timeout(apiPlayDelay);
|
||||
}
|
||||
|
||||
setToggleFade('fade-out');
|
||||
|
||||
let newGameState = deepCopy(gameState);
|
||||
|
||||
// take played card out from hand, and generate playing cards with suite
|
||||
const currentHand = newGameState.hands[gameState.currentPlayer];
|
||||
let newHand;
|
||||
let newLatestAction = [];
|
||||
if (playingCard.length === 0) {
|
||||
newHand = currentHand;
|
||||
newLatestAction = 'pass';
|
||||
} else if (rankOnly) {
|
||||
newHand = currentHand.filter((card) => {
|
||||
if (playingCard.length === 0) return true;
|
||||
|
||||
const { rank } = card2SuiteAndRank(card);
|
||||
const idx = playingCard.indexOf(rank);
|
||||
if (idx >= 0) {
|
||||
playingCard.splice(idx, 1);
|
||||
newLatestAction.push(card);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
newLatestAction = playingCard.slice();
|
||||
newHand = currentHand.filter((card) => {
|
||||
if (playingCard.length === 0) return true;
|
||||
|
||||
const idx = playingCard.indexOf(card);
|
||||
if (idx >= 0) {
|
||||
playingCard.splice(idx, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// update value records for douzero
|
||||
const newHistoryRecord = sortDoudizhuCards(newLatestAction === 'pass' ? [] : newLatestAction, true);
|
||||
switch (playerInfo[gameState.currentPlayer].douzeroPlayerPosition) {
|
||||
case 0:
|
||||
lastMoveLandlord = newHistoryRecord;
|
||||
playedCardsLandlord = playedCardsLandlord.concat(newHistoryRecord);
|
||||
break;
|
||||
case 1:
|
||||
lastMoveLandlordDown = newHistoryRecord;
|
||||
playedCardsLandlordDown = playedCardsLandlordDown.concat(newHistoryRecord);
|
||||
break;
|
||||
case 2:
|
||||
lastMoveLandlordUp = newHistoryRecord;
|
||||
playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord);
|
||||
break;
|
||||
}
|
||||
gameHistory.push(newHistoryRecord);
|
||||
if (isDoudizhuBomb(newHistoryRecord)) bombNum++;
|
||||
|
||||
newGameState.latestAction[gameState.currentPlayer] = newLatestAction;
|
||||
newGameState.hands[gameState.currentPlayer] = newHand;
|
||||
newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 3;
|
||||
newGameState.turn++;
|
||||
setGameState(newGameState);
|
||||
setToggleFade('fade-in');
|
||||
setTimeout(() => {
|
||||
setToggleFade('');
|
||||
}, 200);
|
||||
if (gameStateTimeout) {
|
||||
clearTimeout(gameStateTimeout);
|
||||
}
|
||||
setConsiderationTime(initConsiderationTime);
|
||||
};
|
||||
|
||||
const requestApiPlay = async () => {
|
||||
// gather information for api request
|
||||
const player_position = playerInfo[gameState.currentPlayer].douzeroPlayerPosition;
|
||||
const player_hand_cards = cardArr2DouzeroFormat(gameState.hands[gameState.currentPlayer].slice().reverse());
|
||||
const num_cards_left_landlord =
|
||||
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 0).index].length;
|
||||
const num_cards_left_landlord_down =
|
||||
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 1).index].length;
|
||||
const num_cards_left_landlord_up =
|
||||
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 2).index].length;
|
||||
const three_landlord_cards = cardArr2DouzeroFormat(threeLandlordCards.slice().reverse());
|
||||
const card_play_action_seq = gameHistory
|
||||
.map((cards) => {
|
||||
return cardArr2DouzeroFormat(cards);
|
||||
})
|
||||
.join(',');
|
||||
const other_hand_cards = cardArr2DouzeroFormat(
|
||||
sortDoudizhuCards(
|
||||
gameState.hands[(gameState.currentPlayer + 1) % 3].concat(
|
||||
gameState.hands[(gameState.currentPlayer + 2) % 3],
|
||||
),
|
||||
true,
|
||||
),
|
||||
);
|
||||
const last_move_landlord = cardArr2DouzeroFormat(lastMoveLandlord.slice().reverse());
|
||||
const last_move_landlord_down = cardArr2DouzeroFormat(lastMoveLandlordDown.slice().reverse());
|
||||
const last_move_landlord_up = cardArr2DouzeroFormat(lastMoveLandlordUp.slice().reverse());
|
||||
const bomb_num = bombNum;
|
||||
const played_cards_landlord = cardArr2DouzeroFormat(playedCardsLandlord);
|
||||
const played_cards_landlord_down = cardArr2DouzeroFormat(playedCardsLandlordDown);
|
||||
const played_cards_landlord_up = cardArr2DouzeroFormat(playedCardsLandlordUp);
|
||||
|
||||
const requestBody = {
|
||||
player_position,
|
||||
player_hand_cards,
|
||||
num_cards_left_landlord,
|
||||
num_cards_left_landlord_down,
|
||||
num_cards_left_landlord_up,
|
||||
three_landlord_cards,
|
||||
card_play_action_seq,
|
||||
other_hand_cards,
|
||||
last_move_landlord,
|
||||
last_move_landlord_down,
|
||||
last_move_landlord_up,
|
||||
bomb_num,
|
||||
played_cards_landlord,
|
||||
played_cards_landlord_down,
|
||||
played_cards_landlord_up,
|
||||
};
|
||||
|
||||
try {
|
||||
const apiRes = await axios.post(`${douzeroDemoUrl}/predict`, qs.stringify(requestBody));
|
||||
const data = apiRes.data;
|
||||
|
||||
if (data.status !== 0) {
|
||||
if (data.status === -1) {
|
||||
// check if no legal action can be made
|
||||
const player_hand_cards = cardArr2DouzeroFormat(
|
||||
gameState.hands[gameState.currentPlayer].slice().reverse(),
|
||||
);
|
||||
let rival_move = '';
|
||||
if (gameHistory[gameHistory.length - 1].length > 0) {
|
||||
rival_move = cardArr2DouzeroFormat(
|
||||
sortDoudizhuCards(gameHistory[gameHistory.length - 1], true),
|
||||
);
|
||||
} else if (gameHistory.length >= 2 && gameHistory[gameHistory.length - 2].length > 0) {
|
||||
rival_move = cardArr2DouzeroFormat(
|
||||
sortDoudizhuCards(gameHistory[gameHistory.length - 2], true),
|
||||
);
|
||||
}
|
||||
const requestBody = {
|
||||
player_hand_cards,
|
||||
rival_move,
|
||||
};
|
||||
const apiRes = await axios.post(`${douzeroDemoUrl}/legal`, qs.stringify(requestBody));
|
||||
if (apiRes.data.legal_action === '') proceedNextTurn([]);
|
||||
else {
|
||||
Message({
|
||||
message: 'Error receiving prediction result, please try refresh the page',
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let bestAction = '';
|
||||
if (data.result && Object.keys(data.result).length > 0) {
|
||||
if (Object.keys(data.result).length === 1) bestAction = Object.keys(data.result)[0];
|
||||
else {
|
||||
bestAction = Object.keys(data.result)[0];
|
||||
let bestConfidence = Number(data.result[Object.keys(data.result)[0]]);
|
||||
for (let i = 1; i < Object.keys(data.result).length; i++) {
|
||||
if (Number(data.result[Object.keys(data.result)[i]]) > bestConfidence) {
|
||||
bestAction = Object.keys(data.result)[i];
|
||||
bestConfidence = Number(data.result[Object.keys(data.result)[i]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
proceedNextTurn(bestAction.split(''));
|
||||
}
|
||||
} catch (err) {
|
||||
Message({
|
||||
message: 'Error receiving prediction result, please try refresh the page',
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectedCards = (cards) => {
|
||||
let newSelectedCards = selectedCards.slice();
|
||||
cards.forEach((card) => {
|
||||
if (newSelectedCards.indexOf(card) >= 0) {
|
||||
newSelectedCards.splice(newSelectedCards.indexOf(card), 1);
|
||||
} else {
|
||||
newSelectedCards.push(card);
|
||||
}
|
||||
});
|
||||
setSelectedCards(newSelectedCards);
|
||||
};
|
||||
|
||||
const gameStateTimer = () => {
|
||||
gameStateTimeout = setTimeout(() => {
|
||||
let currentConsiderationTime = considerationTime;
|
||||
if (currentConsiderationTime > 0) {
|
||||
currentConsiderationTime -= considerationTimeDeduction;
|
||||
currentConsiderationTime = Math.max(currentConsiderationTime, 0);
|
||||
setConsiderationTime(currentConsiderationTime);
|
||||
} else {
|
||||
// consideration time used up for current player
|
||||
// if current player is controlled by user, play a random card
|
||||
// todo
|
||||
}
|
||||
}, considerationTimeDeduction);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
gameStateTimer();
|
||||
}, [considerationTime]);
|
||||
|
||||
// set init game state
|
||||
useEffect(() => {
|
||||
// start game
|
||||
setGameStatus('playing');
|
||||
|
||||
const newGameState = deepCopy(gameState);
|
||||
// find landord to be the first player
|
||||
newGameState.currentPlayer = playerInfo.find((element) => element.role === 'landlord').index;
|
||||
newGameState.hands = initHands.map((element) => sortDoudizhuCards(element));
|
||||
setGameState(newGameState);
|
||||
gameStateTimer();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (gameState.currentPlayer) {
|
||||
// if current player is not user, request for API player
|
||||
if (gameState.currentPlayer !== mainPlayerId) {
|
||||
requestApiPlay();
|
||||
}
|
||||
}
|
||||
}, [gameState.currentPlayer]);
|
||||
|
||||
const runNewTurn = () => {};
|
||||
|
||||
const handleMainPlayerAct = (type) => {
|
||||
switch (type) {
|
||||
case 'play': {
|
||||
// check if cards to play is in legal action list
|
||||
if (gameState.turn === legalActions.turn) {
|
||||
if (
|
||||
legalActions.actions.indexOf(cardArr2DouzeroFormat(sortDoudizhuCards(selectedCards, true))) >= 0
|
||||
) {
|
||||
proceedNextTurn(selectedCards, false);
|
||||
} else {
|
||||
Message({
|
||||
message: 'Selected cards are not legal action',
|
||||
type: 'warning',
|
||||
showClose: true,
|
||||
});
|
||||
setSelectedCards([]);
|
||||
}
|
||||
} else {
|
||||
Message({
|
||||
message: 'Legal Action not received or turn info inconsistant',
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pass': {
|
||||
proceedNextTurn([], false);
|
||||
setSelectedCards([]);
|
||||
break;
|
||||
}
|
||||
case 'deselect': {
|
||||
setSelectedCards([]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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
|
||||
isPassDisabled={isPassDisabled}
|
||||
gamePlayable={true}
|
||||
playerInfo={playerInfo}
|
||||
hands={gameState.hands}
|
||||
selectedCards={selectedCards}
|
||||
handleSelectedCards={handleSelectedCards}
|
||||
latestAction={gameState.latestAction}
|
||||
mainPlayerId={mainPlayerId}
|
||||
currentPlayer={gameState.currentPlayer}
|
||||
considerationTime={considerationTime}
|
||||
turn={gameState.turn}
|
||||
runNewTurn={(prevTurn) => runNewTurn(prevTurn)}
|
||||
toggleFade={toggleFade}
|
||||
gameStatus={gameStatus}
|
||||
handleMainPlayerAct={handleMainPlayerAct}
|
||||
/>
|
||||
</Paper>
|
||||
</div>
|
||||
</Layout.Col>
|
||||
</Layout.Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PvEDoudizhuDemoView;
|
|
@ -0,0 +1,3 @@
|
|||
import PvEDoudizhuDemoView from './PvEDoudizhuDemoView';
|
||||
|
||||
export {PvEDoudizhuDemoView};
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import axios from 'axios';
|
||||
import '../../assets/gameview.scss';
|
||||
import { DoudizhuGameBoard } from '../../components/GameBoard';
|
||||
import Navbar from "../../components/Navbar";
|
||||
import {removeCards, doubleRaf, deepCopy, computeHandCardsWidth, translateCardData} from "../../utils";
|
||||
import { apiUrl } from "../../utils/config";
|
||||
|
||||
|
@ -25,7 +24,7 @@ import DialogActions from "@material-ui/core/DialogActions";
|
|||
import Dialog from "@material-ui/core/Dialog";
|
||||
import qs from "query-string";
|
||||
|
||||
class DoudizhuGameView extends React.Component {
|
||||
class DoudizhuReplayView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -381,7 +380,6 @@ class DoudizhuGameView extends React.Component {
|
|||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Navbar gameName={"Doudizhu"} />
|
||||
<div className={"doudizhu-view-container"}>
|
||||
<Layout.Row style={{"height": "540px"}}>
|
||||
<Layout.Col style={{"height": "100%"}} span="17">
|
||||
|
@ -493,4 +491,4 @@ class DoudizhuGameView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default DoudizhuGameView;
|
||||
export default DoudizhuReplayView;
|
|
@ -3,7 +3,6 @@ import axios from 'axios';
|
|||
import qs from 'query-string';
|
||||
import '../../assets/gameview.scss';
|
||||
import {LeducHoldemGameBoard} from '../../components/GameBoard';
|
||||
import Navbar from '../../components/Navbar';
|
||||
import {deepCopy} from "../../utils";
|
||||
import { apiUrl } from "../../utils/config";
|
||||
|
||||
|
@ -26,7 +25,7 @@ import DialogContent from '@material-ui/core/DialogContent';
|
|||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
|
||||
class LeducHoldemGameView extends React.Component {
|
||||
class LeducHoldemReplayView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -398,7 +397,6 @@ class LeducHoldemGameView extends React.Component {
|
|||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Navbar gameName="Leduc Hold'em" />
|
||||
<div className={"leduc-view-container"}>
|
||||
<Layout.Row style={{"height": "540px"}}>
|
||||
<Layout.Col style={{"height": "100%"}} span="17">
|
||||
|
@ -513,4 +511,4 @@ class LeducHoldemGameView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default LeducHoldemGameView;
|
||||
export default LeducHoldemReplayView;
|
|
@ -0,0 +1,4 @@
|
|||
import DoudizhuReplayView from "./DoudizhuReplayView";
|
||||
import LeducHoldemReplayView from "./LeducHoldemReplayView";
|
||||
|
||||
export {DoudizhuReplayView, LeducHoldemReplayView};
|
Loading…
Reference in New Issue