更新为四人版本

This commit is contained in:
ZaneYork 2021-12-18 15:49:37 +08:00
parent a24ac2590a
commit ed080b3f39
14 changed files with 845 additions and 402 deletions

View File

@ -7,21 +7,36 @@ from collections import Counter
Card2Column = {3: 0, 4: 1, 5: 2, 6: 3, 7: 4, 8: 5, 9: 6, 10: 7, Card2Column = {3: 0, 4: 1, 5: 2, 6: 3, 7: 4, 8: 5, 9: 6, 10: 7,
11: 8, 12: 9, 13: 10, 14: 11, 17: 12, 20: 13, 30: 14} 11: 8, 12: 9, 13: 10, 14: 11, 17: 12, 20: 13, 30: 14}
NumOnes2Array = {0: np.array([0, 0, 0, 0]), NumOnes2Array = {0: np.array([0, 0, 0, 0, 0, 0, 0, 0]),
1: np.array([1, 0, 0, 0]), 1: np.array([1, 0, 0, 0, 0, 0, 0, 0]),
2: np.array([1, 1, 0, 0]), 2: np.array([1, 1, 0, 0, 0, 0, 0, 0]),
3: np.array([1, 1, 1, 0]), 3: np.array([1, 1, 1, 0, 0, 0, 0, 0]),
4: np.array([1, 1, 1, 1])} 4: np.array([1, 1, 1, 1, 0, 0, 0, 0]),
5: np.array([1, 1, 1, 1, 1, 0, 0, 0]),
6: np.array([1, 1, 1, 1, 1, 1, 0, 0]),
7: np.array([1, 1, 1, 1, 1, 1, 1, 0]),
8: np.array([1, 1, 1, 1, 1, 1, 1, 1])}
def _get_one_hot_bomb(bomb_num):
one_hot = np.zeros(15, dtype=np.float32) def _get_one_hot_bomb(bomb_num, use_legacy = False):
one_hot[bomb_num] = 1 """
A utility function to encode the number of bombs
into one-hot representation.
"""
if use_legacy:
one_hot = np.zeros(29)
one_hot[bomb_num[0] + bomb_num[1]] = 1
else:
one_hot = np.zeros(56) # 14 + 15 + 27
one_hot[bomb_num[0]] = 1
one_hot[14 + bomb_num[1]] = 1
one_hot[29 + bomb_num[2]] = 1
return one_hot return one_hot
def _load_model(position, model_dir, use_onnx): def _load_model(position, model_dir, use_onnx):
if not use_onnx or not os.path.isfile(os.path.join(model_dir, position+'.onnx')) : if not use_onnx or not os.path.isfile(os.path.join(model_dir, position+'.onnx')) :
from models import model_dict from models import model_dict_new
model = model_dict[position]() model = model_dict_new[position]()
model_state_dict = model.state_dict() model_state_dict = model.state_dict()
model_path = os.path.join(model_dir, position+'.ckpt') model_path = os.path.join(model_dir, position+'.ckpt')
if torch.cuda.is_available(): if torch.cuda.is_available():
@ -58,8 +73,15 @@ def _load_model(position, model_dir, use_onnx):
model = onnxruntime.InferenceSession(os.path.join(model_dir, position+'.onnx')) model = onnxruntime.InferenceSession(os.path.join(model_dir, position+'.onnx'))
return model return model
def _process_action_seq(sequence, length=15): def _process_action_seq(sequence, length=20, new_model=True):
"""
A utility function encoding historical moves. We
encode 20 moves. If there is no 20 moves, we pad
with zeros.
"""
sequence = sequence[-length:].copy() sequence = sequence[-length:].copy()
if new_model:
sequence = sequence[::-1]
if len(sequence) < length: if len(sequence) < length:
empty_sequence = [[] for _ in range(length - len(sequence))] empty_sequence = [[] for _ in range(length - len(sequence))]
empty_sequence.extend(sequence) empty_sequence.extend(sequence)
@ -74,18 +96,22 @@ class DeepAgent:
def cards2array(self, list_cards): def cards2array(self, list_cards):
if len(list_cards) == 0: if len(list_cards) == 0:
return np.zeros(54, dtype=np.float32) return np.zeros(108, dtype=np.int8)
matrix = np.zeros([4, 13], dtype=np.float32) matrix = np.zeros([8, 13], dtype=np.int8)
jokers = np.zeros(2, dtype=np.float32) jokers = np.zeros(4, dtype=np.int8)
counter = Counter(list_cards) counter = Counter(list_cards)
for card, num_times in counter.items(): for card, num_times in counter.items():
if card < 20: if card < 20:
matrix[:, Card2Column[card]] = NumOnes2Array[num_times] matrix[:, Card2Column[card]] = NumOnes2Array[num_times]
elif card == 20: elif card == 20:
jokers[0] = 1 jokers[0] = 1
if num_times == 2:
jokers[1] = 1
elif card == 30: elif card == 30:
jokers[1] = 1 jokers[2] = 1
if num_times == 2:
jokers[3] = 1
return np.concatenate((matrix.flatten('F'), jokers)) return np.concatenate((matrix.flatten('F'), jokers))
def get_one_hot_array(self, num_left_cards, max_num_cards): def get_one_hot_array(self, num_left_cards, max_num_cards):
@ -94,173 +120,146 @@ class DeepAgent:
return one_hot return one_hot
def action_seq_list2array(self, action_seq_list): def action_seq_list2array(self, action_seq_list, new_model=True):
action_seq_array = np.zeros((len(action_seq_list), 54), dtype=np.float32) """
for row, list_cards in enumerate(action_seq_list): A utility function to encode the historical moves.
action_seq_array[row, :] = self.cards2array(list_cards) We encode the historical 20 actions. If there is
action_seq_array = action_seq_array.reshape(5, 162) no 20 actions, we pad the features with 0. Since
three moves is a round in DouDizhu, we concatenate
the representations for each consecutive three moves.
Finally, we obtain a 5x432 matrix, which will be fed
into LSTM for encoding.
"""
if new_model:
# position_map = {"landlord": 0, "landlord_up": 1, "landlord_front": 2, "landlord_down": 3}
action_seq_array = np.ones((len(action_seq_list), 108)) * -1 # Default Value -1 for not using area
for row, list_cards in enumerate(action_seq_list):
if list_cards != []:
action_seq_array[row, :108] = self.cards2array(list_cards)
else:
action_seq_array = np.zeros((len(action_seq_list), 108))
for row, list_cards in enumerate(action_seq_list):
if list_cards != []:
action_seq_array[row, :] = self.cards2array(list_cards)
action_seq_array = action_seq_array.reshape(5, 432)
return action_seq_array return action_seq_array
def act(self, infoset): def get_obs_general(self, infoset, position, use_legacy = False):
player_position = infoset.player_position
num_legal_actions = len(infoset.legal_actions) num_legal_actions = len(infoset.legal_actions)
my_handcards = self.cards2array(infoset.player_hand_cards) my_handcards = self.cards2array(infoset.player_hand_cards)
my_handcards_batch = np.repeat(my_handcards[np.newaxis, :], my_handcards_batch = np.repeat(my_handcards[np.newaxis, :],
num_legal_actions, axis=0) num_legal_actions, axis=0)
other_handcards = self.cards2array(infoset.other_hand_cards) other_handcards = self.cards2array(infoset.other_hand_cards)
other_handcards_batch = np.repeat(other_handcards[np.newaxis, :],
num_legal_actions, axis=0)
my_action_batch = np.zeros(my_handcards_batch.shape, dtype=np.float32) bid_info = np.array(infoset.bid_info).flatten()
bid_info_batch = np.repeat(bid_info[np.newaxis, :],
num_legal_actions, axis=0)
multiply_info = np.array(infoset.multiply_info)
multiply_info_batch = np.repeat(multiply_info[np.newaxis, :],
num_legal_actions, axis=0)
my_action_batch = np.zeros(my_handcards_batch.shape)
for j, action in enumerate(infoset.legal_actions): for j, action in enumerate(infoset.legal_actions):
my_action_batch[j, :] = self.cards2array(action) my_action_batch[j, :] = self.cards2array(action)
last_action = self.cards2array(infoset.rival_move) landlord_num_cards_left = self.get_one_hot_array(
last_action_batch = np.repeat(last_action[np.newaxis, :], infoset.num_cards_left_dict['landlord'], 33)
num_legal_actions, axis=0)
if player_position == 0: landlord_up_num_cards_left = self.get_one_hot_array(
landlord_up_num_cards_left = self.get_one_hot_array( infoset.num_cards_left_dict['landlord_up'], 25)
infoset.num_cards_left[2], 17)
landlord_up_num_cards_left_batch = np.repeat(
landlord_up_num_cards_left[np.newaxis, :],
num_legal_actions, axis=0)
landlord_down_num_cards_left = self.get_one_hot_array( landlord_front_num_cards_left = self.get_one_hot_array(
infoset.num_cards_left[1], 17) infoset.num_cards_left_dict['landlord_front'], 25)
landlord_down_num_cards_left_batch = np.repeat(
landlord_down_num_cards_left[np.newaxis, :],
num_legal_actions, axis=0)
landlord_up_played_cards = self.cards2array( landlord_down_num_cards_left = self.get_one_hot_array(
infoset.played_cards[2]) infoset.num_cards_left_dict['landlord_down'], 25)
landlord_up_played_cards_batch = np.repeat(
landlord_up_played_cards[np.newaxis, :],
num_legal_actions, axis=0)
landlord_down_played_cards = self.cards2array( landlord_played_cards = self.cards2array(
infoset.played_cards[1]) infoset.played_cards[0])
landlord_down_played_cards_batch = np.repeat(
landlord_down_played_cards[np.newaxis, :],
num_legal_actions, axis=0)
bomb_num = _get_one_hot_bomb( landlord_up_played_cards = self.cards2array(
infoset.bomb_num) infoset.played_cards[1])
bomb_num_batch = np.repeat(
bomb_num[np.newaxis, :],
num_legal_actions, axis=0)
x_batch = np.hstack((my_handcards_batch, landlord_front_played_cards = self.cards2array(
other_handcards_batch, infoset.played_cards[2])
last_action_batch,
landlord_up_played_cards_batch, landlord_down_played_cards = self.cards2array(
landlord_down_played_cards_batch, infoset.played_cards[3])
landlord_up_num_cards_left_batch,
landlord_down_num_cards_left_batch, bomb_num = _get_one_hot_bomb(
bomb_num_batch, infoset.bomb_num, use_legacy)
my_action_batch)) bomb_num_batch = np.repeat(
z = self.action_seq_list2array(_process_action_seq( bomb_num[np.newaxis, :],
infoset.card_play_action_seq)) num_legal_actions, axis=0)
z_batch = np.repeat( num_cards_left = np.hstack((
z[np.newaxis, :, :], landlord_num_cards_left, # 33
num_legal_actions, axis=0) landlord_up_num_cards_left, # 25
if self.use_onnx: landlord_front_num_cards_left, # 25
ort_inputs = {'z': z_batch, 'x': x_batch} landlord_down_num_cards_left))
y_pred = self.model.run(None, ort_inputs)[0]
elif torch.cuda.is_available(): if use_legacy:
y_pred = self.model.forward(torch.from_numpy(z_batch).float().cuda(), x_batch = np.hstack((
torch.from_numpy(x_batch).float().cuda()) bid_info_batch, # 20
y_pred = y_pred.cpu().detach().numpy() multiply_info_batch)) # 4
else: x_no_action = np.hstack((
y_pred = self.model.forward(torch.from_numpy(z_batch).float(), bid_info,
torch.from_numpy(x_batch).float()) multiply_info))
y_pred = y_pred.detach().numpy()
else: else:
last_landlord_action = self.cards2array( x_batch = np.hstack((
infoset.last_moves[0]) bomb_num_batch, # 56
last_landlord_action_batch = np.repeat( bid_info_batch, # 20
last_landlord_action[np.newaxis, :], multiply_info_batch)) # 4
num_legal_actions, axis=0) x_no_action = np.hstack((
landlord_num_cards_left = self.get_one_hot_array( bomb_num, # 56
infoset.num_cards_left[0], 20) bid_info,
landlord_num_cards_left_batch = np.repeat( multiply_info))
landlord_num_cards_left[np.newaxis, :], z =np.vstack((
num_legal_actions, axis=0) num_cards_left,
my_handcards, # 108
other_handcards, # 108
# three_landlord_cards, # 108
landlord_played_cards, # 108
landlord_up_played_cards, # 108
landlord_front_played_cards, # 108
landlord_down_played_cards, # 108
self.action_seq_list2array(_process_action_seq(infoset.card_play_action_seq, 32))
))
landlord_played_cards = self.cards2array( _z_batch = np.repeat(
infoset.played_cards[0]) z[np.newaxis, :, :],
landlord_played_cards_batch = np.repeat( num_legal_actions, axis=0)
landlord_played_cards[np.newaxis, :], my_action_batch = my_action_batch[:,np.newaxis,:]
num_legal_actions, axis=0) z_batch = np.zeros([len(_z_batch),40,108],int)
for i in range(0,len(_z_batch)):
z_batch[i] = np.vstack((my_action_batch[i],_z_batch[i]))
obs = {
'position': position,
'x_batch': x_batch.astype(np.float32),
'z_batch': z_batch.astype(np.float32),
'legal_actions': infoset.legal_actions,
'x_no_action': x_no_action.astype(np.int8),
'z': z.astype(np.int8),
}
return obs
if player_position == 2: def act(self, infoset):
last_teammate_action = self.cards2array( obs = self.get_obs_general(infoset, infoset.player_position)
infoset.last_moves[1]) z_batch = obs['z_batch']
last_teammate_action_batch = np.repeat( x_batch = obs['x_batch']
last_teammate_action[np.newaxis, :], if self.use_onnx:
num_legal_actions, axis=0) ort_inputs = {'z': z_batch, 'x': x_batch}
teammate_num_cards_left = self.get_one_hot_array( y_pred = self.model.run(None, ort_inputs)[0]
infoset.num_cards_left[1], 17) elif torch.cuda.is_available():
teammate_num_cards_left_batch = np.repeat( y_pred = self.model.forward(torch.from_numpy(z_batch).float().cuda(),
teammate_num_cards_left[np.newaxis, :], torch.from_numpy(x_batch).float().cuda())
num_legal_actions, axis=0) y_pred = y_pred.cpu().detach().numpy()
else:
teammate_played_cards = self.cards2array( y_pred = self.model.forward(torch.from_numpy(z_batch).float(),
infoset.played_cards[1]) torch.from_numpy(x_batch).float())['values']
teammate_played_cards_batch = np.repeat( y_pred = y_pred.detach().numpy()
teammate_played_cards[np.newaxis, :],
num_legal_actions, axis=0)
else:
last_teammate_action = self.cards2array(
infoset.last_moves[2])
last_teammate_action_batch = np.repeat(
last_teammate_action[np.newaxis, :],
num_legal_actions, axis=0)
teammate_num_cards_left = self.get_one_hot_array(
infoset.num_cards_left[2], 17)
teammate_num_cards_left_batch = np.repeat(
teammate_num_cards_left[np.newaxis, :],
num_legal_actions, axis=0)
teammate_played_cards = self.cards2array(
infoset.played_cards[2])
teammate_played_cards_batch = np.repeat(
teammate_played_cards[np.newaxis, :],
num_legal_actions, axis=0)
bomb_num = _get_one_hot_bomb(
infoset.bomb_num)
bomb_num_batch = np.repeat(
bomb_num[np.newaxis, :],
num_legal_actions, axis=0)
x_batch = np.hstack((my_handcards_batch,
other_handcards_batch,
landlord_played_cards_batch,
teammate_played_cards_batch,
last_action_batch,
last_landlord_action_batch,
last_teammate_action_batch,
landlord_num_cards_left_batch,
teammate_num_cards_left_batch,
bomb_num_batch,
my_action_batch))
z = self.action_seq_list2array(_process_action_seq(infoset.card_play_action_seq))
z_batch = np.repeat(
z[np.newaxis, :, :],
num_legal_actions, axis=0)
if self.use_onnx:
ort_inputs = {'z': z_batch, 'x': x_batch}
y_pred = self.model.run(None, ort_inputs)[0]
elif torch.cuda.is_available():
y_pred = self.model.forward(torch.from_numpy(z_batch).float().cuda(),
torch.from_numpy(x_batch).float().cuda())
y_pred = y_pred.cpu().detach().numpy()
else:
y_pred = self.model.forward(torch.from_numpy(z_batch).float(),
torch.from_numpy(x_batch).float())
y_pred = y_pred.detach().numpy()
y_pred = y_pred.flatten() y_pred = y_pred.flatten()

