add logic for request api play

This commit is contained in:
Songyi Huang 2021-04-20 23:30:27 -07:00
parent 58526e7fc5
commit 14ce853ce9
3 changed files with 310 additions and 53 deletions

View File

@ -1,3 +1,4 @@
const apiUrl = 'http://127.0.0.1:8000'; const apiUrl = 'http://127.0.0.1:8000';
const douzeroDemoUrl = 'http://127.0.0.1:5000';
export {apiUrl}; export { apiUrl, douzeroDemoUrl };

View File

@ -102,10 +102,140 @@ export function computeHandCardsWidth(num, emWidth) {
export function card2SuiteAndRank(card) { export function card2SuiteAndRank(card) {
if (card === 'BJ') { if (card === 'BJ') {
return {suite: null, rank: 'X'}; return { suite: null, rank: 'X' };
} else if (card === 'RJ') { } else if (card === 'RJ') {
return {suite: null, rank: 'D'}; return { suite: null, rank: 'D' };
} else { } else {
return {suite: card[0], rank: card[1]}; 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;
}

View File

@ -1,8 +1,11 @@
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import axios from 'axios';
import { Layout } from 'element-react'; import { Layout } from 'element-react';
import qs from 'query-string';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { DoudizhuGameBoard } from '../../components/GameBoard'; import { DoudizhuGameBoard } from '../../components/GameBoard';
import { deepCopy, card2SuiteAndRank } from '../../utils'; import { card2SuiteAndRank, deepCopy, isDoudizhuBomb, sortDoudizhuCards } from '../../utils';
import { douzeroDemoUrl } from '../../utils/config';
const initHands = [ const initHands = [
'S2 H2 HK DK HQ CQ DQ CJ S9 H9 D9 C7 S6 H6 C4 D4 S3', 'S2 H2 HK DK HQ CQ DQ CJ S9 H9 D9 C7 S6 H6 C4 D4 S3',
@ -10,47 +13,70 @@ const initHands = [
'RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3 D3', 'RJ BJ D2 SA SK CK SJ HJ DJ CT DT C9 S8 H8 C8 D7 D5 H3 S3 D3',
]; ];
const initConsiderationTime = 2000;
const considerationTimeDeduction = 200;
const mainPlayerId = 0;
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,
},
];
const threeLandlordCards = ['RJ', 'BJ', 'D2'];
let gameStateTimeout = null; let gameStateTimeout = null;
function PvEDoudizhuDemoView() { let gameHistory = [];
const initConsiderationTime = 2000; let bombNum = 0;
const considerationTimeDeduction = 200; let lastMoveLandlord = [];
const mainPlayerId = 0; let lastMoveLandlordDown = [];
let lastMoveLandlordUp = [];
let playedCardsLandlord = [];
let playedCardsLandlordDown = [];
let playedCardsLandlordUp = [];
function PvEDoudizhuDemoView() {
const [considerationTime, setConsiderationTime] = useState(initConsiderationTime); const [considerationTime, setConsiderationTime] = useState(initConsiderationTime);
const [toggleFade, setToggleFade] = useState(''); const [toggleFade, setToggleFade] = useState('');
const [gameStatus, setGameStatus] = useState('ready'); // "ready", "playing", "paused", "over" const [gameStatus, setGameStatus] = useState('ready'); // "ready", "playing", "paused", "over"
const [gameState, setGameState] = useState({ const [gameState, setGameState] = useState({
hands: [[], [], []], hands: [[], [], []],
latestAction: [[], [], []], latestAction: [[], [], []],
currentPlayer: null, // index of current player currentPlayer: null, // index of current player
turn: 0, turn: 0,
}); });
const [selectedCards, setSelectedCards] = useState([]); // user selected hand card const [selectedCards, setSelectedCards] = useState([]); // user selected hand card
const cardStr2Arr = (cardStr) => { const cardStr2Arr = (cardStr) => {
return cardStr === 'pass' || cardStr === '' ? 'pass' : cardStr.split(' '); 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 // todo: generate inital player / hand states
// for test use // for test use
const playerInfo = [
{
id: 0,
index: 0,
role: 'peasant',
},
{
id: 1,
index: 1,
role: 'peasant',
},
{
id: 2,
index: 2,
role: 'landlord',
},
];
function timeout(ms) { function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
@ -70,9 +96,8 @@ function PvEDoudizhuDemoView() {
newHand = currentHand; newHand = currentHand;
newLatestAction = 'pass'; newLatestAction = 'pass';
} else if (rankOnly) { } else if (rankOnly) {
newHand = currentHand.filter(card => { newHand = currentHand.filter((card) => {
if (playingCard.length === 0) if (playingCard.length === 0) return true;
return true;
const { rank } = card2SuiteAndRank(card); const { rank } = card2SuiteAndRank(card);
const idx = playingCard.indexOf(rank); const idx = playingCard.indexOf(rank);
@ -85,9 +110,8 @@ function PvEDoudizhuDemoView() {
}); });
} else { } else {
newLatestAction = playingCard.slice(); newLatestAction = playingCard.slice();
newHand = currentHand.filter(card => { newHand = currentHand.filter((card) => {
if (playingCard.length === 0) if (playingCard.length === 0) return true;
return true;
const idx = playingCard.indexOf(card); const idx = playingCard.indexOf(card);
if (idx >= 0) { if (idx >= 0) {
@ -98,13 +122,33 @@ function PvEDoudizhuDemoView() {
}); });
} }
// update value records for douzero
// debugger;
const newHistoryRecord = newLatestAction === 'pass' ? [] : newLatestAction;
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.latestAction[gameState.currentPlayer] = newLatestAction;
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++;
setGameState(newGameState); setGameState(newGameState);
setToggleFade('fade-in'); setToggleFade('fade-in');
setTimeout(()=> { setTimeout(() => {
setToggleFade(''); setToggleFade('');
}, 200); }, 200);
if (gameStateTimeout) { if (gameStateTimeout) {
@ -115,15 +159,96 @@ function PvEDoudizhuDemoView() {
const requestApiPlay = async () => { const requestApiPlay = async () => {
// mock delayed API play // mock delayed API play
await timeout(1200); // await timeout(1200);
const apiRes = [card2SuiteAndRank(gameState.hands[gameState.currentPlayer][gameState.hands[gameState.currentPlayer].length - 1]).rank]; // const apiRes = [
console.log('mock api res', apiRes, gameStateTimeout); // card2SuiteAndRank(
proceedNextTurn(apiRes); // gameState.hands[gameState.currentPlayer][gameState.hands[gameState.currentPlayer].length - 1],
// ).rank,
// ];
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));
console.log(apiRes.data);
const data = apiRes.data;
if (data.status !== 0) {
if (data.status === -1) {
// todo: check if no legal action can be made
proceedNextTurn([]);
}
console.log(data.status, data.message);
} else {
console.log('api res', data, gameStateTimeout);
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) {
console.log(err);
}
}; };
const handleSelectedCards = (cards) => { const handleSelectedCards = (cards) => {
let newSelectedCards = selectedCards.slice(); let newSelectedCards = selectedCards.slice();
cards.forEach(card => { cards.forEach((card) => {
if (newSelectedCards.indexOf(card) >= 0) { if (newSelectedCards.indexOf(card) >= 0) {
newSelectedCards.splice(newSelectedCards.indexOf(card), 1); newSelectedCards.splice(newSelectedCards.indexOf(card), 1);
} else { } else {
@ -131,7 +256,7 @@ function PvEDoudizhuDemoView() {
} }
}); });
setSelectedCards(newSelectedCards); setSelectedCards(newSelectedCards);
} };
const gameStateTimer = () => { const gameStateTimer = () => {
gameStateTimeout = setTimeout(() => { gameStateTimeout = setTimeout(() => {
@ -160,7 +285,7 @@ function PvEDoudizhuDemoView() {
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;
newGameState.hands = initHands.map((element) => cardStr2Arr(element)); newGameState.hands = initHands.map((element) => sortDoudizhuCards(cardStr2Arr(element)));
setGameState(newGameState); setGameState(newGameState);
gameStateTimer(); gameStateTimer();
}, []); }, []);
@ -169,6 +294,7 @@ function PvEDoudizhuDemoView() {
if (gameState.currentPlayer) { if (gameState.currentPlayer) {
// 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) {
// debugger;
requestApiPlay(); requestApiPlay();
} }
} }
@ -179,7 +305,7 @@ function PvEDoudizhuDemoView() {
}; };
const handleMainPlayerAct = (type) => { const handleMainPlayerAct = (type) => {
switch(type) { switch (type) {
case 'play': { case 'play': {
proceedNextTurn(selectedCards, false); proceedNextTurn(selectedCards, false);
break; break;
@ -194,7 +320,7 @@ function PvEDoudizhuDemoView() {
break; break;
} }
} }
} };
return ( return (
<div> <div>