diff --git a/pve_server/deep.py b/pve_server/deep.py index 9219a68..e5227df 100644 --- a/pve_server/deep.py +++ b/pve_server/deep.py @@ -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, 11: 8, 12: 9, 13: 10, 14: 11, 17: 12, 20: 13, 30: 14} -NumOnes2Array = {0: np.array([0, 0, 0, 0]), - 1: np.array([1, 0, 0, 0]), - 2: np.array([1, 1, 0, 0]), - 3: np.array([1, 1, 1, 0]), - 4: np.array([1, 1, 1, 1])} +NumOnes2Array = {0: np.array([0, 0, 0, 0, 0, 0, 0, 0]), + 1: np.array([1, 0, 0, 0, 0, 0, 0, 0]), + 2: np.array([1, 1, 0, 0, 0, 0, 0, 0]), + 3: np.array([1, 1, 1, 0, 0, 0, 0, 0]), + 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) - one_hot[bomb_num] = 1 + +def _get_one_hot_bomb(bomb_num, use_legacy = False): + """ + 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 def _load_model(position, model_dir, use_onnx): if not use_onnx or not os.path.isfile(os.path.join(model_dir, position+'.onnx')) : - from models import model_dict - model = model_dict[position]() + from models import model_dict_new + model = model_dict_new[position]() model_state_dict = model.state_dict() model_path = os.path.join(model_dir, position+'.ckpt') 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')) 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() + if new_model: + sequence = sequence[::-1] if len(sequence) < length: empty_sequence = [[] for _ in range(length - len(sequence))] empty_sequence.extend(sequence) @@ -74,18 +96,22 @@ class DeepAgent: def cards2array(self, list_cards): 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) - jokers = np.zeros(2, dtype=np.float32) + matrix = np.zeros([8, 13], dtype=np.int8) + jokers = np.zeros(4, dtype=np.int8) counter = Counter(list_cards) for card, num_times in counter.items(): if card < 20: matrix[:, Card2Column[card]] = NumOnes2Array[num_times] elif card == 20: jokers[0] = 1 + if num_times == 2: + jokers[1] = 1 elif card == 30: - jokers[1] = 1 + jokers[2] = 1 + if num_times == 2: + jokers[3] = 1 return np.concatenate((matrix.flatten('F'), jokers)) def get_one_hot_array(self, num_left_cards, max_num_cards): @@ -94,173 +120,146 @@ class DeepAgent: return one_hot - def action_seq_list2array(self, action_seq_list): - action_seq_array = np.zeros((len(action_seq_list), 54), dtype=np.float32) - for row, list_cards in enumerate(action_seq_list): - action_seq_array[row, :] = self.cards2array(list_cards) - action_seq_array = action_seq_array.reshape(5, 162) + def action_seq_list2array(self, action_seq_list, new_model=True): + """ + A utility function to encode the historical moves. + We encode the historical 20 actions. If there is + 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 - def act(self, infoset): - player_position = infoset.player_position + def get_obs_general(self, infoset, position, use_legacy = False): num_legal_actions = len(infoset.legal_actions) my_handcards = self.cards2array(infoset.player_hand_cards) 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_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): my_action_batch[j, :] = self.cards2array(action) - last_action = self.cards2array(infoset.rival_move) - last_action_batch = np.repeat(last_action[np.newaxis, :], - num_legal_actions, axis=0) + landlord_num_cards_left = self.get_one_hot_array( + infoset.num_cards_left_dict['landlord'], 33) - if player_position == 0: - landlord_up_num_cards_left = self.get_one_hot_array( - 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_up_num_cards_left = self.get_one_hot_array( + infoset.num_cards_left_dict['landlord_up'], 25) - landlord_down_num_cards_left = self.get_one_hot_array( - infoset.num_cards_left[1], 17) - landlord_down_num_cards_left_batch = np.repeat( - landlord_down_num_cards_left[np.newaxis, :], - num_legal_actions, axis=0) + landlord_front_num_cards_left = self.get_one_hot_array( + infoset.num_cards_left_dict['landlord_front'], 25) - landlord_up_played_cards = self.cards2array( - infoset.played_cards[2]) - landlord_up_played_cards_batch = np.repeat( - landlord_up_played_cards[np.newaxis, :], - num_legal_actions, axis=0) + landlord_down_num_cards_left = self.get_one_hot_array( + infoset.num_cards_left_dict['landlord_down'], 25) - landlord_down_played_cards = self.cards2array( - infoset.played_cards[1]) - landlord_down_played_cards_batch = np.repeat( - landlord_down_played_cards[np.newaxis, :], - num_legal_actions, axis=0) + landlord_played_cards = self.cards2array( + infoset.played_cards[0]) - bomb_num = _get_one_hot_bomb( - infoset.bomb_num) - bomb_num_batch = np.repeat( - bomb_num[np.newaxis, :], - num_legal_actions, axis=0) + landlord_up_played_cards = self.cards2array( + infoset.played_cards[1]) - x_batch = np.hstack((my_handcards_batch, - other_handcards_batch, - last_action_batch, - landlord_up_played_cards_batch, - landlord_down_played_cards_batch, - landlord_up_num_cards_left_batch, - landlord_down_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() + landlord_front_played_cards = self.cards2array( + infoset.played_cards[2]) + + landlord_down_played_cards = self.cards2array( + infoset.played_cards[3]) + + bomb_num = _get_one_hot_bomb( + infoset.bomb_num, use_legacy) + bomb_num_batch = np.repeat( + bomb_num[np.newaxis, :], + num_legal_actions, axis=0) + num_cards_left = np.hstack(( + landlord_num_cards_left, # 33 + landlord_up_num_cards_left, # 25 + landlord_front_num_cards_left, # 25 + landlord_down_num_cards_left)) + + if use_legacy: + x_batch = np.hstack(( + bid_info_batch, # 20 + multiply_info_batch)) # 4 + x_no_action = np.hstack(( + bid_info, + multiply_info)) else: - last_landlord_action = self.cards2array( - infoset.last_moves[0]) - last_landlord_action_batch = np.repeat( - last_landlord_action[np.newaxis, :], - num_legal_actions, axis=0) - landlord_num_cards_left = self.get_one_hot_array( - infoset.num_cards_left[0], 20) - landlord_num_cards_left_batch = np.repeat( - landlord_num_cards_left[np.newaxis, :], - num_legal_actions, axis=0) + x_batch = np.hstack(( + bomb_num_batch, # 56 + bid_info_batch, # 20 + multiply_info_batch)) # 4 + x_no_action = np.hstack(( + bomb_num, # 56 + bid_info, + multiply_info)) + z =np.vstack(( + 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( - infoset.played_cards[0]) - landlord_played_cards_batch = np.repeat( - landlord_played_cards[np.newaxis, :], - num_legal_actions, axis=0) + _z_batch = np.repeat( + z[np.newaxis, :, :], + num_legal_actions, axis=0) + my_action_batch = my_action_batch[:,np.newaxis,:] + 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: - last_teammate_action = self.cards2array( - infoset.last_moves[1]) - 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[1], 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[1]) - teammate_played_cards_batch = np.repeat( - 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() + def act(self, infoset): + obs = self.get_obs_general(infoset, infoset.player_position) + z_batch = obs['z_batch'] + x_batch = obs['x_batch'] + 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())['values'] + y_pred = y_pred.detach().numpy() y_pred = y_pred.flatten() diff --git a/pve_server/models.py b/pve_server/models.py index b6b4b2a..4299611 100644 --- a/pve_server/models.py +++ b/pve_server/models.py @@ -1,5 +1,6 @@ import torch from torch import nn +import torch.nn.functional as F class LandlordLstmModel(nn.Module): def __init__(self): @@ -61,7 +62,137 @@ class FarmerLstmModel(nn.Module): x = self.dense6(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['landlord'] = LandlordLstmModel model_dict['landlord_up'] = 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 diff --git a/pve_server/run_dmc.py b/pve_server/run_dmc.py index e9ef4c8..e785c6d 100644 --- a/pve_server/run_dmc.py +++ b/pve_server/run_dmc.py @@ -37,7 +37,7 @@ RealCard2EnvCard = {'3': '3', '4': '4', '5': '5', '6': '6', '7': '7', pretrained_dir = 'pretrained/dmc_pretrained' device = torch.device('cpu') players = [] -for i in range(3): +for i in range(4): model_path = os.path.join(pretrained_dir, str(i)+'.pth') agent = torch.load(model_path, map_location=device) agent.set_device(device) @@ -50,33 +50,38 @@ def predict(): try: # Player postion player_position = request.form.get('player_position') - if player_position not in ['0', '1', '2']: - return jsonify({'status': 1, 'message': 'player_position must be 0, 1, or 2'}) + if player_position not in ['0', '1', '2', '3']: + return jsonify({'status': 1, 'message': 'player_position must be 0, 1, 2 or 3'}) player_position = int(player_position) # Player hand cards player_hand_cards = ''.join( [RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')]) if player_position == 0: - if len(player_hand_cards) < 1 or len(player_hand_cards) > 20: - return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-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-33'}) else: - if len(player_hand_cards) < 1 or len(player_hand_cards) > 17: - return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-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-25'}) # 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): 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'}) # Three landlord cards - three_landlord_cards = ''.join( - [RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')]) - 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'}) + # three_landlord_cards = ''.join( + # [RealCard2EnvCard[c] for c in request.form.get('three_landlord_cards')]) + # 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'}) # Card play sequence if request.form.get('card_play_action_seq') == '': @@ -89,7 +94,7 @@ def predict(): tmp_seq[i] = 'pass' card_play_action_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 = ''.join( @@ -99,7 +104,7 @@ def predict(): # 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( ''.join([RealCard2EnvCard[c] for c in request.form.get(field)])) @@ -110,7 +115,7 @@ def predict(): state['num_cards_left'] = num_cards_left state['others_hand'] = other_hand_cards state['played_cards'] = played_cards - state['seen_cards'] = three_landlord_cards + # state['seen_cards'] = three_landlord_cards state['self'] = player_position state['trace'] = card_play_action_seq @@ -118,7 +123,10 @@ def predict(): rival_move = 'pass' if len(card_play_action_seq) != 0: 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: rival_move = card_play_action_seq[-1][1] 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) 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 += 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: 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: all_moves = mg.gen_type_7_3_2() 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) 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: 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) - elif rival_move_type == md.TYPE_13_4_2: - all_moves = mg.gen_type_13_4_2() - 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 rival_move_type != md.TYPE_0_PASS and rival_move_type < md.TYPE_4_BOMB: + 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() if len(rival_move) != 0: # rival_move is not 'pass' moves = moves + [[]] @@ -355,37 +367,39 @@ def _get_legal_card_play_actions(player_hand_cards, rival_move): moves.sort() 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 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} -NumOnes2Array = {0: np.array([0, 0, 0, 0]), - 1: np.array([1, 0, 0, 0]), - 2: np.array([1, 1, 0, 0]), - 3: np.array([1, 1, 1, 0]), - 4: np.array([1, 1, 1, 1])} +NumOnes2Array = {0: np.array([0, 0, 0, 0, 0, 0, 0, 0]), + 1: np.array([1, 0, 0, 0, 0, 0, 0, 0]), + 2: np.array([1, 1, 0, 0, 0, 0, 0, 0]), + 3: np.array([1, 1, 1, 0, 0, 0, 0, 0]), + 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): 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) - jokers = np.zeros(2, dtype=np.int8) + jokers = np.zeros(4, dtype=np.int8) counter = Counter(cards) for card, num_times in counter.items(): if card == 'B': jokers[0] = 1 + if num_times == 2: + jokers[1] = 1 elif card == 'R': - jokers[1] = 1 + jokers[2] = 1 + if num_times == 2: + jokers[3] = 1 else: matrix[:, Card2Column[card]] = NumOnes2Array[num_times] 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): - action_seq_array = np.zeros((len(action_seq_list), 54), np.int8) - for row, cards in enumerate(action_seq_list): - action_seq_array[row, :] = _cards2array(cards) - action_seq_array = action_seq_array.flatten() + 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] = _cards2array(list_cards[1]) return action_seq_array diff --git a/pve_server/run_douzero.py b/pve_server/run_douzero.py index e8e1869..7cba810 100644 --- a/pve_server/run_douzero.py +++ b/pve_server/run_douzero.py @@ -20,8 +20,8 @@ RealCard2EnvCard = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7, pretrained_dir = 'pretrained/douzero_pretrained' players = [] -for position in ['landlord', 'landlord_down', 'landlord_up']: - players.append(DeepAgent(position, pretrained_dir, use_onnx=True)) +for position in ['landlord', 'landlord_down', 'landlord_front', 'landlord_up']: + players.append(DeepAgent(position, pretrained_dir, use_onnx=False)) @app.route('/predict', methods=['POST']) def predict(): @@ -29,30 +29,36 @@ def predict(): try: # Player postion player_position = request.form.get('player_position') - if player_position not in ['0', '1', '2']: - return jsonify({'status': 1, 'message': 'player_position must be 0, 1, or 2'}) + if player_position not in ['0', '1', '2', '3']: + return jsonify({'status': 1, 'message': 'player_position must be 0, 1, 2 or 3'}) player_position = int(player_position) # Player hand cards player_hand_cards = [RealCard2EnvCard[c] for c in request.form.get('player_hand_cards')] if player_position == 0: - if len(player_hand_cards) < 1 or len(player_hand_cards) > 20: - return jsonify({'status': 2, 'message': 'the number of hand cards should be 1-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-33'}) else: - if len(player_hand_cards) < 1 or len(player_hand_cards) > 17: - return jsonify({'status': 3, 'message': 'the number of hand cards should be 1-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-25'}) # 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): 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'}) # 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: - return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'}) + # 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: + # return jsonify({'status': 6, 'message': 'the number of landlord cards should be 0-3'}) # Card play sequence if request.form.get('card_play_action_seq') == '': @@ -67,34 +73,47 @@ def predict(): # 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)]) # 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)]) # Bomb Num - bomb_num = int(request.form.get('bomb_num')) + bomb_num = request.form.get('bomb_num') # InfoSet info_set = InfoSet() info_set.player_position = player_position info_set.player_hand_cards = player_hand_cards 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.other_hand_cards = other_hand_cards info_set.last_moves = last_moves 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 rival_move = [] if len(card_play_action_seq) != 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: rival_move = card_play_action_seq[-1] info_set.rival_move = rival_move @@ -154,7 +173,9 @@ class InfoSet(object): self.player_position = None self.player_hand_cards = 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.other_hand_cards = 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) 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 += 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: 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: all_moves = mg.gen_type_7_3_2() 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) 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: 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) - elif rival_move_type == md.TYPE_13_4_2: - all_moves = mg.gen_type_13_4_2() - 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 rival_move_type != md.TYPE_0_PASS and rival_move_type < md.TYPE_4_BOMB: + 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() if len(rival_move) != 0: # rival_move is not 'pass' moves = moves + [[]] diff --git a/pve_server/utils/move_detector.py b/pve_server/utils/move_detector.py index f2d68b3..3b60137 100644 --- a/pve_server/utils/move_detector.py +++ b/pve_server/utils/move_detector.py @@ -24,8 +24,8 @@ def get_move_type(move): if move_size == 2: if move[0] == move[1]: return {'type': TYPE_2_PAIR, 'rank': move[0]} - elif move == [20, 30]: # Kings - return {'type': TYPE_5_KING_BOMB} + # elif move == [20, 30]: # Kings + # return {'type': TYPE_5_KING_BOMB} else: return {'type': TYPE_15_WRONG} @@ -39,8 +39,10 @@ def get_move_type(move): if len(move_dict) == 1: return {'type': TYPE_4_BOMB, 'rank': move[0]} elif len(move_dict) == 2: - if move[0] == move[1] == move[2] or move[1] == move[2] == move[3]: - return {'type': TYPE_6_3_1, 'rank': move[1]} + if move[0] == 20 and move[2] == 30: # Kings + 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: return {'type': TYPE_15_WRONG} else: @@ -50,7 +52,9 @@ def get_move_type(move): return {'type': TYPE_8_SERIAL_SINGLE, 'rank': move[0], 'len': len(move)} 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]} else: return {'type': TYPE_15_WRONG} @@ -60,13 +64,21 @@ def get_move_type(move): count_dict[n] += 1 if move_size == 6: - 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 len(move_dict) == 1: + return {'type': TYPE_4_BOMB6, 'rank': move[0]} + # 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 - (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])} + # 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): + # 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()) 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() if is_continuous_seq(serial_3): - if len(serial_3) == len(single)+len(pair)*2: - return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3)} + # if len(serial_3) == len(single)+len(pair)*2: + # 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: return {'type': TYPE_12_SERIAL_3_2, 'rank': serial_3[0], 'len': len(serial_3)} - if len(serial_3) == 4: - if is_continuous_seq(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]): - return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3) - 1} + # if len(serial_3) == 4: + # if is_continuous_seq(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]): + # return {'type': TYPE_11_SERIAL_3_1, 'rank': serial_3[0], 'len': len(serial_3) - 1} return {'type': TYPE_15_WRONG} diff --git a/pve_server/utils/move_generator.py b/pve_server/utils/move_generator.py index 1cccdeb..2185052 100644 --- a/pve_server/utils/move_generator.py +++ b/pve_server/utils/move_generator.py @@ -3,7 +3,9 @@ import collections import itertools class MovesGener(object): - + """ + This is for generating the possible combinations + """ def __init__(self, cards_list): self.cards_list = cards_list self.cards_dict = collections.defaultdict(int) @@ -89,17 +91,17 @@ class MovesGener(object): self.triple_cards_moves.append([k, k, k]) return self.triple_cards_moves - def gen_type_4_bomb(self): + def gen_type_4_bomb(self, num = 4): self.bomb_moves = [] for k, v in self.cards_dict.items(): - if v == 4: - self.bomb_moves.append([k, k, k, k]) + if v == num: + self.bomb_moves.append([k] * num) return self.bomb_moves def gen_type_5_king_bomb(self): self.final_bomb_moves = [] - if 20 in self.cards_list and 30 in self.cards_list: - self.final_bomb_moves.append([20, 30]) + 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, 20, 30, 30]) return self.final_bomb_moves 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_2_pair()) 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_6_3_1()) + # moves.extend(self.gen_type_6_3_1()) moves.extend(self.gen_type_7_3_2()) moves.extend(self.gen_type_8_serial_single()) moves.extend(self.gen_type_9_serial_pair()) 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_13_4_2()) - moves.extend(self.gen_type_14_4_22()) + # moves.extend(self.gen_type_13_4_2()) + # moves.extend(self.gen_type_14_4_22()) return moves diff --git a/pve_server/utils/move_selector.py b/pve_server/utils/move_selector.py index 589f13b..04ed31e 100644 --- a/pve_server/utils/move_selector.py +++ b/pve_server/utils/move_selector.py @@ -9,7 +9,6 @@ def common_handle(moves, rival_move): new_moves.append(move) return new_moves - def filter_type_1_single(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): return common_handle(moves, rival_move) - # No need to filter for type_5_king_bomb 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) return new_moves - def filter_type_7_3_2(moves, rival_move): rival_move.sort() rival_rank = rival_move[2] @@ -51,19 +48,15 @@ def filter_type_7_3_2(moves, rival_move): new_moves.append(move) return new_moves - def filter_type_8_serial_single(moves, rival_move): return common_handle(moves, rival_move) - def filter_type_9_serial_pair(moves, rival_move): return common_handle(moves, rival_move) - def filter_type_10_serial_triple(moves, rival_move): return common_handle(moves, rival_move) - def filter_type_11_serial_3_1(moves, rival_move): rival = collections.Counter(rival_move) 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) return new_moves - def filter_type_12_serial_3_2(moves, rival_move): rival = collections.Counter(rival_move) 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) return new_moves - def filter_type_13_4_2(moves, rival_move): rival_move.sort() rival_rank = rival_move[2] @@ -99,7 +90,6 @@ def filter_type_13_4_2(moves, rival_move): new_moves.append(move) return new_moves - def filter_type_14_4_22(moves, rival_move): rival = collections.Counter(rival_move) rival_rank = my_rank = 0 diff --git a/pve_server/utils/utils.py b/pve_server/utils/utils.py index c3a2be7..2e83150 100644 --- a/pve_server/utils/utils.py +++ b/pve_server/utils/utils.py @@ -10,17 +10,22 @@ TYPE_0_PASS = 0 TYPE_1_SINGLE = 1 TYPE_2_PAIR = 2 TYPE_3_TRIPLE = 3 -TYPE_4_BOMB = 4 -TYPE_5_KING_BOMB = 5 +TYPE_4_BOMB = 44 +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_7_3_2 = 7 TYPE_8_SERIAL_SINGLE = 8 TYPE_9_SERIAL_PAIR = 9 TYPE_10_SERIAL_TRIPLE = 10 -TYPE_11_SERIAL_3_1 = 11 +# TYPE_11_SERIAL_3_1 = 11 TYPE_12_SERIAL_3_2 = 12 -TYPE_13_4_2 = 13 -TYPE_14_4_22 = 14 +# TYPE_13_4_2 = 13 +# TYPE_14_4_22 = 14 TYPE_15_WRONG = 15 # betting round action diff --git a/src/assets/cards.css b/src/assets/cards.css index 23af653..7ef982c 100644 --- a/src/assets/cards.css +++ b/src/assets/cards.css @@ -642,7 +642,6 @@ .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(13) { left: 16.8em; } - .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(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(25) { left: 33.6em; } .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(2) { left: 1.1em; } @@ -684,6 +690,13 @@ .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(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 */ .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(31) { left: 60px; bottom: 30px; } .playingCards ul.deck li:nth-child(32) { left: 62px; bottom: 31px; } +.playingCards ul.deck li:nth-child(33) { left: 64px; bottom: 32px; } diff --git a/src/assets/doudizhu.scss b/src/assets/doudizhu.scss index 01b0df8..8a427d8 100644 --- a/src/assets/doudizhu.scss +++ b/src/assets/doudizhu.scss @@ -100,8 +100,8 @@ .player-info { font-size: 16px; - width: 130px; - height: 130px; + width: 80px; + height: 80px; div { text-align: center; } @@ -118,30 +118,32 @@ #left-player { position: absolute; left: 10px; - top: 10px; + top: 80px; .player-main-area { width: 300px; - height: 130px; + height: 80px; font-size: 10px; .player-hand-up { position: absolute; top: 0; - margin-left: 150px; + margin-left: 10px; + margin-top: 100px; width: 150px; } .player-hand-down { position: absolute; top: 50px; - margin-left: 150px; + margin-left: 10px; + margin-top: 100px; width: 150px; } .player-hand-placeholder { width: 150px; - height: 130px; + height: 80px; position: absolute; top: 0; margin-left: 130px; @@ -150,38 +152,40 @@ .played-card-area { position: relative; - left: 20px; - top: 20px; + left: 50px; + top: 80px; } } #right-player { position: absolute; right: 10px; - top: 10px; + top: 80px; .player-main-area { width: 300px; - height: 130px; + height: 80px; font-size: 10px; .player-hand-up { position: absolute; top: 0; - margin-right: 150px; + margin-top: 100px; + margin-right: 10px; width: 150px; } .player-hand-down { position: absolute; top: 50px; + margin-top: 100px; margin-right: 150px; width: 150px; } .player-hand-placeholder { width: 150px; - height: 130px; + height: 80px; position: absolute; top: 0; 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 { position: relative; right: 20px; - top: 20px; + top: 10px; } } @@ -203,29 +250,29 @@ position: absolute; bottom: 10px; left: 50%; - margin-left: -290px; + margin-left: -340px; .player-main-area { width: 580px; - height: 130px; + height: 80px; position: relative; .player-hand { position: absolute; top: 0; padding-left: 20px; - margin-left: 130px; + margin-left: 80px; width: 450px; font-size: 14px; } .player-hand-placeholder { width: 150px; - height: 130px; + height: 80px; position: absolute; top: 0; padding-left: 20px; - margin-left: 130px; + margin-left: 80px; } } diff --git a/src/components/GameBoard/DoudizhuGameBoard.js b/src/components/GameBoard/DoudizhuGameBoard.js index 7c68b9b..90d2822 100644 --- a/src/components/GameBoard/DoudizhuGameBoard.js +++ b/src/components/GameBoard/DoudizhuGameBoard.js @@ -163,9 +163,9 @@ class DoudizhuGameBoard extends React.Component { computeSideHand(cards) { let upCards; let downCards = []; - if (cards.length > 10) { - upCards = cards.slice(0, 10); - downCards = cards.slice(10); + if (cards.length > 16) { + upCards = cards.slice(0, 16); + downCards = cards.slice(16); } else { upCards = cards; } @@ -320,11 +320,13 @@ class DoudizhuGameBoard extends React.Component { return element.id === bottomId; }); const bottomIdx = found ? found.index : -1; - const rightIdx = bottomIdx >= 0 ? (bottomIdx + 1) % 3 : -1; - const leftIdx = rightIdx >= 0 ? (rightIdx + 1) % 3 : -1; + const rightIdx = bottomIdx >= 0 ? (bottomIdx + 1) % 4 : -1; + const topIdx = rightIdx >= 0 ? (rightIdx + 1) % 4 : -1; + const leftIdx = topIdx >= 0 ? (topIdx + 1) % 4 : -1; let rightId = -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) => { return element.index === rightIdx; }); @@ -333,6 +335,10 @@ class DoudizhuGameBoard extends React.Component { return element.index === leftIdx; }); if (found) leftId = found.id; + found = this.props.playerInfo.find((element) => { + return element.index === topIdx; + }); + if (found) topId = found.id; } return (
{rightIdx >= 0 ? this.playerDecisionArea(rightIdx) : ''}
+
+
+
{this.computePlayerPortrait(topId, topIdx)}
+ {topIdx >= 0 ? ( +
+ {this.computeSideHand(this.props.hands[topIdx])} +
+ ) : ( +
+ {t('waiting...')} +
+ )} +
+
{topIdx >= 0 ? this.playerDecisionArea(topIdx) : ''}
+
{bottomIdx >= 0 ? this.playerDecisionArea(bottomIdx) : ''} @@ -438,7 +459,12 @@ class DoudizhuGameBoard extends React.Component { + )} {this.props.gamePlayable && this.props.gameStatus === 'localeSelection' && ( diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 8550f2b..5f5d542 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -11,6 +11,7 @@ "play_as_landlord": "扮演地主", "play_as_peasant": "扮演农民", "landlord_up": "地主上家", + "landlord_front": "地主对家", "landlord_down": "地主下家", "peasants_win": "农民胜利!", "landlord_win": "地主胜利!", diff --git a/src/utils/index.js b/src/utils/index.js index b12c97f..9c87dea 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -51,12 +51,12 @@ export function translateCardData(card) { let rankText; let suitText = ''; // translate rank - if (card === 'RJ') { + if (card === 'RJ' || card === 'RJ2') { rankClass = 'big'; rankText = '+'; suitClass = 'joker'; suitText = 'Joker'; - } else if (card === 'BJ') { + } else if (card === 'BJ' || card === 'BJ2') { rankClass = 'little'; rankText = '-'; suitClass = 'joker'; @@ -67,7 +67,7 @@ export function translateCardData(card) { rankText = card.charAt(1) === 'T' ? `10` : card.charAt(1); } // translate suitClass - if (card !== 'RJ' && card !== 'BJ') { + if (card !== 'RJ' && card !== 'BJ' && card !== 'RJ2' && card !== 'BJ2') { suitClass = suitMap.get(card.charAt(0)); suitText = suitMapSymbol.get(card.charAt(0)); } @@ -101,9 +101,9 @@ export function computeHandCardsWidth(num, emWidth) { } export function card2SuiteAndRank(card) { - if (card === 'BJ' || card === 'B') { + if (card === 'BJ' || card === 'B' || card === 'BJ2' || card === 'B2') { 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' }; } else { return { suite: card[0], rank: card[1] }; @@ -165,6 +165,60 @@ export const fullDoudizhuDeck = [ 'C3', 'H3', '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 = { @@ -222,6 +276,60 @@ export const fullDoudizhuDeckIndex = { C3: 3, H3: 2, 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) { @@ -234,10 +342,38 @@ export function sortDoudizhuCards(cards, ascending = false) { } 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; + if(cards.length <= 3 || cards.length >= 9) + return -1; + const distinctedCards = [...new Set(cards.map(x=>x.substring(0, 2)))]; + 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) { diff --git a/src/view/PvEView/PvEDoudizhuDemoView.js b/src/view/PvEView/PvEDoudizhuDemoView.js index 205f75b..b7b10d0 100644 --- a/src/view/PvEView/PvEDoudizhuDemoView.js +++ b/src/view/PvEView/PvEDoudizhuDemoView.js @@ -33,7 +33,7 @@ import { douzeroDemoUrl } from '../../utils/config'; 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(); const initConsiderationTime = 30000; @@ -42,9 +42,10 @@ const mainPlayerId = 0; // index of main player (for the sake of simplify code l let playerInfo = []; let initHands = [ - shuffledDoudizhuDeck.slice(3, 20), - shuffledDoudizhuDeck.slice(20, 37), - shuffledDoudizhuDeck.slice(37, 54), + shuffledDoudizhuDeck.slice(8, 33), + shuffledDoudizhuDeck.slice(33, 58), + shuffledDoudizhuDeck.slice(58, 83), + shuffledDoudizhuDeck.slice(83, 108), ]; console.log('init hands', initHands); @@ -53,12 +54,14 @@ console.log('three landlord card', threeLandlordCards); let gameStateTimeout = null; let gameHistory = []; -let bombNum = 0; +let bombNum = [0, 0 , 0]; let lastMoveLandlord = []; let lastMoveLandlordDown = []; +let lastMoveLandlordFront = []; let lastMoveLandlordUp = []; let playedCardsLandlord = []; let playedCardsLandlordDown = []; +let playedCardsLandlordFront = []; let playedCardsLandlordUp = []; let legalActions = { turn: -1, actions: [] }; let hintIdx = -1; @@ -73,8 +76,8 @@ function PvEDoudizhuDemoView() { const [toggleFade, setToggleFade] = useState(''); const [gameStatus, setGameStatus] = useState(localStorage.getItem('LOCALE') ? 'ready' : 'localeSelection'); // "localeSelection", "ready", "playing", "paused", "over" const [gameState, setGameState] = useState({ - hands: [[], [], []], - latestAction: [[], [], []], + hands: [[], [], [], []], + latestAction: [[], [], [], []], currentPlayer: null, // index of current player turn: 0, }); @@ -90,8 +93,8 @@ function PvEDoudizhuDemoView() { const cardArr2DouzeroFormat = (cards) => { return cards .map((card) => { - if (card === 'RJ') return 'D'; - if (card === 'BJ') return 'X'; + if (card === 'RJ' || card === 'RJ2') return 'D'; + if (card === 'BJ' || card === 'BJ2') return 'X'; return card[1]; }) .join(''); @@ -122,12 +125,15 @@ function PvEDoudizhuDemoView() { } // if next player is user, get legal actions - if ((gameState.currentPlayer + 1) % 3 === mainPlayerId) { + if ((gameState.currentPlayer + 1) % 4 === mainPlayerId) { hintIdx = -1; const player_hand_cards = cardArr2DouzeroFormat(gameState.hands[mainPlayerId].slice().reverse()); let rival_move = ''; if (playingCard.length === 0) { rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 1], true)); + if(rival_move === '') { + rival_move = cardArr2DouzeroFormat(sortDoudizhuCards(gameHistory[gameHistory.length - 2], true)); + } } else { rival_move = rankOnly ? playingCard.join('') : cardArr2DouzeroFormat(playingCard); } @@ -142,7 +148,7 @@ function PvEDoudizhuDemoView() { actions: data.legal_action.split(','), }; 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 @@ -200,6 +206,10 @@ function PvEDoudizhuDemoView() { playedCardsLandlordDown = playedCardsLandlordDown.concat(newHistoryRecord); break; case 2: + lastMoveLandlordFront = newHistoryRecord; + playedCardsLandlordFront = playedCardsLandlordFront.concat(newHistoryRecord); + break; + case 3: lastMoveLandlordUp = newHistoryRecord; playedCardsLandlordUp = playedCardsLandlordUp.concat(newHistoryRecord); break; @@ -207,11 +217,12 @@ function PvEDoudizhuDemoView() { break; } gameHistory.push(newHistoryRecord); - if (isDoudizhuBomb(newHistoryRecord)) bombNum++; + const bombLevel = isDoudizhuBomb(newHistoryRecord); + if (bombLevel >= 0) bombNum[bombLevel]++; newGameState.latestAction[gameState.currentPlayer] = newLatestAction; newGameState.hands[gameState.currentPlayer] = newHand; - newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 3; + newGameState.currentPlayer = (newGameState.currentPlayer + 1) % 4; newGameState.turn++; if (newHand.length === 0) { setGameStatus('over'); @@ -241,6 +252,8 @@ function PvEDoudizhuDemoView() { landlordWinNum: 0, landlordUpGameNum: 0, landlordUpWinNum: 0, + landlordFrontGameNum: 0, + landlordFrontWinNum: 0, landlordDownGameNum: 0, landlordDownWinNum: 0, }; @@ -262,6 +275,13 @@ function PvEDoudizhuDemoView() { } break; case 2: + gameStatistics.landlordFrontGameNum += 1; + if (winner.role === playerInfo[mainPlayerId].role) { + gameStatistics.totalWinNum += 1; + gameStatistics.landlordDownWinNum += 1; + } + break; + case 3: gameStatistics.landlordUpGameNum += 1; if (winner.role === playerInfo[mainPlayerId].role) { 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'), win: gameStatistics.landlordDownWinNum, @@ -335,9 +364,11 @@ function PvEDoudizhuDemoView() { 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 = + const num_cards_left_landlord_front = 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 .map((cards) => { return cardArr2DouzeroFormat(cards); @@ -345,18 +376,21 @@ function PvEDoudizhuDemoView() { .join(','); const other_hand_cards = cardArr2DouzeroFormat( sortDoudizhuCards( - gameState.hands[(gameState.currentPlayer + 1) % 3].concat( - gameState.hands[(gameState.currentPlayer + 2) % 3], + gameState.hands[(gameState.currentPlayer + 1) % 4].concat( + gameState.hands[(gameState.currentPlayer + 2) % 4], + gameState.hands[(gameState.currentPlayer + 3) % 4], ), true, ), ); const last_move_landlord = cardArr2DouzeroFormat(lastMoveLandlord.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 bomb_num = bombNum; + const bomb_num = bombNum.join(','); const played_cards_landlord = cardArr2DouzeroFormat(playedCardsLandlord); const played_cards_landlord_down = cardArr2DouzeroFormat(playedCardsLandlordDown); + const played_cards_landlord_front = cardArr2DouzeroFormat(playedCardsLandlordFront); const played_cards_landlord_up = cardArr2DouzeroFormat(playedCardsLandlordUp); const requestBody = { @@ -364,16 +398,19 @@ function PvEDoudizhuDemoView() { player_hand_cards, num_cards_left_landlord, num_cards_left_landlord_down, + num_cards_left_landlord_front, num_cards_left_landlord_up, - three_landlord_cards, + // three_landlord_cards, card_play_action_seq, other_hand_cards, last_move_landlord, last_move_landlord_down, + last_move_landlord_front, last_move_landlord_up, bomb_num, played_cards_landlord, played_cards_landlord_down, + played_cards_landlord_front, played_cards_landlord_up, }; @@ -396,6 +433,10 @@ function PvEDoudizhuDemoView() { rival_move = cardArr2DouzeroFormat( 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 = { player_hand_cards, @@ -500,6 +541,12 @@ function PvEDoudizhuDemoView() { role: 'peasant', douzeroPlayerPosition: -1, }, + { + id: 3, + index: 3, + role: 'peasant', + douzeroPlayerPosition: -1, + }, ]; switch (role) { case 'landlord_up': @@ -511,6 +558,10 @@ function PvEDoudizhuDemoView() { playerInfo[0].role = 'landlord'; break; case 'landlord_down': + playerInfo = deepCopy(playerInfoTemplate); + playerInfo[3].role = 'landlord'; + break; + case 'landlord_front': playerInfo = deepCopy(playerInfoTemplate); playerInfo[2].role = 'landlord'; break; @@ -519,8 +570,9 @@ function PvEDoudizhuDemoView() { } const landlordIdx = playerInfo.find((player) => player.role === 'landlord').index; playerInfo[landlordIdx].douzeroPlayerPosition = 0; - playerInfo[(landlordIdx + 1) % 3].douzeroPlayerPosition = 1; - playerInfo[(landlordIdx + 2) % 3].douzeroPlayerPosition = 2; + playerInfo[(landlordIdx + 1) % 4].douzeroPlayerPosition = 1; + playerInfo[(landlordIdx + 2) % 4].douzeroPlayerPosition = 2; + playerInfo[(landlordIdx + 3) % 4].douzeroPlayerPosition = 3; initHands[landlordIdx] = initHands[landlordIdx].concat(threeLandlordCards.slice()); setGameStatus('playing'); syncGameStatus = 'playing'; @@ -556,6 +608,12 @@ function PvEDoudizhuDemoView() { total: 0, winRate: '-', }, + { + role: t('doudizhu.landlord_front'), + win: 0, + total: 0, + winRate: '-', + }, { role: t('doudizhu.landlord_down'), win: 0, @@ -575,25 +633,28 @@ function PvEDoudizhuDemoView() { // reset all game state for new game shuffledDoudizhuDeck = shuffleArray(fullDoudizhuDeck.slice()); - threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 3))); + threeLandlordCards = shuffleArray(sortDoudizhuCards(shuffledDoudizhuDeck.slice(0, 8))); originalThreeLandlordCards = threeLandlordCards.slice(); initHands = [ - shuffledDoudizhuDeck.slice(3, 20), - shuffledDoudizhuDeck.slice(20, 37), - shuffledDoudizhuDeck.slice(37, 54), + shuffledDoudizhuDeck.slice(8, 33), + shuffledDoudizhuDeck.slice(33, 58), + shuffledDoudizhuDeck.slice(58, 83), + shuffledDoudizhuDeck.slice(83, 108), ]; playerInfo = []; gameStateTimeout = null; gameHistory = []; - bombNum = 0; + bombNum = [0, 0, 0]; lastMoveLandlord = []; lastMoveLandlordDown = []; + lastMoveLandlordFront = []; lastMoveLandlordUp = []; playedCardsLandlord = []; playedCardsLandlordDown = []; + playedCardsLandlordFront = []; playedCardsLandlordUp = []; legalActions = { turn: -1, actions: [] }; @@ -602,8 +663,8 @@ function PvEDoudizhuDemoView() { setIsPassDisabled(true); setIsHintDisabled(true); setGameState({ - hands: [[], [], []], - latestAction: [[], [], []], + hands: [[], [], [], []], + latestAction: [[], [], [], []], currentPlayer: null, // index of current player turn: 0, }); @@ -961,47 +1022,12 @@ function PvEDoudizhuDemoView() { - {playerInfo.length > 0 && gameState.currentPlayer !== null ? ( -
- - {t('doudizhu.three_landlord_cards')} - -
- {sortDoudizhuCards(originalThreeLandlordCards, true).map((card) => { - const [rankClass, suitClass, rankText, suitText] = translateCardData(card); - return ( -
- {rankText} - {suitText} -
- ); - })} -
-
- ) : ( -
- {t('waiting...')} -
- )} -
{playerInfo.length > 0 && gameState.currentPlayer !== null ? ( {t( `doudizhu.${ - ['landlord', 'landlord_down', 'landlord_up'][ + ['landlord', 'landlord_down', 'landlord_front', 'landlord_up'][ playerInfo[gameState.currentPlayer].douzeroPlayerPosition ] }`, @@ -1016,6 +1042,7 @@ function PvEDoudizhuDemoView() {
{computeProbabilityItem(0)}
{computeProbabilityItem(1)}
{computeProbabilityItem(2)}
+
{computeProbabilityItem(3)}