View File

@ -1,5 +1,6 @@
import torch import torch
from torch import nn from torch import nn
import torch.nn.functional as F
class LandlordLstmModel(nn.Module): class LandlordLstmModel(nn.Module):
def __init__(self): def __init__(self):
@ -61,7 +62,137 @@ class FarmerLstmModel(nn.Module):
x = self.dense6(x) x = self.dense6(x)
return x return x
# 用于ResNet18和34的残差块用的是2个3x3的卷积
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, in_planes, planes, stride=1):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv1d(in_planes, planes, kernel_size=(3,),
stride=(stride,), padding=1, bias=False)
self.bn1 = nn.BatchNorm1d(planes)
self.conv2 = nn.Conv1d(planes, planes, kernel_size=(3,),
stride=(1,), padding=1, bias=False)
self.bn2 = nn.BatchNorm1d(planes)
self.shortcut = nn.Sequential()
# 经过处理后的x要与x的维度相同(尺寸和深度)
# 如果不相同,需要添加卷积+BN来变换为同一维度
if stride != 1 or in_planes != self.expansion * planes:
self.shortcut = nn.Sequential(
nn.Conv1d(in_planes, self.expansion * planes,
kernel_size=(1,), stride=(stride,), bias=False),
nn.BatchNorm1d(self.expansion * planes)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x)
out = F.relu(out)
return out
class GeneralModel(nn.Module):
def __init__(self):
super().__init__()
self.in_planes = 80
#input 1*108*41
self.conv1 = nn.Conv1d(40, 80, kernel_size=(3,),
stride=(2,), padding=1, bias=False) #1*108*80
self.bn1 = nn.BatchNorm1d(80)
self.layer1 = self._make_layer(BasicBlock, 80, 2, stride=2)#1*27*80
self.layer2 = self._make_layer(BasicBlock, 160, 2, stride=2)#1*14*160
self.layer3 = self._make_layer(BasicBlock, 320, 2, stride=2)#1*7*320
self.layer4 = self._make_layer(BasicBlock, 640, 2, stride=2)#1*4*640
# self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.linear1 = nn.Linear(640 * BasicBlock.expansion * 4 + 80, 2048)
self.linear2 = nn.Linear(2048, 1024)
self.linear3 = nn.Linear(1024, 512)
self.linear4 = nn.Linear(512, 256)
self.linear5 = nn.Linear(256, 1)
def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1)
layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
def get_onnx_params(self):
return {
'args': (
torch.tensor(np.zeros([1, 40, 108]), dtype=torch.float32, device='cuda:0'),
torch.tensor(np.zeros((1, 80)), dtype=torch.float32, device='cuda:0')
),
'input_names': ['z_batch','x_batch'],
'output_names': ['values'],
'dynamic_axes': {
'z_batch': {
0: "legal_actions"
},
'x_batch': {
0: "legal_actions"
}
}
}
def forward(self, z, x):
out = F.relu(self.bn1(self.conv1(z)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = out.flatten(1,2)
out = torch.cat([x,out], dim=-1)
out = F.leaky_relu_(self.linear1(out))
out = F.leaky_relu_(self.linear2(out))
out = F.leaky_relu_(self.linear3(out))
out = F.leaky_relu_(self.linear4(out))
out = F.leaky_relu_(self.linear5(out))
return dict(values=out)
class BidModel(nn.Module):
def __init__(self):
super().__init__()
self.dense1 = nn.Linear(208, 512)
self.dense2 = nn.Linear(512, 512)
self.dense3 = nn.Linear(512, 512)
self.dense4 = nn.Linear(512, 512)
self.dense5 = nn.Linear(512, 512)
self.dense6 = nn.Linear(512, 1)
def forward(self, z, x):
x = self.dense1(x)
x = F.leaky_relu(x)
# x = F.relu(x)
x = self.dense2(x)
x = F.leaky_relu(x)
# x = F.relu(x)
x = self.dense3(x)
x = F.leaky_relu(x)
# x = F.relu(x)
x = self.dense4(x)
x = F.leaky_relu(x)
# x = F.relu(x)
x = self.dense5(x)
# x = F.relu(x)
x = F.leaky_relu(x)
x = self.dense6(x)
return dict(values=x)
model_dict = {} model_dict = {}
model_dict['landlord'] = LandlordLstmModel model_dict['landlord'] = LandlordLstmModel
model_dict['landlord_up'] = FarmerLstmModel model_dict['landlord_up'] = FarmerLstmModel
model_dict['landlord_down'] = FarmerLstmModel model_dict['landlord_down'] = FarmerLstmModel
model_dict_new = {}
model_dict_new['landlord'] = GeneralModel
model_dict_new['landlord_up'] = GeneralModel
model_dict_new['landlord_front'] = GeneralModel
model_dict_new['landlord_down'] = GeneralModel
model_dict_new['bidding'] = BidModel

View File

@ -37,7 +37,7 @@ RealCard2EnvCard = {'3': '3', '4': '4', '5': '5', '6': '6', '7': '7',
pretrained_dir = 'pretrained/dmc_pretrained' pretrained_dir = 'pretrained/dmc_pretrained'
device = torch.device('cpu') device = torch.device('cpu')
players = [] players = []
for i in range(3): for i in range(4):
model_path = os.path.join(pretrained_dir, str(i)+'.pth') model_path = os.path.join(pretrained_dir, str(i)+'.pth')
agent = torch.load(model_path, map_location=device) agent = torch.load(model_path, map_location=device)
agent.set_device(device) agent.set_device(device)
@ -50,33 +50,38 @@ def predict():
try: try:
# Player postion # Player postion
player_position = request.form.get('player_position') player_position = request.form.get('player_position')
if player_position not in ['0', '1', '2']: if player_position not in ['0', '1', '2', '3']:
return jsonify({'status': 1, 'message': 'player_position must be 0, 1, or 2'}) return jsonify({'status': 1, 'message': 'player_position must be 0, 1, 2 or 3'})
player_position = int(player_position) player_position = int(player_position)
# Player hand cards # Player hand cards
player_hand_cards = ''.join( player_hand_cards = ''.join(
[RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')]) [RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')])
if player_position == 0: if player_position == 0:
if len(player_hand_cards) < 1 or len(player_hand_cards) > 20: if len(player_hand_cards) < 1 or len(player_hand_cards) > 33:
return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-20'}) return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-33'})
else: else:
if len(player_hand_cards) < 1 or len(player_hand_cards) > 17: if len(player_hand_cards) < 1 or len(player_hand_cards) > 25:
return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-17'}) return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-25'})
# Number cards left # Number cards left
num_cards_left = [int(request.form.get('num_cards_left_landlord')), int(request.form.get( num_cards_left = [
'num_cards_left_landlord_down')), int(request.form.get('num_cards_left_landlord_up'))] int(request.form.get('num_cards_left_landlord')),
int(request.form.get('num_cards_left_landlord_down')),
int(request.form.get('num_cards_left_landlord_front')),
int(request.form.get('num_cards_left_landlord_up'))
]
if num_cards_left[player_position] != len(player_hand_cards): if num_cards_left[player_position] != len(player_hand_cards):
return jsonify({'status': 4, 'message': 'the number of cards left do not align with hand cards'}) return jsonify({'status': 4, 'message': 'the number of cards left do not align with hand cards'})
if num_cards_left[0] < 0 or num_cards_left[1] < 0 or num_cards_left[2] < 0 or num_cards_left[0] > 20 or num_cards_left[1] > 17 or num_cards_left[2] > 17: if num_cards_left[0] < 0 or num_cards_left[1] < 0 or num_cards_left[2] < 0 or num_cards_left[3] < 0 \
or num_cards_left[0] > 33 or num_cards_left[1] > 25 or num_cards_left[2] > 25 or num_cards_left[3] > 25:
return jsonify({'status': 5, 'message': 'the number of cards left not in range'}) return jsonify({'status': 5, 'message': 'the number of cards left not in range'})
# Three landlord cards # Three landlord cards
three_landlord_cards = ''.join( # three_landlord_cards = ''.join(
[RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')]) # [RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')])
if len(three_landlord_cards) < 0 or len(three_landlord_cards) > 3: # if len(three_landlord_cards) < 0 or len(three_landlord_cards) > 3:
return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'}) # return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'})
# Card play sequence # Card play sequence
if request.form.get('card_play_action_seq') == '': if request.form.get('card_play_action_seq') == '':
@ -89,7 +94,7 @@ def predict():
tmp_seq[i] = 'pass' tmp_seq[i] = 'pass'
card_play_action_seq = [] card_play_action_seq = []
for i in range(len(tmp_seq)): for i in range(len(tmp_seq)):
card_play_action_seq.append((i % 3, tmp_seq[i])) card_play_action_seq.append((i % 4, tmp_seq[i]))
# Other hand cards # Other hand cards
other_hand_cards = ''.join( other_hand_cards = ''.join(
@ -99,7 +104,7 @@ def predict():
# Played cards # Played cards
played_cards = [] played_cards = []
for field in ['played_cards_landlord', 'played_cards_landlord_down', 'played_cards_landlord_up']: for field in ['played_cards_landlord', 'played_cards_landlord_down', 'played_cards_landlord_front', 'played_cards_landlord_up']:
played_cards.append( played_cards.append(
''.join([RealCard2EnvCard[c] for c in request.form.get(field)])) ''.join([RealCard2EnvCard[c] for c in request.form.get(field)]))
@ -110,7 +115,7 @@ def predict():
state['num_cards_left'] = num_cards_left state['num_cards_left'] = num_cards_left
state['others_hand'] = other_hand_cards state['others_hand'] = other_hand_cards
state['played_cards'] = played_cards state['played_cards'] = played_cards
state['seen_cards'] = three_landlord_cards # state['seen_cards'] = three_landlord_cards
state['self'] = player_position state['self'] = player_position
state['trace'] = card_play_action_seq state['trace'] = card_play_action_seq
@ -118,7 +123,10 @@ def predict():
rival_move = 'pass' rival_move = 'pass'
if len(card_play_action_seq) != 0: if len(card_play_action_seq) != 0:
if card_play_action_seq[-1][1] == 'pass': if card_play_action_seq[-1][1] == 'pass':
rival_move = card_play_action_seq[-2][1] if card_play_action_seq[-2][1] == 'pass':
rival_move = card_play_action_seq[-3][1]
else:
rival_move = card_play_action_seq[-2][1]
else: else:
rival_move = card_play_action_seq[-1][1] rival_move = card_play_action_seq[-1][1]
if rival_move == 'pass': if rival_move == 'pass':
@ -300,16 +308,33 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move):
moves = ms.filter_type_3_triple(all_moves, rival_move) moves = ms.filter_type_3_triple(all_moves, rival_move)
elif rival_move_type == md.TYPE_4_BOMB: elif rival_move_type == md.TYPE_4_BOMB:
all_moves = mg.gen_type_4_bomb() + mg.gen_type_5_king_bomb() all_moves = mg.gen_type_4_bomb(4)
moves = ms.filter_type_4_bomb(all_moves, rival_move) moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(5) + mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB5:
all_moves = mg.gen_type_4_bomb(5)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB6:
all_moves = mg.gen_type_4_bomb(6)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB7:
all_moves = mg.gen_type_4_bomb(7)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB8:
all_moves = mg.gen_type_4_bomb(8)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_5_KING_BOMB: elif rival_move_type == md.TYPE_5_KING_BOMB:
moves = [] moves = []
elif rival_move_type == md.TYPE_6_3_1:
all_moves = mg.gen_type_6_3_1()
moves = ms.filter_type_6_3_1(all_moves, rival_move)
elif rival_move_type == md.TYPE_7_3_2: elif rival_move_type == md.TYPE_7_3_2:
all_moves = mg.gen_type_7_3_2() all_moves = mg.gen_type_7_3_2()
moves = ms.filter_type_7_3_2(all_moves, rival_move) moves = ms.filter_type_7_3_2(all_moves, rival_move)
@ -326,25 +351,12 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move):
all_moves = mg.gen_type_10_serial_triple(repeat_num=rival_move_len) all_moves = mg.gen_type_10_serial_triple(repeat_num=rival_move_len)
moves = ms.filter_type_10_serial_triple(all_moves, rival_move) moves = ms.filter_type_10_serial_triple(all_moves, rival_move)
elif rival_move_type == md.TYPE_11_SERIAL_3_1:
all_moves = mg.gen_type_11_serial_3_1(repeat_num=rival_move_len)
moves = ms.filter_type_11_serial_3_1(all_moves, rival_move)
elif rival_move_type == md.TYPE_12_SERIAL_3_2: elif rival_move_type == md.TYPE_12_SERIAL_3_2:
all_moves = mg.gen_type_12_serial_3_2(repeat_num=rival_move_len) all_moves = mg.gen_type_12_serial_3_2(repeat_num=rival_move_len)
moves = ms.filter_type_12_serial_3_2(all_moves, rival_move) moves = ms.filter_type_12_serial_3_2(all_moves, rival_move)
elif rival_move_type == md.TYPE_13_4_2: if rival_move_type != md.TYPE_0_PASS and rival_move_type < md.TYPE_4_BOMB:
all_moves = mg.gen_type_13_4_2() moves = moves + mg.gen_type_4_bomb(4) + mg.gen_type_4_bomb(5) + mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
moves = ms.filter_type_13_4_2(all_moves, rival_move)
elif rival_move_type == md.TYPE_14_4_22:
all_moves = mg.gen_type_14_4_22()
moves = ms.filter_type_14_4_22(all_moves, rival_move)
if rival_move_type not in [md.TYPE_0_PASS,
md.TYPE_4_BOMB, md.TYPE_5_KING_BOMB]:
moves = moves + mg.gen_type_4_bomb() + mg.gen_type_5_king_bomb()
if len(rival_move) != 0: # rival_move is not 'pass' if len(rival_move) != 0: # rival_move is not 'pass'
moves = moves + [[]] moves = moves + [[]]
@ -355,37 +367,39 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move):
moves.sort() moves.sort()
moves = list(move for move, _ in itertools.groupby(moves)) moves = list(move for move, _ in itertools.groupby(moves))
# Remove Quad with black and red joker
for i in [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17]:
illegal_move = [i]*4 + [20, 30]
if illegal_move in moves:
moves.remove(illegal_move)
return moves return moves
Card2Column = {'3': 0, '4': 1, '5': 2, '6': 3, '7': 4, '8': 5, '9': 6, 'T': 7, Card2Column = {'3': 0, '4': 1, '5': 2, '6': 3, '7': 4, '8': 5, '9': 6, 'T': 7,
'J': 8, 'Q': 9, 'K': 10, 'A': 11, '2': 12} 'J': 8, 'Q': 9, 'K': 10, 'A': 11, '2': 12}
NumOnes2Array = {0: np.array([0, 0, 0, 0]), NumOnes2Array = {0: np.array([0, 0, 0, 0, 0, 0, 0, 0]),
1: np.array([1, 0, 0, 0]), 1: np.array([1, 0, 0, 0, 0, 0, 0, 0]),
2: np.array([1, 1, 0, 0]), 2: np.array([1, 1, 0, 0, 0, 0, 0, 0]),
3: np.array([1, 1, 1, 0]), 3: np.array([1, 1, 1, 0, 0, 0, 0, 0]),
4: np.array([1, 1, 1, 1])} 4: np.array([1, 1, 1, 1, 0, 0, 0, 0]),
5: np.array([1, 1, 1, 1, 1, 0, 0, 0]),
6: np.array([1, 1, 1, 1, 1, 1, 0, 0]),
7: np.array([1, 1, 1, 1, 1, 1, 1, 0]),
8: np.array([1, 1, 1, 1, 1, 1, 1, 1])}
def _cards2array(cards): def _cards2array(cards):
if cards == 'pass': if cards == 'pass':
return np.zeros(54, dtype=np.int8) return np.zeros(108, dtype=np.int8)
matrix = np.zeros([4, 13], dtype=np.int8) matrix = np.zeros([4, 13], dtype=np.int8)
jokers = np.zeros(2, dtype=np.int8) jokers = np.zeros(4, dtype=np.int8)
counter = Counter(cards) counter = Counter(cards)
for card, num_times in counter.items(): for card, num_times in counter.items():
if card == 'B': if card == 'B':
jokers[0] = 1 jokers[0] = 1
if num_times == 2:
jokers[1] = 1
elif card == 'R': elif card == 'R':
jokers[1] = 1 jokers[2] = 1
if num_times == 2:
jokers[3] = 1
else: else:
matrix[:, Card2Column[card]] = NumOnes2Array[num_times] matrix[:, Card2Column[card]] = NumOnes2Array[num_times]
return np.concatenate((matrix.flatten('F'), jokers)) return np.concatenate((matrix.flatten('F'), jokers))
@ -399,10 +413,10 @@ def _get_one_hot_array(num_left_cards, max_num_cards):
def _action_seq2array(action_seq_list): def _action_seq2array(action_seq_list):
action_seq_array = np.zeros((len(action_seq_list), 54), np.int8) action_seq_array = np.ones((len(action_seq_list), 108)) * -1 # Default Value -1 for not using area
for row, cards in enumerate(action_seq_list): for row, list_cards in enumerate(action_seq_list):
action_seq_array[row, :] = _cards2array(cards) if list_cards != []:
action_seq_array = action_seq_array.flatten() action_seq_array[row, :108] = _cards2array(list_cards[1])
return action_seq_array return action_seq_array

