This commit is contained in:
Songyi Huang 2021-05-02 15:25:42 -07:00
parent 9da0633270
commit a4d95e943a
2 changed files with 149 additions and 39 deletions

View File

@ -7,7 +7,7 @@ import '../../assets/doudizhu.scss';
import Landlord_wName from '../../assets/images/Portrait/Landlord_wName.png'; import Landlord_wName from '../../assets/images/Portrait/Landlord_wName.png';
import Peasant_wName from '../../assets/images/Portrait/Peasant_wName.png'; import Peasant_wName from '../../assets/images/Portrait/Peasant_wName.png';
import PlaceHolderPlayer from '../../assets/images/Portrait/Player.png'; import PlaceHolderPlayer from '../../assets/images/Portrait/Player.png';
import { computeHandCardsWidth, millisecond2Second, translateCardData } from '../../utils'; import { computeHandCardsWidth, millisecond2Second, translateCardData, sortDoudizhuCards } from '../../utils';
class DoudizhuGameBoard extends React.Component { class DoudizhuGameBoard extends React.Component {
computePlayerPortrait(playerId, playerIdx) { computePlayerPortrait(playerId, playerIdx) {
@ -32,7 +32,8 @@ class DoudizhuGameBoard extends React.Component {
); );
} }
computeSingleLineHand(cards, fadeClassName = '', cardSelectable = false) { computeSingleLineHand(inputCards, fadeClassName = '', cardSelectable = false) {
const cards = inputCards === 'pass' ? inputCards : sortDoudizhuCards(inputCards);
if (cards === 'pass') { if (cards === 'pass') {
return ( return (
<div className="non-card"> <div className="non-card">

View File

@ -1,5 +1,6 @@
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import Slider from '@material-ui/core/Slider';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from '@material-ui/core/DialogContentText';
@ -28,14 +29,13 @@ import {
} from '../../utils'; } from '../../utils';
import { douzeroDemoUrl } from '../../utils/config'; import { douzeroDemoUrl } from '../../utils/config';
const shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice()); let shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice());
let threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3))); let threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3)));
const originalThreeLandlordCards = threeLandlordCards.slice(); let originalThreeLandlordCards = threeLandlordCards.slice();
const initConsiderationTime = 30000; const initConsiderationTime = 30000;
const considerationTimeDeduction = 1000; const considerationTimeDeduction = 1000;
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)
let playerInfo = []; let playerInfo = [];
@ -60,8 +60,10 @@ let playedCardsLandlordDown = [];
let playedCardsLandlordUp = []; let playedCardsLandlordUp = [];
let legalActions = { turn: -1, actions: [] }; let legalActions = { turn: -1, actions: [] };
let gameEndDialogText = ''; let gameEndDialogText = '';
let syncGameStatus = 'ready';
function PvEDoudizhuDemoView() { function PvEDoudizhuDemoView() {
const [apiPlayDelay, setApiPlayDelay] = useState(3000);
const [isGameEndDialogOpen, setIsGameEndDialogOpen] = useState(false); const [isGameEndDialogOpen, setIsGameEndDialogOpen] = useState(false);
const [considerationTime, setConsiderationTime] = useState(initConsiderationTime); const [considerationTime, setConsiderationTime] = useState(initConsiderationTime);
const [toggleFade, setToggleFade] = useState(''); const [toggleFade, setToggleFade] = useState('');
@ -203,6 +205,10 @@ function PvEDoudizhuDemoView() {
newGameState.hands[gameState.currentPlayer] = newHand; newGameState.hands[gameState.currentPlayer] = newHand;
newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 3; newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 3;
newGameState.turn++; newGameState.turn++;
if (newHand.length === 0) {
setGameStatus('over');
syncGameStatus = 'over';
}
setGameState(newGameState); setGameState(newGameState);
setToggleFade('fade-in'); setToggleFade('fade-in');
setTimeout(() => { setTimeout(() => {
@ -215,7 +221,6 @@ function PvEDoudizhuDemoView() {
if (newHand.length === 0) { if (newHand.length === 0) {
const winner = playerInfo[gameState.currentPlayer]; const winner = playerInfo[gameState.currentPlayer];
setGameStatus('over');
setTimeout(() => { setTimeout(() => {
gameEndDialogText = winner.role + ' wins!'; gameEndDialogText = winner.role + ' wins!';
setIsGameEndDialogOpen(true); setIsGameEndDialogOpen(true);
@ -331,9 +336,12 @@ function PvEDoudizhuDemoView() {
} else { } else {
let bestAction = ''; let bestAction = '';
if (data.result && Object.keys(data.result).length > 0) { if (data.result && Object.keys(data.result).length > 0) {
setPredictionRes({ const sortedResult = Object.entries(data.result).sort((a, b) => {
prediction: Object.entries(data.result).sort((a, b) => {
return Number(b[1]) - Number(a[1]); return Number(b[1]) - Number(a[1]);
});
setPredictionRes({
prediction: sortedResult.map((result) => {
return [result[0], data.win_rates[result[0]]];
}), }),
hands: gameState.hands[gameState.currentPlayer].slice(), hands: gameState.hands[gameState.currentPlayer].slice(),
}); });
@ -360,11 +368,8 @@ function PvEDoudizhuDemoView() {
} }
}; };
const toggleHideRivalHand = () => {
setHideRivalHand(!hideRivalHand);
};
const toggleHidePredictionArea = () => { const toggleHidePredictionArea = () => {
setHideRivalHand(!hideRivalHand);
setHidePredictionArea(!hidePredictionArea); setHidePredictionArea(!hidePredictionArea);
}; };
@ -423,6 +428,7 @@ function PvEDoudizhuDemoView() {
playerInfo[(landlordIdx + 2) % 3].douzeroPlayerPosition = 2; playerInfo[(landlordIdx + 2) % 3].douzeroPlayerPosition = 2;
initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice()); initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice());
setGameStatus('playing'); setGameStatus('playing');
syncGameStatus = 'playing';
}; };
const gameStateTimer = () => { const gameStateTimer = () => {
@ -441,14 +447,53 @@ function PvEDoudizhuDemoView() {
}; };
const handleCloseGameEndDialog = () => { const handleCloseGameEndDialog = () => {
// reset all game state for new game
shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice());
threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3)));
originalThreeLandlordCards = threeLandlordCards.slice();
initHands = [
shuffledDoudizhuDeck.slice(3, 20),
shuffledDoudizhuDeck.slice(20, 37),
shuffledDoudizhuDeck.slice(37, 54),
];
playerInfo = [];
gameStateTimeout = null;
gameHistory = [];
bombNum = 0;
lastMoveLandlord = [];
lastMoveLandlordDown = [];
lastMoveLandlordUp = [];
playedCardsLandlord = [];
playedCardsLandlordDown = [];
playedCardsLandlordUp = [];
legalActions = { turn: -1, actions: [] };
gameEndDialogText = '';
setConsiderationTime(initConsiderationTime);
setToggleFade('');
setGameState({
hands: [[], [], []],
latestAction: [[], [], []],
currentPlayer: null, // index of current player
turn: 0,
});
setSelectedCards([]); // user selected hand card
setPredictionRes({ prediction: [], hands: [] });
setGameStatus('ready');
syncGameStatus = 'ready';
setIsGameEndDialogOpen(false); setIsGameEndDialogOpen(false);
// todo: proceed next game option
}; };
const startGame = async () => { const startGame = async () => {
// start game // start game
setGameStatus('playing'); setGameStatus('playing');
syncGameStatus = 'playing';
const newGameState = deepCopy(gameState); const newGameState = deepCopy(gameState);
// 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;
@ -475,11 +520,11 @@ function PvEDoudizhuDemoView() {
}; };
useEffect(() => { useEffect(() => {
if (gameStatus === 'playing') gameStateTimer(); if (syncGameStatus === 'playing') gameStateTimer();
}, [considerationTime]); }, [considerationTime]);
useEffect(() => { useEffect(() => {
if (gameState.currentPlayer !== null && gameStatus === 'playing') { if (gameState.currentPlayer !== null && syncGameStatus === '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();
@ -609,6 +654,70 @@ function PvEDoudizhuDemoView() {
} }
}; };
const gameSpeedMarks = [
{
value: 0,
label: '0s',
},
{
value: 1,
label: '1s',
},
{
value: 2,
label: '3s',
},
{
value: 3,
label: '5s',
},
{
value: 4,
label: '10s',
},
{
value: 5,
label: '30s',
}
];
const gameSpeedMap = [
{
value: 0,
delay: 0,
},
{
value: 1,
delay: 1000,
},
{
value: 2,
delay: 3000,
},
{
value: 3,
delay: 5000,
},
{
value: 4,
delay: 10000,
},
{
value: 5,
delay: 30000,
}
];
const changeApiPlayerDelay = (newVal) => {
const found = gameSpeedMap.find(element => element.value === newVal);
if (found)
setApiPlayDelay(found.delay);
}
const sliderValueText = (value) => {
return value;
};
return ( return (
<div> <div>
<Dialog <Dialog
@ -625,7 +734,7 @@ function PvEDoudizhuDemoView() {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => handleCloseGameEndDialog()} color="primary" autoFocus> <Button onClick={() => handleCloseGameEndDialog()} color="primary" autoFocus>
OK Play Again
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -668,6 +777,7 @@ function PvEDoudizhuDemoView() {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card); const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return ( return (
<div <div
key={'probability-cards-' + rankText + '-' + suitText}
style={{ fontSize: '1.2em' }} style={{ fontSize: '1.2em' }}
className={`card ${rankClass} full-content ${suitClass}`} className={`card ${rankClass} full-content ${suitClass}`}
> >
@ -692,11 +802,10 @@ function PvEDoudizhuDemoView() {
</div> </div>
)} )}
<Divider /> <Divider />
<div className={'probability-player'} style={{ height: '19px' }}> <div className={'probability-player'} style={{ height: '19px', textAlign: 'center' }}>
{playerInfo.length > 0 && gameState.currentPlayer !== null ? ( {playerInfo.length > 0 && gameState.currentPlayer !== null ? (
<span> <span>
Current Player: {gameState.currentPlayer} |{' '} {['Landlord', 'Landlord Down', 'Landlord Up'][playerInfo[gameState.currentPlayer].douzeroPlayerPosition]}
{playerInfo[gameState.currentPlayer].role}
</span> </span>
) : ( ) : (
<span>Waiting...</span> <span>Waiting...</span>
@ -716,19 +825,6 @@ function PvEDoudizhuDemoView() {
<Layout.Row style={{ height: '51px' }}> <Layout.Row style={{ height: '51px' }}>
<Layout.Col span="6" style={{ height: '51px', lineHeight: '48px' }}> <Layout.Col span="6" style={{ height: '51px', lineHeight: '48px' }}>
<FormGroup style={{ height: '100%' }}> <FormGroup style={{ height: '100%' }}>
<FormControlLabel
style={{ textAlign: 'center', height: '100%', display: 'inline-block' }}
className="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 <FormControlLabel
style={{ textAlign: 'center', height: '100%', display: 'inline-block' }} style={{ textAlign: 'center', height: '100%', display: 'inline-block' }}
className="switch-control" className="switch-control"
@ -743,7 +839,7 @@ function PvEDoudizhuDemoView() {
<Divider orientation="vertical" /> <Divider orientation="vertical" />
</Layout.Col> </Layout.Col>
<Layout.Col <Layout.Col
span="6" span="3"
style={{ height: '51px', lineHeight: '51px', marginLeft: '-1px', marginRight: '-1px' }} style={{ height: '51px', lineHeight: '51px', marginLeft: '-1px', marginRight: '-1px' }}
> >
<div style={{ textAlign: 'center' }}>{`Turn ${gameState.turn}`}</div> <div style={{ textAlign: 'center' }}>{`Turn ${gameState.turn}`}</div>
@ -751,11 +847,24 @@ function PvEDoudizhuDemoView() {
<Layout.Col span="1" style={{ height: '100%', width: '1px' }}> <Layout.Col span="1" style={{ height: '100%', width: '1px' }}>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
</Layout.Col> </Layout.Col>
<Layout.Col <Layout.Col span="15">
span="6" <div>
style={{ height: '51px', lineHeight: '51px', marginLeft: '-1px', marginRight: '-1px' }} <label className={"form-label-left"} style={{width: '140px', lineHeight: '28px', fontSize: '15px'}}>AI Player Delay</label>
> <div style={{"marginLeft": "160px", "marginRight": "10px"}}>
<div style={{ textAlign: 'center' }}>{`Game Status: ${gameStatus}`}</div> <Slider
value={gameSpeedMap.find(element => element.delay === apiPlayDelay).value}
getAriaValueText={sliderValueText}
onChange={(e, newVal)=>{changeApiPlayerDelay(newVal)}}
aria-labelledby="discrete-slider-custom"
step={1}
min={0}
max={5}
track={false}
valueLabelDisplay="off"
marks={gameSpeedMarks}
/>
</div>
</div>
</Layout.Col> </Layout.Col>
</Layout.Row> </Layout.Row>
</Paper> </Paper>