add prediction area & control area

This commit is contained in:
Songyi Huang 2021-04-25 20:48:34 -07:00
parent 1593b1235a
commit 3a878122d9
3 changed files with 240 additions and 47 deletions

View File

@ -101,6 +101,12 @@
box-sizing: border-box;
width: 80px;
}
.MuiFormControlLabel-label {
display: table-cell;
line-height: 51px;
vertical-align: middle;
}
}
.doudizhu-view-container {

View File

@ -143,43 +143,41 @@ class DoudizhuGameBoard extends React.Component {
<div style={{ marginRight: '2em' }} className={'timer ' + fadeClassName}>
<div className="timer-text">{millisecond2Second(this.props.considerationTime)}</div>
</div>
{this.props.gamePlayable ?
(<>
<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 || this.props.selectedCards.length === 0}
onClick={() => {
this.props.handleMainPlayerAct('play');
}}
variant="contained"
color="primary"
>
Play
</Button>
</>)
:
undefined}
{this.props.gamePlayable ? (
<>
<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 || this.props.selectedCards.length === 0}
onClick={() => {
this.props.handleMainPlayerAct('play');
}}
variant="contained"
color="primary"
>
Play
</Button>
</>
) : undefined}
</div>
);
} else {
@ -195,7 +193,12 @@ class DoudizhuGameBoard extends React.Component {
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.turn !== this.props.turn && this.props.turn !== 0 && this.props.gameStatus === 'playing') {
if (
this.props.runNewTurn &&
prevProps.turn !== this.props.turn &&
this.props.turn !== 0 &&
this.props.gameStatus === 'playing'
) {
// new turn starts
this.props.runNewTurn(prevProps);
}

View File

@ -4,7 +4,12 @@ import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Divider from '@material-ui/core/Divider';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormGroup from '@material-ui/core/FormGroup';
import Paper from '@material-ui/core/Paper';
import Switch from '@material-ui/core/Switch';
import NotInterestedIcon from '@material-ui/icons/NotInterested';
import axios from 'axios';
import { Layout, Message } from 'element-react';
import qs from 'query-string';
@ -13,11 +18,13 @@ import '../../assets/doudizhu.scss';
import { DoudizhuGameBoard } from '../../components/GameBoard';
import {
card2SuiteAndRank,
computeHandCardsWidth,
deepCopy,
fullDoudizhuDeck,
isDoudizhuBomb,
shuffleArray,
sortDoudizhuCards,
translateCardData,
} from '../../utils';
import { douzeroDemoUrl } from '../../utils/config';
@ -66,6 +73,9 @@ function PvEDoudizhuDemoView() {
});
const [selectedCards, setSelectedCards] = useState([]); // user selected hand card
const [isPassDisabled, setIsPassDisabled] = useState(true);
const [predictionRes, setPredictionRes] = useState({ prediction: [], hands: [] });
const [hideRivalHand, setHideRivalHand] = useState(false);
const [hidePredictionArea, setHidePredictionArea] = useState(false);
const cardArr2DouzeroFormat = (cards) => {
return cards
@ -272,20 +282,41 @@ function PvEDoudizhuDemoView() {
rival_move,
};
const apiRes = await axios.post(`${douzeroDemoUrl}/legal`, qs.stringify(requestBody));
if (apiRes.data.legal_action === '') proceedNextTurn([]);
else if (apiRes.data.legal_action.split(',').length === 1)
if (apiRes.data.legal_action === '') {
proceedNextTurn([]);
setPredictionRes({
prediction: [['', 'Only Choice']],
hands: gameState.hands[gameState.currentPlayer].slice(),
});
} else if (apiRes.data.legal_action.split(',').length === 1) {
proceedNextTurn(apiRes.data.legal_action.split(''));
else {
setPredictionRes({
prediction: [[apiRes.data.legal_action, 'Only Choice']],
hands: gameState.hands[gameState.currentPlayer].slice(),
});
} else {
Message({
message: 'Error receiving prediction result, please try refresh the page',
type: 'error',
showClose: true,
});
}
} else {
Message({
message: `Error: ${apiRes.data.message}`,
type: 'error',
showClose: true,
});
}
} else {
let bestAction = '';
if (data.result && Object.keys(data.result).length > 0) {
setPredictionRes({
prediction: Object.entries(data.result).sort((a, b) => {
return Number(b[1]) - Number(a[1]);
}),
hands: gameState.hands[gameState.currentPlayer].slice(),
});
if (Object.keys(data.result).length === 1) bestAction = Object.keys(data.result)[0];
else {
bestAction = Object.keys(data.result)[0];
@ -309,6 +340,14 @@ function PvEDoudizhuDemoView() {
}
};
const toggleHideRivalHand = () => {
setHideRivalHand(!hideRivalHand);
};
const toggleHidePredictionArea = () => {
setHidePredictionArea(!hidePredictionArea);
};
const handleSelectedCards = (cards) => {
let newSelectedCards = selectedCards.slice();
cards.forEach((card) => {
@ -416,14 +455,16 @@ function PvEDoudizhuDemoView() {
};
useEffect(() => {
gameStateTimer();
if (gameStatus === 'playing') gameStateTimer();
}, [considerationTime]);
useEffect(() => {
if (gameState.currentPlayer && gameStatus === 'playing') {
if (gameState.currentPlayer !== null && gameStatus === 'playing') {
// if current player is not user, request for API player
if (gameState.currentPlayer !== mainPlayerId) {
requestApiPlay();
} else {
setPredictionRes({ prediction: [], hands: [] });
}
}
}, [gameState.currentPlayer]);
@ -432,8 +473,6 @@ function PvEDoudizhuDemoView() {
if (gameStatus === 'playing') startGame();
}, [gameStatus]);
const runNewTurn = () => {};
const handleMainPlayerAct = (type) => {
switch (type) {
case 'play': {
@ -472,6 +511,82 @@ function PvEDoudizhuDemoView() {
}
};
const computePredictionCards = (cards, hands) => {
let computedCards = [];
if (cards.length > 0) {
hands.forEach((card) => {
const { rank } = card2SuiteAndRank(card);
const idx = cards.indexOf(rank);
if (idx >= 0) {
cards.splice(idx, 1);
computedCards.push(card);
}
});
} else {
computedCards = 'pass';
}
if (computedCards === 'pass') {
return (
<div className={'non-card ' + toggleFade}>
<span>Pass</span>
</div>
);
} else {
return (
<div className={'unselectable playingCards loose ' + toggleFade}>
<ul className="hand" style={{ width: computeHandCardsWidth(computedCards.length, 10) }}>
{computedCards.map((card) => {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return (
<li key={`handCard-${card}`}>
<label className={`card ${rankClass} ${suitClass}`} href="/#">
<span className="rank">{rankText}</span>
<span className="suit">{suitText}</span>
</label>
</li>
);
})}
</ul>
</div>
);
}
};
const computeProbabilityItem = (idx) => {
if (gameStatus !== 'ready') {
if (hidePredictionArea) {
return (
<div className={'playing'}>
<div className={'non-card'}>
<span>{'Hidden'}</span>
</div>
</div>
);
}
return (
<div className={'playing'}>
<div className="probability-move">
{predictionRes.prediction.length > idx ? (
computePredictionCards(predictionRes.prediction[idx][0].split(''), predictionRes.hands)
) : (
<NotInterestedIcon fontSize="large" />
)}
</div>
{predictionRes.prediction.length > idx ? (
<div className={'non-card'}>
<span>{`Expected Score: ${predictionRes.prediction[idx][1]}`}</span>
</div>
) : (
''
)}
</div>
);
} else {
return <span className={'waiting'}>Waiting...</span>;
}
};
return (
<div>
<Dialog
@ -498,7 +613,7 @@ function PvEDoudizhuDemoView() {
<div style={{ height: '100%' }}>
<Paper className={'doudizhu-gameboard-paper'} elevation={3}>
<DoudizhuGameBoard
showCardBack={true}
showCardBack={hideRivalHand}
handleSelectRole={handleSelectRole}
isPassDisabled={isPassDisabled}
gamePlayable={true}
@ -511,7 +626,6 @@ function PvEDoudizhuDemoView() {
currentPlayer={gameState.currentPlayer}
considerationTime={considerationTime}
turn={gameState.turn}
runNewTurn={(prevTurn) => runNewTurn(prevTurn)}
toggleFade={toggleFade}
gameStatus={gameStatus}
handleMainPlayerAct={handleMainPlayerAct}
@ -520,7 +634,77 @@ function PvEDoudizhuDemoView() {
</Paper>
</div>
</Layout.Col>
<Layout.Col span="7" style={{ height: '100%' }}>
<Paper className={'doudizhu-probability-paper'} elevation={3}>
<div className={'probability-player'}>
{playerInfo.length > 0 && gameState.currentPlayer !== null ? (
<span>
Current Player: {gameState.currentPlayer}
<br />
{playerInfo[gameState.currentPlayer].role}
</span>
) : (
<span>Waiting...</span>
)}
</div>
<Divider />
<div className={'probability-table'}>
<div className={'probability-item'}>{computeProbabilityItem(0)}</div>
<div className={'probability-item'}>{computeProbabilityItem(1)}</div>
<div className={'probability-item'}>{computeProbabilityItem(2)}</div>
</div>
</Paper>
</Layout.Col>
</Layout.Row>
<div className="game-controller">
<Paper className={'game-controller-paper'} elevation={3}>
<Layout.Row style={{ height: '51px' }}>
<Layout.Col span="6" style={{ height: '51px', lineHeight: '48px' }}>
<FormGroup style={{ height: '100%' }}>
<FormControlLabel
style={{ textAlign: 'center', height: '100%', display: 'table' }}
class="switch-control"
control={<Switch checked={!hideRivalHand} onChange={toggleHideRivalHand} />}
label="Show Rival Cards"
/>
</FormGroup>
</Layout.Col>
<Layout.Col span="1" style={{ height: '100%', width: '1px' }}>
<Divider orientation="vertical" />
</Layout.Col>
<Layout.Col span="6" style={{ height: '51px', lineHeight: '48px' }}>
<FormGroup sty282718 le={{ height: '100%' }}>
<FormControlLabel
style={{ textAlign: 'center', height: '100%', display: 'table' }}
class="switch-control"
control={
<Switch checked={!hidePredictionArea} onChange={toggleHidePredictionArea} />
}
label="Show Prediction Area"
/>
</FormGroup>
</Layout.Col>
<Layout.Col span="1" style={{ height: '100%', width: '1px' }}>
<Divider orientation="vertical" />
</Layout.Col>
<Layout.Col
span="6"
style={{ height: '51px', lineHeight: '51px', marginLeft: '-1px', marginRight: '-1px' }}
>
<div style={{ textAlign: 'center' }}>{`Turn ${gameState.turn}`}</div>
</Layout.Col>
<Layout.Col span="1" style={{ height: '100%', width: '1px' }}>
<Divider orientation="vertical" />
</Layout.Col>
<Layout.Col
span="6"
style={{ height: '51px', lineHeight: '51px', marginLeft: '-1px', marginRight: '-1px' }}
>
<div style={{ textAlign: 'center' }}>{`Game Status: ${gameStatus}`}</div>
</Layout.Col>
</Layout.Row>
</Paper>
</div>
</div>
</div>
);