View File

@ -20,8 +20,8 @@ RealCard2EnvCard = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
pretrained_dir = 'pretrained/douzero_pretrained' pretrained_dir = 'pretrained/douzero_pretrained'
players = [] players = []
for position in ['landlord', 'landlord_down', 'landlord_up']: for position in ['landlord', 'landlord_down', 'landlord_front', 'landlord_up']:
players.append(DeepAgent(position, pretrained_dir, use_onnx=True)) players.append(DeepAgent(position, pretrained_dir, use_onnx=False))
@app.route('/predict', methods=['POST']) @app.route('/predict', methods=['POST'])
def predict(): def predict():
@ -29,30 +29,36 @@ def predict():
try: try:
# Player postion # Player postion
player_position = request.form.get('player_position') player_position = request.form.get('player_position')
if player_position not in ['0', '1', '2']: if player_position not in ['0', '1', '2', '3']:
return jsonify({'status': 1, 'message': 'player_position must be 0, 1, or 2'}) return jsonify({'status': 1, 'message': 'player_position must be 0, 1, 2 or 3'})
player_position = int(player_position) player_position = int(player_position)
# Player hand cards # Player hand cards
player_hand_cards = [RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')] player_hand_cards = [RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')]
if player_position == 0: if player_position == 0:
if len(player_hand_cards) < 1 or len(player_hand_cards) > 20: if len(player_hand_cards) < 1 or len(player_hand_cards) > 33:
return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-20'}) return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-33'})
else: else:
if len(player_hand_cards) < 1 or len(player_hand_cards) > 17: if len(player_hand_cards) < 1 or len(player_hand_cards) > 25:
return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-17'}) return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-25'})
# Number cards left # Number cards left
num_cards_left = [int(request.form.get('num_cards_left_landlord')), int(request.form.get('num_cards_left_landlord_down')), int(request.form.get('num_cards_left_landlord_up'))] num_cards_left = [
int(request.form.get('num_cards_left_landlord')),
int(request.form.get('num_cards_left_landlord_down')),
int(request.form.get('num_cards_left_landlord_front')),
int(request.form.get('num_cards_left_landlord_up'))
]
if num_cards_left[player_position] != len(player_hand_cards): if num_cards_left[player_position] != len(player_hand_cards):
return jsonify({'status': 4, 'message': 'the number of cards left do not align with hand cards'}) return jsonify({'status': 4, 'message': 'the number of cards left do not align with hand cards'})
if num_cards_left[0] < 0 or num_cards_left[1] < 0 or num_cards_left[2] < 0 or num_cards_left[0] > 20 or num_cards_left[1] > 17 or num_cards_left[2] > 17: if num_cards_left[0] < 0 or num_cards_left[1] < 0 or num_cards_left[2] < 0 or num_cards_left[3] < 0 \
or num_cards_left[0] > 33 or num_cards_left[1] > 25 or num_cards_left[2] > 25 or num_cards_left[2] > 25:
return jsonify({'status': 5, 'message': 'the number of cards left not in range'}) return jsonify({'status': 5, 'message': 'the number of cards left not in range'})
# Three landlord cards # Three landlord cards
three_landlord_cards = [RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')] # three_landlord_cards = [RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')]
if len(three_landlord_cards) < 0 or len(three_landlord_cards) > 3: # if len(three_landlord_cards) < 0 or len(three_landlord_cards) > 3:
return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'}) # return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'})
# Card play sequence # Card play sequence
if request.form.get('card_play_action_seq') == '': if request.form.get('card_play_action_seq') == '':
@ -67,34 +73,47 @@ def predict():
# Last moves # Last moves
last_moves = [] last_moves = []
for field in ['last_move_landlord', 'last_move_landlord_down', 'last_move_landlord_up']: for field in ['last_move_landlord', 'last_move_landlord_down', 'last_move_landlord_front', 'last_move_landlord_up']:
last_moves.append([RealCard2EnvCard[c] for c in request.form.get(field)]) last_moves.append([RealCard2EnvCard[c] for c in request.form.get(field)])
# Played cards # Played cards
played_cards = [] played_cards = []
for field in ['played_cards_landlord', 'played_cards_landlord_down', 'played_cards_landlord_up']: for field in ['played_cards_landlord', 'played_cards_landlord_down', 'played_cards_landlord_front', 'played_cards_landlord_up']:
played_cards.append([RealCard2EnvCard[c] for c in request.form.get(field)]) played_cards.append([RealCard2EnvCard[c] for c in request.form.get(field)])
# Bomb Num # Bomb Num
bomb_num = int(request.form.get('bomb_num')) bomb_num = request.form.get('bomb_num')
# InfoSet # InfoSet
info_set = InfoSet() info_set = InfoSet()
info_set.player_position = player_position info_set.player_position = player_position
info_set.player_hand_cards = player_hand_cards info_set.player_hand_cards = player_hand_cards
info_set.num_cards_left = num_cards_left info_set.num_cards_left = num_cards_left
info_set.three_landlord_cards = three_landlord_cards # info_set.three_landlord_cards = three_landlord_cards
info_set.card_play_action_seq = card_play_action_seq info_set.card_play_action_seq = card_play_action_seq
info_set.other_hand_cards = other_hand_cards info_set.other_hand_cards = other_hand_cards
info_set.last_moves = last_moves info_set.last_moves = last_moves
info_set.played_cards = played_cards info_set.played_cards = played_cards
info_set.bomb_num = bomb_num info_set.bomb_num = [int(x) for x in str.split(bomb_num, ',')]
info_set.bid_info = [[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1],
[-1, -1, -1, -1]]
info_set.multiply_info = [1, 0, 0, 0]
info_set.num_cards_left_dict['landlord'] = num_cards_left[0]
info_set.num_cards_left_dict['landlord_down'] = num_cards_left[1]
info_set.num_cards_left_dict['landlord_front'] = num_cards_left[2]
info_set.num_cards_left_dict['landlord_up'] = num_cards_left[3]
# Get rival move and legal_actions # Get rival move and legal_actions
rival_move = [] rival_move = []
if len(card_play_action_seq) != 0: if len(card_play_action_seq) != 0:
if len(card_play_action_seq[-1]) == 0: if len(card_play_action_seq[-1]) == 0:
rival_move = card_play_action_seq[-2] if len(card_play_action_seq[-2]) == 0:
rival_move = card_play_action_seq[-3]
else:
rival_move = card_play_action_seq[-2]
else: else:
rival_move = card_play_action_seq[-1] rival_move = card_play_action_seq[-1]
info_set.rival_move = rival_move info_set.rival_move = rival_move
@ -154,7 +173,9 @@ class InfoSet(object):
self.player_position = None self.player_position = None
self.player_hand_cards = None self.player_hand_cards = None
self.num_cards_left = None self.num_cards_left = None
self.three_landlord_cards = None self.num_cards_left_dict = {}
self.all_handcards = {}
# self.three_landlord_cards = None
self.card_play_action_seq = None self.card_play_action_seq = None
self.other_hand_cards = None self.other_hand_cards = None
self.legal_actions = None self.legal_actions = None
@ -187,16 +208,33 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move):
moves = ms.filter_type_3_triple(all_moves, rival_move) moves = ms.filter_type_3_triple(all_moves, rival_move)
elif rival_move_type == md.TYPE_4_BOMB: elif rival_move_type == md.TYPE_4_BOMB:
all_moves = mg.gen_type_4_bomb() + mg.gen_type_5_king_bomb() all_moves = mg.gen_type_4_bomb(4)
moves = ms.filter_type_4_bomb(all_moves, rival_move) moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(5) + mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB5:
all_moves = mg.gen_type_4_bomb(5)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB6:
all_moves = mg.gen_type_4_bomb(6)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB7:
all_moves = mg.gen_type_4_bomb(7)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_4_BOMB8:
all_moves = mg.gen_type_4_bomb(8)
moves = ms.filter_type_4_bomb(all_moves, rival_move)
moves += mg.gen_type_5_king_bomb()
elif rival_move_type == md.TYPE_5_KING_BOMB: elif rival_move_type == md.TYPE_5_KING_BOMB:
moves = [] moves = []
elif rival_move_type == md.TYPE_6_3_1:
all_moves = mg.gen_type_6_3_1()
moves = ms.filter_type_6_3_1(all_moves, rival_move)
elif rival_move_type == md.TYPE_7_3_2: elif rival_move_type == md.TYPE_7_3_2:
all_moves = mg.gen_type_7_3_2() all_moves = mg.gen_type_7_3_2()
moves = ms.filter_type_7_3_2(all_moves, rival_move) moves = ms.filter_type_7_3_2(all_moves, rival_move)
@ -213,25 +251,12 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move):
all_moves = mg.gen_type_10_serial_triple(repeat_num=rival_move_len) all_moves = mg.gen_type_10_serial_triple(repeat_num=rival_move_len)
moves = ms.filter_type_10_serial_triple(all_moves, rival_move) moves = ms.filter_type_10_serial_triple(all_moves, rival_move)
elif rival_move_type == md.TYPE_11_SERIAL_3_1:
all_moves = mg.gen_type_11_serial_3_1(repeat_num=rival_move_len)
moves = ms.filter_type_11_serial_3_1(all_moves, rival_move)
elif rival_move_type == md.TYPE_12_SERIAL_3_2: elif rival_move_type == md.TYPE_12_SERIAL_3_2:
all_moves = mg.gen_type_12_serial_3_2(repeat_num=rival_move_len) all_moves = mg.gen_type_12_serial_3_2(repeat_num=rival_move_len)
moves = ms.filter_type_12_serial_3_2(all_moves, rival_move) moves = ms.filter_type_12_serial_3_2(all_moves, rival_move)
elif rival_move_type == md.TYPE_13_4_2: if rival_move_type != md.TYPE_0_PASS and rival_move_type < md.TYPE_4_BOMB:
all_moves = mg.gen_type_13_4_2() moves = moves + mg.gen_type_4_bomb(4) + mg.gen_type_4_bomb(5) + mg.gen_type_4_bomb(6) + mg.gen_type_4_bomb(7) + mg.gen_type_4_bomb(8) + mg.gen_type_5_king_bomb()
moves = ms.filter_type_13_4_2(all_moves, rival_move)
elif rival_move_type == md.TYPE_14_4_22:
all_moves = mg.gen_type_14_4_22()
moves = ms.filter_type_14_4_22(all_moves, rival_move)
if rival_move_type not in [md.TYPE_0_PASS,
md.TYPE_4_BOMB, md.TYPE_5_KING_BOMB]:
moves = moves + mg.gen_type_4_bomb() + mg.gen_type_5_king_bomb()
if len(rival_move) != 0: # rival_move is not 'pass' if len(rival_move) != 0: # rival_move is not 'pass'
moves = moves + [[]] moves = moves + [[]]

