adding select role interface for pve demo; fix bugs

This commit is contained in:
Songyi Huang 2021-04-25 16:08:50 -07:00
parent b6f0503c9e
commit edc060c00a
3 changed files with 205 additions and 85 deletions

View File

@ -3,12 +3,21 @@
.doudizhu-wrapper { .doudizhu-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
//background-color: #C3CDFF;
background-image: url("./images/gameboard.png");
background-repeat: no-repeat;
background-size: 100% 125%;
background-position: bottom;
position: relative; position: relative;
#gameboard-background {
width: 100%;
height: 100%;
background-image: url("./images/gameboard.png");
background-repeat: no-repeat;
background-size: 100% 125%;
background-position: bottom;
&.blur-background {
filter: blur(3px);
pointer-events: none;
}
}
.played-card-area { .played-card-area {
font-size: 12px; font-size: 12px;

View File

@ -1,6 +1,7 @@
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Chip from '@material-ui/core/Chip'; import Chip from '@material-ui/core/Chip';
import { Layout } from 'element-react';
import React from 'react'; import React from 'react';
import '../../assets/doudizhu.scss'; import '../../assets/doudizhu.scss';
import Landlord_wName from '../../assets/images/Portrait/Landlord_wName.png'; import Landlord_wName from '../../assets/images/Portrait/Landlord_wName.png';
@ -14,19 +15,19 @@ class DoudizhuGameBoard extends React.Component {
return this.props.playerInfo[playerIdx].role === 'landlord' ? ( return this.props.playerInfo[playerIdx].role === 'landlord' ? (
<div> <div>
<img src={Landlord_wName} alt={'Landlord'} height="70%" width="70%" /> <img src={Landlord_wName} alt={'Landlord'} height="70%" width="70%" />
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" /> <Chip avatar={<Avatar>ID</Avatar>} label={playerId} color="primary" />
</div> </div>
) : ( ) : (
<div> <div>
<img src={Peasant_wName} alt={'Peasant'} height="70%" width="70%" /> <img src={Peasant_wName} alt={'Peasant'} height="70%" width="70%" />
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" /> <Chip avatar={<Avatar>ID</Avatar>} label={playerId} color="primary" />
</div> </div>
); );
} else } else
return ( return (
<div> <div>
<img src={PlaceHolderPlayer} alt={'Player'} height="70%" width="70%" /> <img src={PlaceHolderPlayer} alt={'Player'} height="70%" width="70%" />
<Chip avatar={<Avatar>ID</Avatar>} label={playerId} clickable color="primary" /> <Chip avatar={<Avatar>ID</Avatar>} label={playerId} color="primary" />
</div> </div>
); );
} }
@ -90,7 +91,11 @@ class DoudizhuGameBoard extends React.Component {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card); const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return ( return (
<li key={`handCard-${card}`}> <li key={`handCard-${card}`}>
<label className={`card ${rankClass} ${suitClass}`}> <label
className={`card ${
this.props.showCardBack ? 'back' : '' + rankClass + ' ' + suitClass
}`}
>
<span className="rank">{rankText}</span> <span className="rank">{rankText}</span>
<span className="suit">{suitText}</span> <span className="suit">{suitText}</span>
</label> </label>
@ -107,7 +112,11 @@ class DoudizhuGameBoard extends React.Component {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card); const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return ( return (
<li key={`handCard-${card}`}> <li key={`handCard-${card}`}>
<label className={`card ${rankClass} ${suitClass}`}> <label
className={`card ${
this.props.showCardBack ? 'back' : '' + rankClass + ' ' + suitClass
}`}
>
<span className="rank">{rankText}</span> <span className="rank">{rankText}</span>
<span className="suit">{suitText}</span> <span className="suit">{suitText}</span>
</label> </label>
@ -209,47 +218,102 @@ class DoudizhuGameBoard extends React.Component {
} }
return ( return (
<div className="doudizhu-wrapper" style={{}}> <div className="doudizhu-wrapper" style={{}}>
<div id={'left-player'}> <div
<div className="player-main-area"> id={'gameboard-background'}
<div className="player-info">{this.computePlayerPortrait(leftId, leftIdx)}</div> className={
{leftIdx >= 0 ? ( this.props.gameStatus === 'ready' && this.props.gamePlayable ? 'blur-background' : undefined
this.computeSideHand(this.props.hands[leftIdx]) }
) : ( >
<div className="player-hand-placeholder"> <div id={'left-player'}>
<span>Waiting...</span> <div className="player-main-area">
</div> <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>
<div className="played-card-area">{leftIdx >= 0 ? this.playerDecisionArea(leftIdx) : ''}</div> <div id={'right-player'}>
</div> <div className="player-main-area">
<div id={'right-player'}> <div className="player-info">{this.computePlayerPortrait(rightId, rightIdx)}</div>
<div className="player-main-area"> {rightIdx >= 0 ? (
<div className="player-info">{this.computePlayerPortrait(rightId, rightIdx)}</div> this.computeSideHand(this.props.hands[rightIdx])
{rightIdx >= 0 ? ( ) : (
this.computeSideHand(this.props.hands[rightIdx]) <div className="player-hand-placeholder">
) : ( <span>Waiting...</span>
<div className="player-hand-placeholder"> </div>
<span>Waiting...</span> )}
</div> </div>
)} <div className="played-card-area">{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}</div>
</div> </div>
<div className="played-card-area">{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}</div> <div id={'bottom-player'}>
</div> <div className="played-card-area">
<div id={'bottom-player'}> {bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''}
<div className="played-card-area">{bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''}</div> </div>
<div className="player-main-area"> <div className="player-main-area">
<div className="player-info">{this.computePlayerPortrait(bottomId, bottomIdx)}</div> <div className="player-info">{this.computePlayerPortrait(bottomId, bottomIdx)}</div>
{bottomIdx >= 0 ? ( {bottomIdx >= 0 ? (
<div className="player-hand"> <div className="player-hand">
{this.computeSingleLineHand(this.props.hands[bottomIdx], '', true)} {this.computeSingleLineHand(this.props.hands[bottomIdx], '', true)}
</div> </div>
) : ( ) : (
<div className="player-hand-placeholder"> <div className="player-hand-placeholder">
<span>Waiting...</span> <span>Waiting...</span>
</div> </div>
)} )}
</div>
</div> </div>
</div> </div>
{this.props.gamePlayable && this.props.gameStatus === 'ready' ? (
<Layout.Row
type="flex"
style={{
position: 'absolute',
top: 0,
height: '100%',
width: '100%',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Button
onClick={() => this.props.handleSelectRole('landlord_up')}
style={{ width: '220px' }}
variant="contained"
color="primary"
startIcon={<img src={Peasant_wName} alt="Peasant" width="48px" />}
>
Play as Peasant
<br />
(Early Hand)
</Button>
<Button
onClick={() => this.props.handleSelectRole('landlord')}
style={{ width: '220px', marginTop: '20px', marginBottom: '20px' }}
variant="contained"
color="primary"
startIcon={<img src={Landlord_wName} alt="Peasant" width="48px" />}
>
Play as Landlord
</Button>
<Button
onClick={() => this.props.handleSelectRole('landlord_down')}
style={{ width: '220px' }}
variant="contained"
color="primary"
startIcon={<img src={Peasant_wName} alt="Peasant" width="48px" />}
>
Play as Peasant
<br />
(Late Hand)
</Button>
</Layout.Row>
) : undefined}
</div> </div>
); );
} }

View File

@ -9,6 +9,7 @@ import axios from 'axios';
import { Layout, Message } from 'element-react'; import { Layout, Message } from 'element-react';
import qs from 'query-string'; import qs from 'query-string';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import '../../assets/doudizhu.scss';
import { DoudizhuGameBoard } from '../../components/GameBoard'; import { DoudizhuGameBoard } from '../../components/GameBoard';
import { import {
card2SuiteAndRank, card2SuiteAndRank,
@ -28,26 +29,7 @@ const initConsiderationTime = 30000;
const considerationTimeDeduction = 1000; const considerationTimeDeduction = 1000;
const apiPlayDelay = 3000; const apiPlayDelay = 3000;
const mainPlayerId = 0; // index of main player (for the sake of simplify code logic) const mainPlayerId = 0; // index of main player (for the sake of simplify code logic)
const playerInfo = [ let 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 = [ let initHands = [
shuffledDoudizhuDeck.slice(3, 20), shuffledDoudizhuDeck.slice(3, 20),
@ -57,8 +39,6 @@ let initHands = [
console.log('init hands', initHands); console.log('init hands', initHands);
console.log('three landlord card', threeLandlordCards); console.log('three landlord card', threeLandlordCards);
console.log('player info', playerInfo); 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 gameStateTimeout = null;
@ -87,10 +67,6 @@ function PvEDoudizhuDemoView() {
const [selectedCards, setSelectedCards] = useState([]); // user selected hand card const [selectedCards, setSelectedCards] = useState([]); // user selected hand card
const [isPassDisabled, setIsPassDisabled] = useState(true); const [isPassDisabled, setIsPassDisabled] = useState(true);
const cardStr2Arr = (cardStr) => {
return cardStr === 'pass' || cardStr === '' ? 'pass' : cardStr.split(' ');
};
const cardArr2DouzeroFormat = (cards) => { const cardArr2DouzeroFormat = (cards) => {
return cards return cards
.map((card) => { .map((card) => {
@ -190,6 +166,8 @@ function PvEDoudizhuDemoView() {
lastMoveLandlordUp = newHistoryRecord; lastMoveLandlordUp = newHistoryRecord;
playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord); playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord);
break; break;
default:
break;
} }
gameHistory.push(newHistoryRecord); gameHistory.push(newHistoryRecord);
if (isDoudizhuBomb(newHistoryRecord)) bombNum++; if (isDoudizhuBomb(newHistoryRecord)) bombNum++;
@ -215,10 +193,11 @@ function PvEDoudizhuDemoView() {
gameEndDialogText = winner.role + ' wins!'; gameEndDialogText = winner.role + ' wins!';
setIsGameEndDialogOpen(true); setIsGameEndDialogOpen(true);
}, 300); }, 300);
return; } else {
setConsiderationTime(initConsiderationTime);
// manually trigger timer if consideration time equals initConsiderationTime
if (initConsiderationTime === considerationTime) gameStateTimer();
} }
setConsiderationTime(initConsiderationTime);
}; };
const requestApiPlay = async () => { const requestApiPlay = async () => {
@ -345,6 +324,51 @@ function PvEDoudizhuDemoView() {
setSelectedCards(newSelectedCards); setSelectedCards(newSelectedCards);
}; };
const handleSelectRole = (role) => {
const playerInfoTemplate = [
{
id: 0,
index: 0,
role: 'peasant',
douzeroPlayerPosition: -1,
},
{
id: 1,
index: 1,
role: 'peasant',
douzeroPlayerPosition: -1,
},
{
id: 2,
index: 2,
role: 'peasant',
douzeroPlayerPosition: -1,
},
];
switch (role) {
case 'landlord_up':
playerInfo = deepCopy(playerInfoTemplate);
playerInfo[1].role = 'landlord';
break;
case 'landlord':
playerInfo = deepCopy(playerInfoTemplate);
playerInfo[0].role = 'landlord';
break;
case 'landlord_down':
playerInfo = deepCopy(playerInfoTemplate);
playerInfo[2].role = 'landlord';
break;
default:
break;
}
const landlordIdx = playerInfo.find((player) => player.role === 'landlord').index;
playerInfo[landlordIdx].douzeroPlayerPosition = 0;
playerInfo[(landlordIdx + 1) % 3].douzeroPlayerPosition = 1;
playerInfo[(landlordIdx + 2) % 3].douzeroPlayerPosition = 2;
initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice());
setGameStatus('playing');
};
const gameStateTimer = () => { const gameStateTimer = () => {
gameStateTimeout = setTimeout(() => { gameStateTimeout = setTimeout(() => {
let currentConsiderationTime = considerationTime; let currentConsiderationTime = considerationTime;
@ -365,12 +389,7 @@ function PvEDoudizhuDemoView() {
// todo: proceed next game option // todo: proceed next game option
}; };
useEffect(() => { const startGame = async () => {
gameStateTimer();
}, [considerationTime]);
// set init game state
useEffect(() => {
// start game // start game
setGameStatus('playing'); setGameStatus('playing');
@ -378,12 +397,33 @@ function PvEDoudizhuDemoView() {
// find landord to be the first player // find landord to be the first player
newGameState.currentPlayer = playerInfo.find((element) => element.role === 'landlord').index; newGameState.currentPlayer = playerInfo.find((element) => element.role === 'landlord').index;
newGameState.hands = initHands.map((element) => sortDoudizhuCards(element)); newGameState.hands = initHands.map((element) => sortDoudizhuCards(element));
// if first player is user, fetch legal actions
if (newGameState.currentPlayer === mainPlayerId) {
const player_hand_cards = cardArr2DouzeroFormat(newGameState.hands[mainPlayerId].slice().reverse());
let rival_move = '';
const requestBody = {
player_hand_cards,
rival_move,
};
const apiRes = await axios.post(`${douzeroDemoUrl}/legal`, qs.stringify(requestBody));
const data = apiRes.data;
legalActions = {
turn: 0,
actions: data.legal_action.split(','),
};
}
setGameState(newGameState); setGameState(newGameState);
gameStateTimer(); gameStateTimer();
}, []); };
useEffect(() => { useEffect(() => {
if (gameState.currentPlayer) { gameStateTimer();
}, [considerationTime]);
useEffect(() => {
if (gameState.currentPlayer && gameStatus === 'playing') {
// if current player is not user, request for API player // if current player is not user, request for API player
if (gameState.currentPlayer !== mainPlayerId) { if (gameState.currentPlayer !== mainPlayerId) {
requestApiPlay(); requestApiPlay();
@ -391,6 +431,10 @@ function PvEDoudizhuDemoView() {
} }
}, [gameState.currentPlayer]); }, [gameState.currentPlayer]);
useEffect(() => {
if (gameStatus === 'playing') startGame();
}, [gameStatus]);
const runNewTurn = () => {}; const runNewTurn = () => {};
const handleMainPlayerAct = (type) => { const handleMainPlayerAct = (type) => {
@ -446,7 +490,7 @@ function PvEDoudizhuDemoView() {
<DialogContentText id="alert-dialog-description">{gameEndDialogText}</DialogContentText> <DialogContentText id="alert-dialog-description">{gameEndDialogText}</DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleCloseGameEndDialog} color="primary" autoFocus> <Button onClick={() => handleCloseGameEndDialog()} color="primary" autoFocus>
OK OK
</Button> </Button>
</DialogActions> </DialogActions>
@ -457,6 +501,8 @@ function PvEDoudizhuDemoView() {
<div style={{ height: '100%' }}> <div style={{ height: '100%' }}>
<Paper className={'doudizhu-gameboard-paper'} elevation={3}> <Paper className={'doudizhu-gameboard-paper'} elevation={3}>
<DoudizhuGameBoard <DoudizhuGameBoard
showCardBack={true}
handleSelectRole={handleSelectRole}
isPassDisabled={isPassDisabled} isPassDisabled={isPassDisabled}
gamePlayable={true} gamePlayable={true}
playerInfo={playerInfo} playerInfo={playerInfo}
@ -473,6 +519,7 @@ function PvEDoudizhuDemoView() {
gameStatus={gameStatus} gameStatus={gameStatus}
handleMainPlayerAct={handleMainPlayerAct} handleMainPlayerAct={handleMainPlayerAct}
/> />
{/* )} */}
</Paper> </Paper>
</div> </div>
</Layout.Col> </Layout.Col>