diff --git a/src/utils/config.js b/src/utils/config.js index 40d5cf3..117b76b 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -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 }; diff --git a/src/utils/index.js b/src/utils/index.js index c4b577c..90ecb6b 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -102,10 +102,140 @@ export function computeHandCardsWidth(num, emWidth) { export function card2SuiteAndRank(card) { if (card === 'BJ') { - return {suite: null, rank: 'X'}; + return { suite: null, rank: 'X' }; } else if (card === 'RJ') { - return {suite: null, rank: 'D'}; + return { suite: null, rank: 'D' }; } else { - return {suite: card[0], rank: card[1]}; + return { suite: card[0], rank: card[1] }; } -} \ No newline at end of file +} + +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; +} diff --git a/src/view/PvEView/PvEDoudizhuDemoView.js b/src/view/PvEView/PvEDoudizhuDemoView.js index 4e40014..597aa18 100644 --- a/src/view/PvEView/PvEDoudizhuDemoView.js +++ b/src/view/PvEView/PvEDoudizhuDemoView.js @@ -1,8 +1,11 @@ import Paper from '@material-ui/core/Paper'; +import axios from 'axios'; import { Layout } from 'element-react'; +import qs from 'query-string'; import React, { useEffect, useState } from 'react'; import { DoudizhuGameBoard } from '../../components/GameBoard'; -import { deepCopy, card2SuiteAndRank } from '../../utils'; +import { card2SuiteAndRank, deepCopy, isDoudizhuBomb, sortDoudizhuCards } from '../../utils'; +import { douzeroDemoUrl } from '../../utils/config'; const initHands = [ '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', ]; +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; -function PvEDoudizhuDemoView() { - const initConsiderationTime = 2000; - const considerationTimeDeduction = 200; - const mainPlayerId = 0; +let gameHistory = []; +let bombNum = 0; +let lastMoveLandlord = []; +let lastMoveLandlordDown = []; +let lastMoveLandlordUp = []; +let playedCardsLandlord = []; +let playedCardsLandlordDown = []; +let playedCardsLandlordUp = []; +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 + currentPlayer: null, // index of current player turn: 0, }); - const [selectedCards, setSelectedCards] = useState([]); // user selected hand card + const [selectedCards, setSelectedCards] = useState([]); // user selected hand card 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 - const playerInfo = [ - { - id: 0, - index: 0, - role: 'peasant', - }, - { - id: 1, - index: 1, - role: 'peasant', - }, - { - id: 2, - index: 2, - role: 'landlord', - }, - ]; function timeout(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -58,22 +84,21 @@ function PvEDoudizhuDemoView() { const proceedNextTurn = (playingCard, rankOnly = true) => { setToggleFade('fade-out'); - + let newGameState = deepCopy(gameState); - - // todo: take played card out from hand, and generate playing cards with suite + + // todo: 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; - + newHand = currentHand.filter((card) => { + if (playingCard.length === 0) return true; + const { rank } = card2SuiteAndRank(card); const idx = playingCard.indexOf(rank); if (idx >= 0) { @@ -85,9 +110,8 @@ function PvEDoudizhuDemoView() { }); } else { newLatestAction = playingCard.slice(); - newHand = currentHand.filter(card => { - if (playingCard.length === 0) - return true; + newHand = currentHand.filter((card) => { + if (playingCard.length === 0) return true; const idx = playingCard.indexOf(card); 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.hands[gameState.currentPlayer] = newHand; newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 3; newGameState.turn++; setGameState(newGameState); setToggleFade('fade-in'); - setTimeout(()=> { + setTimeout(() => { setToggleFade(''); }, 200); if (gameStateTimeout) { @@ -115,23 +159,104 @@ function PvEDoudizhuDemoView() { const requestApiPlay = async () => { // mock delayed API play - await timeout(1200); - const apiRes = [card2SuiteAndRank(gameState.hands[gameState.currentPlayer][gameState.hands[gameState.currentPlayer].length - 1]).rank]; - console.log('mock api res', apiRes, gameStateTimeout); - proceedNextTurn(apiRes); + // await timeout(1200); + // const apiRes = [ + // card2SuiteAndRank( + // 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) => { let newSelectedCards = selectedCards.slice(); - cards.forEach(card => { + cards.forEach((card) => { if (newSelectedCards.indexOf(card) >= 0) { newSelectedCards.splice(newSelectedCards.indexOf(card), 1); } else { - newSelectedCards.push(card); + newSelectedCards.push(card); } }); setSelectedCards(newSelectedCards); - } + }; const gameStateTimer = () => { gameStateTimeout = setTimeout(() => { @@ -160,7 +285,7 @@ function PvEDoudizhuDemoView() { 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) => cardStr2Arr(element)); + newGameState.hands = initHands.map((element) => sortDoudizhuCards(cardStr2Arr(element))); setGameState(newGameState); gameStateTimer(); }, []); @@ -169,6 +294,7 @@ function PvEDoudizhuDemoView() { if (gameState.currentPlayer) { // if current player is not user, request for API player if (gameState.currentPlayer !== mainPlayerId) { + // debugger; requestApiPlay(); } } @@ -177,9 +303,9 @@ function PvEDoudizhuDemoView() { const runNewTurn = () => { // gameStateTimer(); }; - + const handleMainPlayerAct = (type) => { - switch(type) { + switch (type) { case 'play': { proceedNextTurn(selectedCards, false); break; @@ -194,7 +320,7 @@ function PvEDoudizhuDemoView() { break; } } - } + }; return (