View File

@ -24,8 +24,8 @@ def get_move_type(move):
if move_size == 2: if move_size == 2:
if move[0] == move[1]: if move[0] == move[1]:
return {'type': TYPE_2_PAIR, 'rank': move[0]} return {'type': TYPE_2_PAIR, 'rank': move[0]}
elif move == [20, 30]: # Kings # elif move == [20, 30]: # Kings
return {'type': TYPE_5_KING_BOMB} # return {'type': TYPE_5_KING_BOMB}
else: else:
return {'type': TYPE_15_WRONG} return {'type': TYPE_15_WRONG}
@ -39,8 +39,10 @@ def get_move_type(move):
if len(move_dict) == 1: if len(move_dict) == 1:
return {'type': TYPE_4_BOMB, 'rank': move[0]} return {'type': TYPE_4_BOMB, 'rank': move[0]}
elif len(move_dict) == 2: elif len(move_dict) == 2:
if move[0] == move[1] == move[2] or move[1] == move[2] == move[3]: if move[0] == 20 and move[2] == 30: # Kings
return {'type': TYPE_6_3_1, 'rank': move[1]} return {'type': TYPE_5_KING_BOMB}
# if move[0] == move[1] == move[2] or move[1] == move[2] == move[3]:
# return {'type': TYPE_6_3_1, 'rank': move[1]}
else: else:
return {'type': TYPE_15_WRONG} return {'type': TYPE_15_WRONG}
else: else:
@ -50,7 +52,9 @@ def get_move_type(move):
return {'type': TYPE_8_SERIAL_SINGLE, 'rank': move[0], 'len': len(move)} return {'type': TYPE_8_SERIAL_SINGLE, 'rank': move[0], 'len': len(move)}
if move_size == 5: if move_size == 5:
if len(move_dict) == 2: if len(move_dict) == 1:
return {'type': TYPE_4_BOMB5, 'rank': move[0]}
elif len(move_dict) == 2:
return {'type': TYPE_7_3_2, 'rank': move[2]} return {'type': TYPE_7_3_2, 'rank': move[2]}
else: else:
return {'type': TYPE_15_WRONG} return {'type': TYPE_15_WRONG}
@ -60,13 +64,21 @@ def get_move_type(move):
count_dict[n] += 1 count_dict[n] += 1
if move_size == 6: if move_size == 6:
if (len(move_dict) == 2 or len(move_dict) == 3) and count_dict.get(4) == 1 and \ if len(move_dict) == 1:
(count_dict.get(2) == 1 or count_dict.get(1) == 2): return {'type': TYPE_4_BOMB6, 'rank': move[0]}
return {'type': TYPE_13_4_2, 'rank': move[2]} # if (len(move_dict) == 2 or len(move_dict) == 3) and count_dict.get(4) == 1 and \
# (count_dict.get(2) == 1 or count_dict.get(1) == 2):
# return {'type': TYPE_13_4_2, 'rank': move[2]}
if move_size == 8 and (((len(move_dict) == 3 or len(move_dict) == 2) and # if move_size == 8 and (((len(move_dict) == 3 or len(move_dict) == 2) and
(count_dict.get(4) == 1 and count_dict.get(2) == 2)) or count_dict.get(4) == 2): # (count_dict.get(4) == 1 and count_dict.get(2) == 2)) or count_dict.get(4) == 2):
return {'type': TYPE_14_4_22, 'rank': max([c for c, n in move_dict.items() if n == 4])} # return {'type': TYPE_14_4_22, 'rank': max([c for c, n in move_dict.items() if n == 4])}
if move_size == 7 and len(move_dict) == 1:
return {'type': TYPE_4_BOMB7, 'rank': move[0]}
if move_size == 8 and len(move_dict) == 1:
return {'type': TYPE_4_BOMB8, 'rank': move[0]}
mdkeys = sorted(move_dict.keys()) mdkeys = sorted(move_dict.keys())
if len(move_dict) == count_dict.get(2) and is_continuous_seq(mdkeys): if len(move_dict) == count_dict.get(2) and is_continuous_seq(mdkeys):
@ -93,15 +105,15 @@ def get_move_type(move):
serial_3.sort() serial_3.sort()
if is_continuous_seq(serial_3): if is_continuous_seq(serial_3):
if len(serial_3) == len(single)+len(pair)*2: # if len(serial_3) == len(single)+len(pair)*2:
return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3)} # return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3)}
if len(serial_3) == len(pair) and len(move_dict) == len(serial_3) * 2: if len(serial_3) == len(pair) and len(move_dict) == len(serial_3) * 2:
return {'type': TYPE_12_SERIAL_3_2, 'rank': serial_3[0], 'len': len(serial_3)} return {'type': TYPE_12_SERIAL_3_2, 'rank': serial_3[0], 'len': len(serial_3)}
if len(serial_3) == 4: # if len(serial_3) == 4:
if is_continuous_seq(serial_3[1:]): # if is_continuous_seq(serial_3[1:]):
return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[1], 'len': len(serial_3) - 1} # return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[1], 'len': len(serial_3) - 1}
if is_continuous_seq(serial_3[:-1]): # if is_continuous_seq(serial_3[:-1]):
return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3) - 1} # return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3) - 1}
return {'type': TYPE_15_WRONG} return {'type': TYPE_15_WRONG}

View File

@ -3,7 +3,9 @@ import collections
import itertools import itertools
class MovesGener(object): class MovesGener(object):
"""
This is for generating the possible combinations
"""
def __init__(self, cards_list): def __init__(self, cards_list):
self.cards_list = cards_list self.cards_list = cards_list
self.cards_dict = collections.defaultdict(int) self.cards_dict = collections.defaultdict(int)
@ -89,17 +91,17 @@ class MovesGener(object):
self.triple_cards_moves.append([k, k, k]) self.triple_cards_moves.append([k, k, k])
return self.triple_cards_moves return self.triple_cards_moves
def gen_type_4_bomb(self): def gen_type_4_bomb(self, num = 4):
self.bomb_moves = [] self.bomb_moves = []
for k, v in self.cards_dict.items(): for k, v in self.cards_dict.items():
if v == 4: if v == num:
self.bomb_moves.append([k, k, k, k]) self.bomb_moves.append([k] * num)
return self.bomb_moves return self.bomb_moves
def gen_type_5_king_bomb(self): def gen_type_5_king_bomb(self):
self.final_bomb_moves = [] self.final_bomb_moves = []
if 20 in self.cards_list and 30 in self.cards_list: if 20 in self.cards_list and self.cards_dict[20] == 2 and 30 in self.cards_list and self.cards_dict[30] == 2:
self.final_bomb_moves.append([20, 30]) self.final_bomb_moves.append([20, 20, 30, 30])
return self.final_bomb_moves return self.final_bomb_moves
def gen_type_6_3_1(self): def gen_type_6_3_1(self):
@ -203,15 +205,19 @@ class MovesGener(object):
moves.extend(self.gen_type_1_single()) moves.extend(self.gen_type_1_single())
moves.extend(self.gen_type_2_pair()) moves.extend(self.gen_type_2_pair())
moves.extend(self.gen_type_3_triple()) moves.extend(self.gen_type_3_triple())
moves.extend(self.gen_type_4_bomb()) moves.extend(self.gen_type_4_bomb(4))
moves.extend(self.gen_type_4_bomb(5))
moves.extend(self.gen_type_4_bomb(6))
moves.extend(self.gen_type_4_bomb(7))
moves.extend(self.gen_type_4_bomb(8))
moves.extend(self.gen_type_5_king_bomb()) moves.extend(self.gen_type_5_king_bomb())
moves.extend(self.gen_type_6_3_1()) # moves.extend(self.gen_type_6_3_1())
moves.extend(self.gen_type_7_3_2()) moves.extend(self.gen_type_7_3_2())
moves.extend(self.gen_type_8_serial_single()) moves.extend(self.gen_type_8_serial_single())
moves.extend(self.gen_type_9_serial_pair()) moves.extend(self.gen_type_9_serial_pair())
moves.extend(self.gen_type_10_serial_triple()) moves.extend(self.gen_type_10_serial_triple())
moves.extend(self.gen_type_11_serial_3_1()) # moves.extend(self.gen_type_11_serial_3_1())
moves.extend(self.gen_type_12_serial_3_2()) moves.extend(self.gen_type_12_serial_3_2())
moves.extend(self.gen_type_13_4_2()) # moves.extend(self.gen_type_13_4_2())
moves.extend(self.gen_type_14_4_22()) # moves.extend(self.gen_type_14_4_22())
return moves return moves

View File

@ -9,7 +9,6 @@ def common_handle(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_1_single(moves, rival_move): def filter_type_1_single(moves, rival_move):
return common_handle(moves, rival_move) return common_handle(moves, rival_move)
@ -25,7 +24,6 @@ def filter_type_3_triple(moves, rival_move):
def filter_type_4_bomb(moves, rival_move): def filter_type_4_bomb(moves, rival_move):
return common_handle(moves, rival_move) return common_handle(moves, rival_move)
# No need to filter for type_5_king_bomb # No need to filter for type_5_king_bomb
def filter_type_6_3_1(moves, rival_move): def filter_type_6_3_1(moves, rival_move):
@ -39,7 +37,6 @@ def filter_type_6_3_1(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_7_3_2(moves, rival_move): def filter_type_7_3_2(moves, rival_move):
rival_move.sort() rival_move.sort()
rival_rank = rival_move[2] rival_rank = rival_move[2]
@ -51,19 +48,15 @@ def filter_type_7_3_2(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_8_serial_single(moves, rival_move): def filter_type_8_serial_single(moves, rival_move):
return common_handle(moves, rival_move) return common_handle(moves, rival_move)
def filter_type_9_serial_pair(moves, rival_move): def filter_type_9_serial_pair(moves, rival_move):
return common_handle(moves, rival_move) return common_handle(moves, rival_move)
def filter_type_10_serial_triple(moves, rival_move): def filter_type_10_serial_triple(moves, rival_move):
return common_handle(moves, rival_move) return common_handle(moves, rival_move)
def filter_type_11_serial_3_1(moves, rival_move): def filter_type_11_serial_3_1(moves, rival_move):
rival = collections.Counter(rival_move) rival = collections.Counter(rival_move)
rival_rank = max([k for k, v in rival.items() if v == 3]) rival_rank = max([k for k, v in rival.items() if v == 3])
@ -75,7 +68,6 @@ def filter_type_11_serial_3_1(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_12_serial_3_2(moves, rival_move): def filter_type_12_serial_3_2(moves, rival_move):
rival = collections.Counter(rival_move) rival = collections.Counter(rival_move)
rival_rank = max([k for k, v in rival.items() if v == 3]) rival_rank = max([k for k, v in rival.items() if v == 3])
@ -87,7 +79,6 @@ def filter_type_12_serial_3_2(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_13_4_2(moves, rival_move): def filter_type_13_4_2(moves, rival_move):
rival_move.sort() rival_move.sort()
rival_rank = rival_move[2] rival_rank = rival_move[2]
@ -99,7 +90,6 @@ def filter_type_13_4_2(moves, rival_move):
new_moves.append(move) new_moves.append(move)
return new_moves return new_moves
def filter_type_14_4_22(moves, rival_move): def filter_type_14_4_22(moves, rival_move):
rival = collections.Counter(rival_move) rival = collections.Counter(rival_move)
rival_rank = my_rank = 0 rival_rank = my_rank = 0

View File

@ -10,17 +10,22 @@ TYPE_0_PASS = 0
TYPE_1_SINGLE = 1 TYPE_1_SINGLE = 1
TYPE_2_PAIR = 2 TYPE_2_PAIR = 2
TYPE_3_TRIPLE = 3 TYPE_3_TRIPLE = 3
TYPE_4_BOMB = 4 TYPE_4_BOMB = 44
TYPE_5_KING_BOMB = 5 TYPE_4_BOMB5 = 45
TYPE_4_BOMB6 = 46
TYPE_4_BOMB7 = 47
TYPE_4_BOMB8 = 48
TYPE_5_KING_BOMB = 50
#TYPE_6_3_1 = 6
TYPE_6_3_1 = 6 TYPE_6_3_1 = 6
TYPE_7_3_2 = 7 TYPE_7_3_2 = 7
TYPE_8_SERIAL_SINGLE = 8 TYPE_8_SERIAL_SINGLE = 8
TYPE_9_SERIAL_PAIR = 9 TYPE_9_SERIAL_PAIR = 9
TYPE_10_SERIAL_TRIPLE = 10 TYPE_10_SERIAL_TRIPLE = 10
TYPE_11_SERIAL_3_1 = 11 # TYPE_11_SERIAL_3_1 = 11
TYPE_12_SERIAL_3_2 = 12 TYPE_12_SERIAL_3_2 = 12
TYPE_13_4_2 = 13 # TYPE_13_4_2 = 13
TYPE_14_4_22 = 14 # TYPE_14_4_22 = 14
TYPE_15_WRONG = 15 TYPE_15_WRONG = 15
# betting round action # betting round action

View File

@ -642,7 +642,6 @@
.playingCards.loose ul.hand li:nth-child(11) { left: 14.0em; } .playingCards.loose ul.hand li:nth-child(11) { left: 14.0em; }
.playingCards.loose ul.hand li:nth-child(12) { left: 15.4em; } .playingCards.loose ul.hand li:nth-child(12) { left: 15.4em; }
.playingCards.loose ul.hand li:nth-child(13) { left: 16.8em; } .playingCards.loose ul.hand li:nth-child(13) { left: 16.8em; }
.playingCards.loose ul.hand li:nth-child(14) { left: 18.2em; } .playingCards.loose ul.hand li:nth-child(14) { left: 18.2em; }
.playingCards.loose ul.hand li:nth-child(15) { left: 19.6em; } .playingCards.loose ul.hand li:nth-child(15) { left: 19.6em; }
.playingCards.loose ul.hand li:nth-child(16) { left: 21em; } .playingCards.loose ul.hand li:nth-child(16) { left: 21em; }
@ -656,6 +655,13 @@
.playingCards.loose ul.hand li:nth-child(24) { left: 32.2em; } .playingCards.loose ul.hand li:nth-child(24) { left: 32.2em; }
.playingCards.loose ul.hand li:nth-child(25) { left: 33.6em; } .playingCards.loose ul.hand li:nth-child(25) { left: 33.6em; }
.playingCards.loose ul.hand li:nth-child(26) { left: 35em; } .playingCards.loose ul.hand li:nth-child(26) { left: 35em; }
.playingCards.loose ul.hand li:nth-child(27) { left: 36.4em; }
.playingCards.loose ul.hand li:nth-child(28) { left: 37.8em; }
.playingCards.loose ul.hand li:nth-child(29) { left: 39.2em; }
.playingCards.loose ul.hand li:nth-child(30) { left: 40.6em; }
.playingCards.loose ul.hand li:nth-child(31) { left: 42em; }
.playingCards.loose ul.hand li:nth-child(32) { left: 43.4em; }
.playingCards.loose ul.hand li:nth-child(33) { left: 44.8em; }
.playingCards ul.hand li:nth-child(1) { left: 0; } .playingCards ul.hand li:nth-child(1) { left: 0; }
.playingCards ul.hand li:nth-child(2) { left: 1.1em; } .playingCards ul.hand li:nth-child(2) { left: 1.1em; }
@ -684,6 +690,13 @@
.playingCards ul.hand li:nth-child(24) { left: 25.3em; } .playingCards ul.hand li:nth-child(24) { left: 25.3em; }
.playingCards ul.hand li:nth-child(25) { left: 26.4em; } .playingCards ul.hand li:nth-child(25) { left: 26.4em; }
.playingCards ul.hand li:nth-child(26) { left: 27.5em; } .playingCards ul.hand li:nth-child(26) { left: 27.5em; }
.playingCards ul.hand li:nth-child(27) { left: 28.4em; }
.playingCards ul.hand li:nth-child(28) { left: 29.3em; }
.playingCards ul.hand li:nth-child(29) { left: 30.2em; }
.playingCards ul.hand li:nth-child(30) { left: 31.1em; }
.playingCards ul.hand li:nth-child(31) { left: 32.0em; }
.playingCards ul.hand li:nth-child(32) { left: 32.9em; }
.playingCards ul.hand li:nth-child(33) { left: 33.8em; }
/* rotate cards if rotateHand option is on */ /* rotate cards if rotateHand option is on */
.playingCards.rotateHand ul.hand li:nth-child(1) { .playingCards.rotateHand ul.hand li:nth-child(1) {
@ -798,3 +811,4 @@
.playingCards ul.deck li:nth-child(30) { left: 58px; bottom: 29px; } .playingCards ul.deck li:nth-child(30) { left: 58px; bottom: 29px; }
.playingCards ul.deck li:nth-child(31) { left: 60px; bottom: 30px; } .playingCards ul.deck li:nth-child(31) { left: 60px; bottom: 30px; }
.playingCards ul.deck li:nth-child(32) { left: 62px; bottom: 31px; } .playingCards ul.deck li:nth-child(32) { left: 62px; bottom: 31px; }
.playingCards ul.deck li:nth-child(33) { left: 64px; bottom: 32px; }

View File

@ -100,8 +100,8 @@
.player-info { .player-info {
font-size: 16px; font-size: 16px;
width: 130px; width: 80px;
height: 130px; height: 80px;
div { div {
text-align: center; text-align: center;
} }
@ -118,30 +118,32 @@
#left-player { #left-player {
position: absolute; position: absolute;
left: 10px; left: 10px;
top: 10px; top: 80px;
.player-main-area { .player-main-area {
width: 300px; width: 300px;
height: 130px; height: 80px;
font-size: 10px; font-size: 10px;
.player-hand-up { .player-hand-up {
position: absolute; position: absolute;
top: 0; top: 0;
margin-left: 150px; margin-left: 10px;
margin-top: 100px;
width: 150px; width: 150px;
} }
.player-hand-down { .player-hand-down {
position: absolute; position: absolute;
top: 50px; top: 50px;
margin-left: 150px; margin-left: 10px;
margin-top: 100px;
width: 150px; width: 150px;
} }
.player-hand-placeholder { .player-hand-placeholder {
width: 150px; width: 150px;
height: 130px; height: 80px;
position: absolute; position: absolute;
top: 0; top: 0;
margin-left: 130px; margin-left: 130px;
@ -150,38 +152,40 @@
.played-card-area { .played-card-area {
position: relative; position: relative;
left: 20px; left: 50px;
top: 20px; top: 80px;
} }
} }
#right-player { #right-player {
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 10px; top: 80px;
.player-main-area { .player-main-area {
width: 300px; width: 300px;
height: 130px; height: 80px;
font-size: 10px; font-size: 10px;
.player-hand-up { .player-hand-up {
position: absolute; position: absolute;
top: 0; top: 0;
margin-right: 150px; margin-top: 100px;
margin-right: 10px;
width: 150px; width: 150px;
} }
.player-hand-down { .player-hand-down {
position: absolute; position: absolute;
top: 50px; top: 50px;
margin-top: 100px;
margin-right: 150px; margin-right: 150px;
width: 150px; width: 150px;
} }
.player-hand-placeholder { .player-hand-placeholder {
width: 150px; width: 150px;
height: 130px; height: 80px;
position: absolute; position: absolute;
top: 0; top: 0;
margin-right: 130px; margin-right: 130px;
@ -192,10 +196,53 @@
} }
} }
.played-card-area {
position: relative;
right: 50px;
top: 80px;
}
}
#top-player {
position: absolute;
left: 80px;
top: 10px;
.player-main-area {
width: 300px;
height: 80px;
font-size: 10px;
.player-hand-up {
position: absolute;
top: 0;
margin-top: 10px;
margin-left: 100px;
width: 150px;
}
.player-hand-down {
position: absolute;
top: 50px;
margin-top: 10px;
margin-left: 100px;
width: 150px;
}
.player-hand-placeholder {
width: 150px;
height: 80px;
position: absolute;
top: 0;
margin-right: 130px;
}
}
.played-card-area { .played-card-area {
position: relative; position: relative;
right: 20px; right: 20px;
top: 20px; top: 10px;
} }
} }
@ -203,29 +250,29 @@
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
left: 50%; left: 50%;
margin-left: -290px; margin-left: -340px;
.player-main-area { .player-main-area {
width: 580px; width: 580px;
height: 130px; height: 80px;
position: relative; position: relative;
.player-hand { .player-hand {
position: absolute; position: absolute;
top: 0; top: 0;
padding-left: 20px; padding-left: 20px;
margin-left: 130px; margin-left: 80px;
width: 450px; width: 450px;
font-size: 14px; font-size: 14px;
} }
.player-hand-placeholder { .player-hand-placeholder {
width: 150px; width: 150px;
height: 130px; height: 80px;
position: absolute; position: absolute;
top: 0; top: 0;
padding-left: 20px; padding-left: 20px;
margin-left: 130px; margin-left: 80px;
} }
} }

View File

@ -163,9 +163,9 @@ class DoudizhuGameBoard extends React.Component {
computeSideHand(cards) { computeSideHand(cards) {
let upCards; let upCards;
let downCards = []; let downCards = [];
if (cards.length > 10) { if (cards.length > 16) {
upCards = cards.slice(0, 10); upCards = cards.slice(0, 16);
downCards = cards.slice(10); downCards = cards.slice(16);
} else { } else {
upCards = cards; upCards = cards;
} }
@ -320,11 +320,13 @@ class DoudizhuGameBoard extends React.Component {
return element.id === bottomId; return element.id === bottomId;
}); });
const bottomIdx = found ? found.index : -1; const bottomIdx = found ? found.index : -1;
const rightIdx = bottomIdx >= 0 ? (bottomIdx + 1) % 3 : -1; const rightIdx = bottomIdx >= 0 ? (bottomIdx + 1) % 4 : -1;
const leftIdx = rightIdx >= 0 ? (rightIdx + 1) % 3 : -1; const topIdx = rightIdx >= 0 ? (rightIdx + 1) % 4 : -1;
const leftIdx = topIdx >= 0 ? (topIdx + 1) % 4 : -1;
let rightId = -1; let rightId = -1;
let leftId = -1; let leftId = -1;
if (rightIdx >= 0 && leftIdx >= 0) { let topId = -1;
if (rightIdx >= 0 && leftIdx >= 0 && topIdx >= 0) {
found = this.props.playerInfo.find((element) => { found = this.props.playerInfo.find((element) => {
return element.index === rightIdx; return element.index === rightIdx;
}); });
@ -333,6 +335,10 @@ class DoudizhuGameBoard extends React.Component {
return element.index === leftIdx; return element.index === leftIdx;
}); });
if (found) leftId = found.id; if (found) leftId = found.id;
found = this.props.playerInfo.find((element) => {
return element.index === topIdx;
});
if (found) topId = found.id;
} }
return ( return (
<div <div
@ -381,6 +387,21 @@ class DoudizhuGameBoard extends React.Component {
</div> </div>
<div className="played-card-area">{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}</div> <div className="played-card-area">{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}</div>
</div> </div>
<div id={'top-player'}>
<div className="player-main-area">
<div className="player-info">{this.computePlayerPortrait(topId, topIdx)}</div>
{topIdx >= 0 ? (
<div className="player-hand">
{this.computeSideHand(this.props.hands[topIdx])}
</div>
) : (
<div className="player-hand-placeholder">
<span>{t('waiting...')}</span>
</div>
)}
</div>
<div className="played-card-area">{topIdx >= 0 ? this.playerDecisionArea(topIdx) : ''}</div>
</div>
<div id={'bottom-player'}> <div id={'bottom-player'}>
<div className="played-card-area"> <div className="played-card-area">
{bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''} {bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''}
@ -438,7 +459,12 @@ class DoudizhuGameBoard extends React.Component {
</Button> </Button>
<Button <Button
onClick={() => this.props.handleSelectRole('landlord_down')} onClick={() => this.props.handleSelectRole('landlord_down')}
style={{ width: '225px', justifyContent: 'space-evenly' }} style={{
width: '225px',
justifyContent: 'space-evenly',
marginTop: '20px',
marginBottom: '20px',
}}
variant="contained" variant="contained"
color="primary" color="primary"
startIcon={<img src={Peasant_wName} alt="Peasant" width="48px" height="48px" />} startIcon={<img src={Peasant_wName} alt="Peasant" width="48px" height="48px" />}
@ -446,6 +472,16 @@ class DoudizhuGameBoard extends React.Component {
{t('doudizhu.play_as_peasant')} {t('doudizhu.play_as_peasant')}
<br />({t('doudizhu.landlord_down')}) <br />({t('doudizhu.landlord_down')})
</Button> </Button>
<Button
onClick={() => this.props.handleSelectRole('landlord_front')}
style={{ width: '225px', justifyContent: 'space-evenly' }}
variant="contained"
color="primary"
startIcon={<img src={Peasant_wName} alt="Peasant" width="48px" height="48px" />}
>
{t('doudizhu.play_as_peasant')}
<br />({t('doudizhu.landlord_front')})
</Button>
</Layout.Row> </Layout.Row>
)} )}
{this.props.gamePlayable && this.props.gameStatus === 'localeSelection' && ( {this.props.gamePlayable && this.props.gameStatus === 'localeSelection' && (

View File

@ -11,6 +11,7 @@
"play_as_landlord": "扮演地主", "play_as_landlord": "扮演地主",
"play_as_peasant": "扮演农民", "play_as_peasant": "扮演农民",
"landlord_up": "地主上家", "landlord_up": "地主上家",
"landlord_front": "地主对家",
"landlord_down": "地主下家", "landlord_down": "地主下家",
"peasants_win": "农民胜利!", "peasants_win": "农民胜利!",
"landlord_win": "地主胜利!", "landlord_win": "地主胜利!",

View File

@ -51,12 +51,12 @@ export function translateCardData(card) {
let rankText; let rankText;
let suitText = ''; let suitText = '';
// translate rank // translate rank
if (card === 'RJ') { if (card === 'RJ' || card === 'RJ2') {
rankClass = 'big'; rankClass = 'big';
rankText = '+'; rankText = '+';
suitClass = 'joker'; suitClass = 'joker';
suitText = 'Joker'; suitText = 'Joker';
} else if (card === 'BJ') { } else if (card === 'BJ' || card === 'BJ2') {
rankClass = 'little'; rankClass = 'little';
rankText = '-'; rankText = '-';
suitClass = 'joker'; suitClass = 'joker';
@ -67,7 +67,7 @@ export function translateCardData(card) {
rankText = card.charAt(1) === 'T' ? `10` : card.charAt(1); rankText = card.charAt(1) === 'T' ? `10` : card.charAt(1);
} }
// translate suitClass // translate suitClass
if (card !== 'RJ' && card !== 'BJ') { if (card !== 'RJ' && card !== 'BJ' && card !== 'RJ2' && card !== 'BJ2') {
suitClass = suitMap.get(card.charAt(0)); suitClass = suitMap.get(card.charAt(0));
suitText = suitMapSymbol.get(card.charAt(0)); suitText = suitMapSymbol.get(card.charAt(0));
} }
@ -101,9 +101,9 @@ export function computeHandCardsWidth(num, emWidth) {
} }
export function card2SuiteAndRank(card) { export function card2SuiteAndRank(card) {
if (card === 'BJ' || card === 'B') { if (card === 'BJ' || card === 'B' || card === 'BJ2' || card === 'B2') {
return { suite: null, rank: 'X' }; return { suite: null, rank: 'X' };
} else if (card === 'RJ' || card === 'R') { } else if (card === 'RJ' || card === 'R' || card === 'RJ2' || card === 'R') {
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] };
@ -165,6 +165,60 @@ export const fullDoudizhuDeck = [
'C3', 'C3',
'H3', 'H3',
'D3', 'D3',
'RJ2',
'BJ2',
'S22',
'C22',
'H22',
'D22',
'SA2',
'CA2',
'HA2',
'DA2',
'SK2',
'CK2',
'HK2',
'DK2',
'SQ2',
'CQ2',
'HQ2',
'DQ2',
'SJ2',
'CJ2',
'HJ2',
'DJ2',
'ST2',
'CT2',
'HT2',
'DT2',
'S92',
'C92',
'H92',
'D92',
'S82',
'C82',
'H82',
'D82',
'S72',
'C72',
'H72',
'D72',
'S62',
'C62',
'H62',
'D62',
'S52',
'C52',
'H52',
'D52',
'S42',
'C42',
'H42',
'D42',
'S32',
'C32',
'H32',
'D32',
]; ];
export const fullDoudizhuDeckIndex = { export const fullDoudizhuDeckIndex = {
@ -222,6 +276,60 @@ export const fullDoudizhuDeckIndex = {
C3: 3, C3: 3,
H3: 2, H3: 2,
D3: 1, D3: 1,
RJ2: 54,
BJ2: 53,
S22: 52,
C22: 51,
H22: 50,
D22: 49,
SA2: 48,
CA2: 47,
HA2: 46,
DA2: 45,
SK2: 44,
CK2: 43,
HK2: 42,
DK2: 41,
SQ2: 40,
CQ2: 39,
HQ2: 38,
DQ2: 37,
SJ2: 36,
CJ2: 35,
HJ2: 34,
DJ2: 33,
ST2: 32,
CT2: 31,
HT2: 30,
DT2: 29,
S92: 28,
C92: 27,
H92: 26,
D92: 25,
S82: 24,
C82: 23,
H82: 22,
D82: 21,
S72: 20,
C72: 19,
H72: 18,
D72: 17,
S62: 16,
C62: 15,
H62: 14,
D62: 13,
S52: 12,
C52: 11,
H52: 10,
D52: 9,
S42: 8,
C42: 7,
H42: 6,
D42: 5,
S32: 4,
C32: 3,
H32: 2,
D32: 1,
}; };
export function sortDoudizhuCards(cards, ascending = false) { export function sortDoudizhuCards(cards, ascending = false) {
@ -234,10 +342,38 @@ export function sortDoudizhuCards(cards, ascending = false) {
} }
export function isDoudizhuBomb(cards) { export function isDoudizhuBomb(cards) {
if (cards.length === 2) return (cards[0] === 'RJ' && cards[1] === 'BJ') || (cards[0] === 'BJ' && cards[1] === 'RJ'); if(cards.length <= 3 || cards.length >= 9)
if (cards.length === 4) return -1;
return cards[0][1] === cards[1][1] && cards[0][1] === cards[2][1] && cards[0][1] === cards[3][1]; const distinctedCards = [...new Set(cards.map(x=>x.substring(0, 2)))];
return false; switch(cards.length) {
case 4:
if(distinctedCards.length == 2) {
if((distinctedCards[0] === 'RJ' && distinctedCards[1] === 'BJ') || (distinctedCards[0] === 'BJ' && distinctedCards[1] === 'RJ')) {
return 1;
}
}
else if(distinctedCards.length == 1) {
return 2;
}
return -1;
case 5:
if(distinctedCards.length == 1) {
return 2;
}
return -1;
case 6:
case 7:
if(distinctedCards.length == 1) {
return 0;
}
return -1;
case 8:
if(distinctedCards.length == 1) {
return 1;
}
return -1;
}
return -1;
} }
export function shuffleArray(inputArray) { export function shuffleArray(inputArray) {

View File

@ -33,7 +33,7 @@ import { douzeroDemoUrl } from '../../utils/config';
let shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice()); let shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice());
let threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3))); let threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 8)));
let originalThreeLandlordCards = threeLandlordCards.slice(); let originalThreeLandlordCards = threeLandlordCards.slice();
const initConsiderationTime = 30000; const initConsiderationTime = 30000;
@ -42,9 +42,10 @@ const mainPlayerId = 0; // index of main player (for the sake of simplify code l
let playerInfo = []; let playerInfo = [];
let initHands = [ let initHands = [
shuffledDoudizhuDeck.slice(3, 20), shuffledDoudizhuDeck.slice(8, 33),
shuffledDoudizhuDeck.slice(20, 37), shuffledDoudizhuDeck.slice(33, 58),
shuffledDoudizhuDeck.slice(37, 54), shuffledDoudizhuDeck.slice(58, 83),
shuffledDoudizhuDeck.slice(83, 108),
]; ];
console.log('init hands', initHands); console.log('init hands', initHands);
@ -53,12 +54,14 @@ console.log('three landlord card', threeLandlordCards);
let gameStateTimeout = null; let gameStateTimeout = null;
let gameHistory = []; let gameHistory = [];
let bombNum = 0; let bombNum = [0, 0 , 0];
let lastMoveLandlord = []; let lastMoveLandlord = [];
let lastMoveLandlordDown = []; let lastMoveLandlordDown = [];
let lastMoveLandlordFront = [];
let lastMoveLandlordUp = []; let lastMoveLandlordUp = [];
let playedCardsLandlord = []; let playedCardsLandlord = [];
let playedCardsLandlordDown = []; let playedCardsLandlordDown = [];
let playedCardsLandlordFront = [];
let playedCardsLandlordUp = []; let playedCardsLandlordUp = [];
let legalActions = { turn: -1, actions: [] }; let legalActions = { turn: -1, actions: [] };
let hintIdx = -1; let hintIdx = -1;
@ -73,8 +76,8 @@ function PvEDoudizhuDemoView() {
const [toggleFade, setToggleFade] = useState(''); const [toggleFade, setToggleFade] = useState('');
const [gameStatus, setGameStatus] = useState(localStorage.getItem('LOCALE') ? 'ready' : 'localeSelection'); // "localeSelection", "ready", "playing", "paused", "over" const [gameStatus, setGameStatus] = useState(localStorage.getItem('LOCALE') ? 'ready' : 'localeSelection'); // "localeSelection", "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,
}); });
@ -90,8 +93,8 @@ function PvEDoudizhuDemoView() {
const cardArr2DouzeroFormat = (cards) => { const cardArr2DouzeroFormat = (cards) => {
return cards return cards
.map((card) => { .map((card) => {
if (card === 'RJ') return 'D'; if (card === 'RJ' || card === 'RJ2') return 'D';
if (card === 'BJ') return 'X'; if (card === 'BJ' || card === 'BJ2') return 'X';
return card[1]; return card[1];
}) })
.join(''); .join('');
@ -122,12 +125,15 @@ function PvEDoudizhuDemoView() {
} }
// if next player is user, get legal actions // if next player is user, get legal actions
if ((gameState.currentPlayer + 1) % 3 === mainPlayerId) { if ((gameState.currentPlayer + 1) % 4 === mainPlayerId) {
hintIdx = -1; hintIdx = -1;
const player_hand_cards = cardArr2DouzeroFormat(gameState.hands[mainPlayerId].slice().reverse()); const player_hand_cards = cardArr2DouzeroFormat(gameState.hands[mainPlayerId].slice().reverse());
let rival_move = ''; let rival_move = '';
if (playingCard.length === 0) { if (playingCard.length === 0) {
rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 1], true)); rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 1], true));
if(rival_move === '') {
rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 2], true));
}
} else { } else {
rival_move = rankOnly ? playingCard.join('') : cardArr2DouzeroFormat(playingCard); rival_move = rankOnly ? playingCard.join('') : cardArr2DouzeroFormat(playingCard);
} }
@ -142,7 +148,7 @@ function PvEDoudizhuDemoView() {
actions: data.legal_action.split(','), actions: data.legal_action.split(','),
}; };
setIsHintDisabled(data.legal_action === ''); setIsHintDisabled(data.legal_action === '');
setIsPassDisabled(playingCard.length === 0 && gameHistory[gameHistory.length - 1].length === 0); setIsPassDisabled(playingCard.length === 0 && gameHistory[gameHistory.length - 1].length === 0 && gameHistory[gameHistory.length - 2].length === 0);
} }
// delay play for api player // delay play for api player
@ -200,6 +206,10 @@ function PvEDoudizhuDemoView() {
playedCardsLandlordDown = playedCardsLandlordDown.concat(newHistoryRecord); playedCardsLandlordDown = playedCardsLandlordDown.concat(newHistoryRecord);
break; break;
case 2: case 2:
lastMoveLandlordFront = newHistoryRecord;
playedCardsLandlordFront = playedCardsLandlordFront.concat(newHistoryRecord);
break;
case 3:
lastMoveLandlordUp = newHistoryRecord; lastMoveLandlordUp = newHistoryRecord;
playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord); playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord);
break; break;
@ -207,11 +217,12 @@ function PvEDoudizhuDemoView() {
break; break;
} }
gameHistory.push(newHistoryRecord); gameHistory.push(newHistoryRecord);
if (isDoudizhuBomb(newHistoryRecord)) bombNum++; const bombLevel = isDoudizhuBomb(newHistoryRecord);
if (bombLevel >= 0) bombNum[bombLevel]++;
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) % 4;
newGameState.turn++; newGameState.turn++;
if (newHand.length === 0) { if (newHand.length === 0) {
setGameStatus('over'); setGameStatus('over');
@ -241,6 +252,8 @@ function PvEDoudizhuDemoView() {
landlordWinNum: 0, landlordWinNum: 0,
landlordUpGameNum: 0, landlordUpGameNum: 0,
landlordUpWinNum: 0, landlordUpWinNum: 0,
landlordFrontGameNum: 0,
landlordFrontWinNum: 0,
landlordDownGameNum: 0, landlordDownGameNum: 0,
landlordDownWinNum: 0, landlordDownWinNum: 0,
}; };
@ -262,6 +275,13 @@ function PvEDoudizhuDemoView() {
} }
break; break;
case 2: case 2:
gameStatistics.landlordFrontGameNum += 1;
if (winner.role === playerInfo[mainPlayerId].role) {
gameStatistics.totalWinNum += 1;
gameStatistics.landlordDownWinNum += 1;
}
break;
case 3:
gameStatistics.landlordUpGameNum += 1; gameStatistics.landlordUpGameNum += 1;
if (winner.role === playerInfo[mainPlayerId].role) { if (winner.role === playerInfo[mainPlayerId].role) {
gameStatistics.totalWinNum += 1; gameStatistics.totalWinNum += 1;
@ -298,6 +318,15 @@ function PvEDoudizhuDemoView() {
'%' '%'
: '-', : '-',
}, },
{
role: t('doudizhu.landlord_front'),
win: gameStatistics.landlordFrontWinNum,
total: gameStatistics.landlordFrontGameNum,
winRate: gameStatistics.landlordFrontGameNum
? ((gameStatistics.landlordFrontWinNum / gameStatistics.landlordFrontGameNum) * 100).toFixed(2) +
'%'
: '-',
},
{ {
role: t('doudizhu.landlord_down'), role: t('doudizhu.landlord_down'),
win: gameStatistics.landlordDownWinNum, win: gameStatistics.landlordDownWinNum,
@ -335,9 +364,11 @@ function PvEDoudizhuDemoView() {
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 0).index].length; gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 0).index].length;
const num_cards_left_landlord_down = const num_cards_left_landlord_down =
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 1).index].length; gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 1).index].length;
const num_cards_left_landlord_up = const num_cards_left_landlord_front =
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 2).index].length; gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 2).index].length;
const three_landlord_cards = cardArr2DouzeroFormat(threeLandlordCards.slice().reverse()); const num_cards_left_landlord_up =
gameState.hands[playerInfo.find((player) => player.douzeroPlayerPosition === 3).index].length;
// const three_landlord_cards = cardArr2DouzeroFormat(threeLandlordCards.slice().reverse());
const card_play_action_seq = gameHistory const card_play_action_seq = gameHistory
.map((cards) => { .map((cards) => {
return cardArr2DouzeroFormat(cards); return cardArr2DouzeroFormat(cards);
@ -345,18 +376,21 @@ function PvEDoudizhuDemoView() {
.join(','); .join(',');
const other_hand_cards = cardArr2DouzeroFormat( const other_hand_cards = cardArr2DouzeroFormat(
sortDoudizhuCards( sortDoudizhuCards(
gameState.hands[(gameState.currentPlayer + 1) % 3].concat( gameState.hands[(gameState.currentPlayer + 1) % 4].concat(
gameState.hands[(gameState.currentPlayer + 2) % 3], gameState.hands[(gameState.currentPlayer + 2) % 4],
gameState.hands[(gameState.currentPlayer + 3) % 4],
), ),
true, true,
), ),
); );
const last_move_landlord = cardArr2DouzeroFormat(lastMoveLandlord.slice().reverse()); const last_move_landlord = cardArr2DouzeroFormat(lastMoveLandlord.slice().reverse());
const last_move_landlord_down = cardArr2DouzeroFormat(lastMoveLandlordDown.slice().reverse()); const last_move_landlord_down = cardArr2DouzeroFormat(lastMoveLandlordDown.slice().reverse());
const last_move_landlord_front = cardArr2DouzeroFormat(lastMoveLandlordFront.slice().reverse());
const last_move_landlord_up = cardArr2DouzeroFormat(lastMoveLandlordUp.slice().reverse()); const last_move_landlord_up = cardArr2DouzeroFormat(lastMoveLandlordUp.slice().reverse());
const bomb_num = bombNum; const bomb_num = bombNum.join(',');
const played_cards_landlord = cardArr2DouzeroFormat(playedCardsLandlord); const played_cards_landlord = cardArr2DouzeroFormat(playedCardsLandlord);
const played_cards_landlord_down = cardArr2DouzeroFormat(playedCardsLandlordDown); const played_cards_landlord_down = cardArr2DouzeroFormat(playedCardsLandlordDown);
const played_cards_landlord_front = cardArr2DouzeroFormat(playedCardsLandlordFront);
const played_cards_landlord_up = cardArr2DouzeroFormat(playedCardsLandlordUp); const played_cards_landlord_up = cardArr2DouzeroFormat(playedCardsLandlordUp);
const requestBody = { const requestBody = {
@ -364,16 +398,19 @@ function PvEDoudizhuDemoView() {
player_hand_cards, player_hand_cards,
num_cards_left_landlord, num_cards_left_landlord,
num_cards_left_landlord_down, num_cards_left_landlord_down,
num_cards_left_landlord_front,
num_cards_left_landlord_up, num_cards_left_landlord_up,
three_landlord_cards, // three_landlord_cards,
card_play_action_seq, card_play_action_seq,
other_hand_cards, other_hand_cards,
last_move_landlord, last_move_landlord,
last_move_landlord_down, last_move_landlord_down,
last_move_landlord_front,
last_move_landlord_up, last_move_landlord_up,
bomb_num, bomb_num,
played_cards_landlord, played_cards_landlord,
played_cards_landlord_down, played_cards_landlord_down,
played_cards_landlord_front,
played_cards_landlord_up, played_cards_landlord_up,
}; };
@ -396,6 +433,10 @@ function PvEDoudizhuDemoView() {
rival_move = cardArr2DouzeroFormat( rival_move = cardArr2DouzeroFormat(
sortDoudizhuCards(gameHistory[gameHistory.length - 2], true), sortDoudizhuCards(gameHistory[gameHistory.length - 2], true),
); );
} else if (gameHistory.length >= 3 && gameHistory[gameHistory.length - 3].length > 0) {
rival_move = cardArr2DouzeroFormat(
sortDoudizhuCards(gameHistory[gameHistory.length - 3], true),
);
} }
const requestBody = { const requestBody = {
player_hand_cards, player_hand_cards,
@ -500,6 +541,12 @@ function PvEDoudizhuDemoView() {
role: 'peasant', role: 'peasant',
douzeroPlayerPosition: -1, douzeroPlayerPosition: -1,
}, },
{
id: 3,
index: 3,
role: 'peasant',
douzeroPlayerPosition: -1,
},
]; ];
switch (role) { switch (role) {
case 'landlord_up': case 'landlord_up':
@ -511,6 +558,10 @@ function PvEDoudizhuDemoView() {
playerInfo[0].role = 'landlord'; playerInfo[0].role = 'landlord';
break; break;
case 'landlord_down': case 'landlord_down':
playerInfo = deepCopy(playerInfoTemplate);
playerInfo[3].role = 'landlord';
break;
case 'landlord_front':
playerInfo = deepCopy(playerInfoTemplate); playerInfo = deepCopy(playerInfoTemplate);
playerInfo[2].role = 'landlord'; playerInfo[2].role = 'landlord';
break; break;
@ -519,8 +570,9 @@ function PvEDoudizhuDemoView() {
} }
const landlordIdx = playerInfo.find((player) => player.role === 'landlord').index; const landlordIdx = playerInfo.find((player) => player.role === 'landlord').index;
playerInfo[landlordIdx].douzeroPlayerPosition = 0; playerInfo[landlordIdx].douzeroPlayerPosition = 0;
playerInfo[(landlordIdx + 1) % 3].douzeroPlayerPosition = 1; playerInfo[(landlordIdx + 1) % 4].douzeroPlayerPosition = 1;
playerInfo[(landlordIdx + 2) % 3].douzeroPlayerPosition = 2; playerInfo[(landlordIdx + 2) % 4].douzeroPlayerPosition = 2;
playerInfo[(landlordIdx + 3) % 4].douzeroPlayerPosition = 3;
initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice()); initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice());
setGameStatus('playing'); setGameStatus('playing');
syncGameStatus = 'playing'; syncGameStatus = 'playing';
@ -556,6 +608,12 @@ function PvEDoudizhuDemoView() {
total: 0, total: 0,
winRate: '-', winRate: '-',
}, },
{
role: t('doudizhu.landlord_front'),
win: 0,
total: 0,
winRate: '-',
},
{ {
role: t('doudizhu.landlord_down'), role: t('doudizhu.landlord_down'),
win: 0, win: 0,
@ -575,25 +633,28 @@ function PvEDoudizhuDemoView() {
// reset all game state for new game // reset all game state for new game
shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice()); shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice());
threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3))); threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 8)));
originalThreeLandlordCards = threeLandlordCards.slice(); originalThreeLandlordCards = threeLandlordCards.slice();
initHands = [ initHands = [
shuffledDoudizhuDeck.slice(3, 20), shuffledDoudizhuDeck.slice(8, 33),
shuffledDoudizhuDeck.slice(20, 37), shuffledDoudizhuDeck.slice(33, 58),
shuffledDoudizhuDeck.slice(37, 54), shuffledDoudizhuDeck.slice(58, 83),
shuffledDoudizhuDeck.slice(83, 108),
]; ];
playerInfo = []; playerInfo = [];
gameStateTimeout = null; gameStateTimeout = null;
gameHistory = []; gameHistory = [];
bombNum = 0; bombNum = [0, 0, 0];
lastMoveLandlord = []; lastMoveLandlord = [];
lastMoveLandlordDown = []; lastMoveLandlordDown = [];
lastMoveLandlordFront = [];
lastMoveLandlordUp = []; lastMoveLandlordUp = [];
playedCardsLandlord = []; playedCardsLandlord = [];
playedCardsLandlordDown = []; playedCardsLandlordDown = [];
playedCardsLandlordFront = [];
playedCardsLandlordUp = []; playedCardsLandlordUp = [];
legalActions = { turn: -1, actions: [] }; legalActions = { turn: -1, actions: [] };
@ -602,8 +663,8 @@ function PvEDoudizhuDemoView() {
setIsPassDisabled(true); setIsPassDisabled(true);
setIsHintDisabled(true); setIsHintDisabled(true);
setGameState({ setGameState({
hands: [[], [], []], hands: [[], [], [], []],
latestAction: [[], [], []], latestAction: [[], [], [], []],
currentPlayer: null, // index of current player currentPlayer: null, // index of current player
turn: 0, turn: 0,
}); });
@ -961,47 +1022,12 @@ function PvEDoudizhuDemoView() {
</Layout.Col> </Layout.Col>
<Layout.Col span="7" style={{ height: '100%' }}> <Layout.Col span="7" style={{ height: '100%' }}>
<Paper className={'doudizhu-probability-paper'} elevation={3}> <Paper className={'doudizhu-probability-paper'} elevation={3}>
{playerInfo.length > 0 && gameState.currentPlayer !== null ? (
<div style={{ padding: '16px' }}>
<span style={{ textAlign: 'center', marginBottom: '8px', display: 'block' }}>
{t('doudizhu.three_landlord_cards')}
</span>
<div className="playingCards" style={{ display: 'flex', justifyContent: 'center' }}>
{sortDoudizhuCards(originalThreeLandlordCards, true).map((card) => {
const [rankClass, suitClass, rankText, suitText] = translateCardData(card);
return (
<div
key={'probability-cards-' + rankText + '-' + suitText}
style={{ fontSize: '1.2em' }}
className={`card ${rankClass} full-content ${suitClass}`}
>
<span className="rank">{rankText}</span>
<span className="suit">{suitText}</span>
</div>
);
})}
</div>
</div>
) : (
<div
style={{
height: '112px',
padding: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span>{t('waiting...')}</span>
</div>
)}
<Divider />
<div className={'probability-player'} style={{ height: '19px', textAlign: 'center' }}> <div className={'probability-player'} style={{ height: '19px', textAlign: 'center' }}>
{playerInfo.length > 0 && gameState.currentPlayer !== null ? ( {playerInfo.length > 0 && gameState.currentPlayer !== null ? (
<span> <span>
{t( {t(
`doudizhu.${ `doudizhu.${
['landlord', 'landlord_down', 'landlord_up'][ ['landlord', 'landlord_down', 'landlord_front', 'landlord_up'][
playerInfo[gameState.currentPlayer].douzeroPlayerPosition playerInfo[gameState.currentPlayer].douzeroPlayerPosition
] ]
}`, }`,
@ -1016,6 +1042,7 @@ function PvEDoudizhuDemoView() {
<div className={'probability-item'}>{computeProbabilityItem(0)}</div> <div className={'probability-item'}>{computeProbabilityItem(0)}</div>
<div className={'probability-item'}>{computeProbabilityItem(1)}</div> <div className={'probability-item'}>{computeProbabilityItem(1)}</div>
<div className={'probability-item'}>{computeProbabilityItem(2)}</div> <div className={'probability-item'}>{computeProbabilityItem(2)}</div>
<div className={'probability-item'}>{computeProbabilityItem(3)}</div>
</div> </div>
</Paper> </Paper>
</Layout.Col> </Layout.Col>