Env
This commit is contained in:
parent
e1e727a2f3
commit
3381e96932
|
@ -1,114 +1 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
*.pyc
|
|
@ -0,0 +1 @@
|
|||
from .env import Env
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,414 @@
|
|||
from copy import deepcopy
|
||||
from . import move_detector as md, move_selector as ms
|
||||
from .move_generator import MovesGener
|
||||
import random
|
||||
|
||||
EnvCard2RealCard = {3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
|
||||
8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q',
|
||||
13: 'K', 14: 'A', 17: '2', 20: 'X', 30: 'D'}
|
||||
|
||||
RealCard2EnvCard = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
|
||||
'8': 8, '9': 9, '10': 10, 'J': 11, 'Q': 12,
|
||||
'K': 13, 'A': 14, '2': 17, 'X': 20, 'D': 30}
|
||||
|
||||
bombs = [[3, 3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5], [6, 6, 6, 6],
|
||||
[7, 7, 7, 7], [8, 8, 8, 8], [9, 9, 9, 9], [10, 10, 10, 10],
|
||||
[11, 11, 11, 11], [12, 12, 12, 12], [13, 13, 13, 13], [14, 14, 14, 14],
|
||||
[17, 17, 17, 17], [20, 30]]
|
||||
|
||||
class GameEnv(object):
|
||||
|
||||
def __init__(self, players):
|
||||
|
||||
self.card_play_action_seq = []
|
||||
|
||||
self.three_landlord_cards = None
|
||||
self.game_over = False
|
||||
|
||||
self.acting_player_position = None
|
||||
self.player_utility_dict = None
|
||||
|
||||
self.players = players
|
||||
|
||||
self.last_move_dict = {'landlord': [],
|
||||
'landlord_up': [],
|
||||
'landlord_down': []}
|
||||
|
||||
self.played_cards = {'landlord': [],
|
||||
'landlord_up': [],
|
||||
'landlord_down': []}
|
||||
|
||||
self.last_move = []
|
||||
self.last_two_moves = []
|
||||
|
||||
self.num_wins = {'landlord': 0,
|
||||
'farmer': 0}
|
||||
|
||||
self.num_scores = {'landlord': 0,
|
||||
'farmer': 0}
|
||||
|
||||
self.info_sets = {'landlord': InfoSet('landlord'),
|
||||
'landlord_up': InfoSet('landlord_up'),
|
||||
'landlord_down': InfoSet('landlord_down')}
|
||||
|
||||
self.bomb_num = 0
|
||||
self.pos_bomb_num = {
|
||||
"landlord": 0,
|
||||
"landlord_up": 0,
|
||||
"landlord_down": 0
|
||||
}
|
||||
self.last_pid = 'landlord'
|
||||
|
||||
self.bid_info = [[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1]]
|
||||
self.bid_count = 0
|
||||
self.multiply_count = {'landlord': 0,
|
||||
'landlord_up': 0,
|
||||
'landlord_down': 0}
|
||||
self.step_count = 0
|
||||
|
||||
|
||||
def card_play_init(self, card_play_data):
|
||||
self.info_sets['landlord'].player_hand_cards = \
|
||||
card_play_data['landlord']
|
||||
self.info_sets['landlord_up'].player_hand_cards = \
|
||||
card_play_data['landlord_up']
|
||||
self.info_sets['landlord_down'].player_hand_cards = \
|
||||
card_play_data['landlord_down']
|
||||
self.three_landlord_cards = card_play_data['three_landlord_cards']
|
||||
self.get_acting_player_position()
|
||||
self.game_infoset = self.get_infoset()
|
||||
|
||||
|
||||
def game_done(self):
|
||||
if len(self.info_sets['landlord'].player_hand_cards) == 0 or \
|
||||
len(self.info_sets['landlord_up'].player_hand_cards) == 0 or \
|
||||
len(self.info_sets['landlord_down'].player_hand_cards) == 0:
|
||||
# if one of the three players discards his hand,
|
||||
# then game is over.
|
||||
self.compute_player_utility()
|
||||
self.update_num_wins_scores()
|
||||
|
||||
self.game_over = True
|
||||
|
||||
def compute_player_utility(self):
|
||||
|
||||
if len(self.info_sets['landlord'].player_hand_cards) == 0:
|
||||
self.player_utility_dict = {'landlord': 2,
|
||||
'farmer': -1}
|
||||
else:
|
||||
self.player_utility_dict = {'landlord': -2,
|
||||
'farmer': 1}
|
||||
|
||||
def update_num_wins_scores(self):
|
||||
for pos, utility in self.player_utility_dict.items():
|
||||
base_score = 2 if pos == 'landlord' else 1
|
||||
if utility > 0:
|
||||
self.num_wins[pos] += 1
|
||||
self.winner = pos
|
||||
self.num_scores[pos] += base_score * (2 ** self.bomb_num)
|
||||
else:
|
||||
self.num_scores[pos] -= base_score * (2 ** self.bomb_num)
|
||||
|
||||
def get_winner(self):
|
||||
return self.winner
|
||||
|
||||
def get_bomb_num(self):
|
||||
return self.bomb_num
|
||||
|
||||
def step(self):
|
||||
action = self.players[self.acting_player_position].act(
|
||||
self.game_infoset)
|
||||
self.step_count += 1
|
||||
if len(action) > 0:
|
||||
self.last_pid = self.acting_player_position
|
||||
|
||||
if action in bombs:
|
||||
self.bomb_num += 1
|
||||
self.pos_bomb_num[self.acting_player_position] += 1
|
||||
|
||||
self.last_move_dict[
|
||||
self.acting_player_position] = action.copy()
|
||||
|
||||
self.card_play_action_seq.append((self.acting_player_position, action))
|
||||
self.update_acting_player_hand_cards(action)
|
||||
|
||||
self.played_cards[self.acting_player_position] += action
|
||||
|
||||
if self.acting_player_position == 'landlord' and \
|
||||
len(action) > 0 and \
|
||||
len(self.three_landlord_cards) > 0:
|
||||
for card in action:
|
||||
if len(self.three_landlord_cards) > 0:
|
||||
if card in self.three_landlord_cards:
|
||||
self.three_landlord_cards.remove(card)
|
||||
else:
|
||||
break
|
||||
|
||||
self.game_done()
|
||||
if not self.game_over:
|
||||
self.get_acting_player_position()
|
||||
self.game_infoset = self.get_infoset()
|
||||
return action
|
||||
|
||||
def get_last_move(self):
|
||||
last_move = []
|
||||
if len(self.card_play_action_seq) != 0:
|
||||
if len(self.card_play_action_seq[-1][1]) == 0:
|
||||
last_move = self.card_play_action_seq[-2][1]
|
||||
else:
|
||||
last_move = self.card_play_action_seq[-1][1]
|
||||
|
||||
return last_move
|
||||
|
||||
def get_last_two_moves(self):
|
||||
last_two_moves = [[], []]
|
||||
for card in self.card_play_action_seq[-2:]:
|
||||
last_two_moves.insert(0, card[1])
|
||||
last_two_moves = last_two_moves[:2]
|
||||
return last_two_moves
|
||||
|
||||
def get_acting_player_position(self):
|
||||
if self.acting_player_position is None:
|
||||
self.acting_player_position = 'landlord'
|
||||
|
||||
else:
|
||||
if self.acting_player_position == 'landlord':
|
||||
self.acting_player_position = 'landlord_down'
|
||||
|
||||
elif self.acting_player_position == 'landlord_down':
|
||||
self.acting_player_position = 'landlord_up'
|
||||
|
||||
else:
|
||||
self.acting_player_position = 'landlord'
|
||||
|
||||
return self.acting_player_position
|
||||
|
||||
def update_acting_player_hand_cards(self, action):
|
||||
if action != []:
|
||||
for card in action:
|
||||
self.info_sets[
|
||||
self.acting_player_position].player_hand_cards.remove(card)
|
||||
self.info_sets[self.acting_player_position].player_hand_cards.sort()
|
||||
|
||||
def get_legal_card_play_actions(self):
|
||||
mg = MovesGener(
|
||||
self.info_sets[self.acting_player_position].player_hand_cards)
|
||||
|
||||
action_sequence = self.card_play_action_seq
|
||||
|
||||
rival_move = []
|
||||
if len(action_sequence) != 0:
|
||||
if len(action_sequence[-1][1]) == 0:
|
||||
rival_move = action_sequence[-2][1]
|
||||
else:
|
||||
rival_move = action_sequence[-1][1]
|
||||
|
||||
rival_type = md.get_move_type(rival_move)
|
||||
rival_move_type = rival_type['type']
|
||||
rival_move_len = rival_type.get('len', 1)
|
||||
moves = list()
|
||||
|
||||
if rival_move_type == md.TYPE_0_PASS:
|
||||
moves = mg.gen_moves()
|
||||
|
||||
elif rival_move_type == md.TYPE_1_SINGLE:
|
||||
all_moves = mg.gen_type_1_single()
|
||||
moves = ms.filter_type_1_single(all_moves, rival_move)
|
||||
|
||||
elif rival_move_type == md.TYPE_2_PAIR:
|
||||
all_moves = mg.gen_type_2_pair()
|
||||
moves = ms.filter_type_2_pair(all_moves, rival_move)
|
||||
|
||||
elif rival_move_type == md.TYPE_3_TRIPLE:
|
||||
all_moves = mg.gen_type_3_triple()
|
||||
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()
|
||||
moves = ms.filter_type_4_bomb(all_moves, rival_move)
|
||||
|
||||
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)
|
||||
|
||||
elif rival_move_type == md.TYPE_8_SERIAL_SINGLE:
|
||||
all_moves = mg.gen_type_8_serial_single(repeat_num=rival_move_len)
|
||||
moves = ms.filter_type_8_serial_single(all_moves, rival_move)
|
||||
|
||||
elif rival_move_type == md.TYPE_9_SERIAL_PAIR:
|
||||
all_moves = mg.gen_type_9_serial_pair(repeat_num=rival_move_len)
|
||||
moves = ms.filter_type_9_serial_pair(all_moves, rival_move)
|
||||
|
||||
elif rival_move_type == md.TYPE_10_SERIAL_TRIPLE:
|
||||
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 len(rival_move) != 0: # rival_move is not 'pass'
|
||||
moves = moves + [[]]
|
||||
|
||||
for m in moves:
|
||||
m.sort()
|
||||
|
||||
return moves
|
||||
|
||||
def reset(self):
|
||||
self.card_play_action_seq = []
|
||||
|
||||
self.three_landlord_cards = None
|
||||
self.game_over = False
|
||||
|
||||
self.acting_player_position = None
|
||||
self.player_utility_dict = None
|
||||
|
||||
self.last_move_dict = {'landlord': [],
|
||||
'landlord_up': [],
|
||||
'landlord_down': []}
|
||||
|
||||
self.played_cards = {'landlord': [],
|
||||
'landlord_up': [],
|
||||
'landlord_down': []}
|
||||
|
||||
self.last_move = []
|
||||
self.last_two_moves = []
|
||||
|
||||
self.info_sets = {'landlord': InfoSet('landlord'),
|
||||
'landlord_up': InfoSet('landlord_up'),
|
||||
'landlord_down': InfoSet('landlord_down')}
|
||||
|
||||
self.bomb_num = 0
|
||||
self.pos_bomb_num = {
|
||||
"landlord": 0,
|
||||
"landlord_up": 0,
|
||||
"landlord_down": 0
|
||||
}
|
||||
self.last_pid = 'landlord'
|
||||
self.bid_info = [[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1]]
|
||||
self.bid_count = 0
|
||||
self.multiply_count = {'landlord': 0,
|
||||
'landlord_up': 0,
|
||||
'landlord_down': 0}
|
||||
self.step_count = 0
|
||||
|
||||
def get_infoset(self):
|
||||
self.info_sets[
|
||||
self.acting_player_position].last_pid = self.last_pid
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].legal_actions = \
|
||||
self.get_legal_card_play_actions()
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].bomb_num = self.bomb_num
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].last_move = self.get_last_move()
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].last_two_moves = self.get_last_two_moves()
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].last_move_dict = self.last_move_dict
|
||||
|
||||
self.info_sets[self.acting_player_position].num_cards_left_dict = \
|
||||
{pos: len(self.info_sets[pos].player_hand_cards)
|
||||
for pos in ['landlord', 'landlord_up', 'landlord_down']}
|
||||
|
||||
self.info_sets[self.acting_player_position].other_hand_cards = []
|
||||
for pos in ['landlord', 'landlord_up', 'landlord_down']:
|
||||
if pos != self.acting_player_position:
|
||||
self.info_sets[
|
||||
self.acting_player_position].other_hand_cards += \
|
||||
self.info_sets[pos].player_hand_cards
|
||||
|
||||
self.info_sets[self.acting_player_position].played_cards = \
|
||||
self.played_cards
|
||||
self.info_sets[self.acting_player_position].three_landlord_cards = \
|
||||
self.three_landlord_cards
|
||||
self.info_sets[self.acting_player_position].card_play_action_seq = \
|
||||
self.card_play_action_seq
|
||||
|
||||
self.info_sets[
|
||||
self.acting_player_position].all_handcards = \
|
||||
{pos: self.info_sets[pos].player_hand_cards
|
||||
for pos in ['landlord', 'landlord_up', 'landlord_down']}
|
||||
|
||||
return deepcopy(self.info_sets[self.acting_player_position])
|
||||
|
||||
class InfoSet(object):
|
||||
"""
|
||||
The game state is described as infoset, which
|
||||
includes all the information in the current situation,
|
||||
such as the hand cards of the three players, the
|
||||
historical moves, etc.
|
||||
"""
|
||||
def __init__(self, player_position):
|
||||
# The player position, i.e., landlord, landlord_down, or landlord_up
|
||||
self.player_position = player_position
|
||||
# The hand cands of the current player. A list.
|
||||
self.player_hand_cards = None
|
||||
# The number of cards left for each player. It is a dict with str-->int
|
||||
self.num_cards_left_dict = None
|
||||
# The three landload cards. A list.
|
||||
self.three_landlord_cards = None
|
||||
# The historical moves. It is a list of list
|
||||
self.card_play_action_seq = None
|
||||
# The union of the hand cards of the other two players for the current player
|
||||
self.other_hand_cards = None
|
||||
# The legal actions for the current move. It is a list of list
|
||||
self.legal_actions = None
|
||||
# The most recent valid move
|
||||
self.last_move = None
|
||||
# The most recent two moves
|
||||
self.last_two_moves = None
|
||||
# The last moves for all the postions
|
||||
self.last_move_dict = None
|
||||
# The played cands so far. It is a list.
|
||||
self.played_cards = None
|
||||
# The hand cards of all the players. It is a dict.
|
||||
self.all_handcards = None
|
||||
# Last player position that plays a valid move, i.e., not `pass`
|
||||
self.last_pid = None
|
||||
# The number of bombs played so far
|
||||
self.bomb_num = None
|
||||
|
||||
self.bid_info = [[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1],
|
||||
[-1, -1, -1]]
|
||||
|
||||
self.multiply_info = [1, 0, 0]
|
||||
|
||||
self.player_id = None
|
|
@ -0,0 +1,107 @@
|
|||
from douzero.env.utils import *
|
||||
import collections
|
||||
|
||||
# check if move is a continuous sequence
|
||||
def is_continuous_seq(move):
|
||||
i = 0
|
||||
while i < len(move) - 1:
|
||||
if move[i+1] - move[i] != 1:
|
||||
return False
|
||||
i += 1
|
||||
return True
|
||||
|
||||
# return the type of the move
|
||||
def get_move_type(move):
|
||||
move_size = len(move)
|
||||
move_dict = collections.Counter(move)
|
||||
|
||||
if move_size == 0:
|
||||
return {'type': TYPE_0_PASS}
|
||||
|
||||
if move_size == 1:
|
||||
return {'type': TYPE_1_SINGLE, 'rank': move[0]}
|
||||
|
||||
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}
|
||||
else:
|
||||
return {'type': TYPE_15_WRONG}
|
||||
|
||||
if move_size == 3:
|
||||
if len(move_dict) == 1:
|
||||
return {'type': TYPE_3_TRIPLE, 'rank': move[0]}
|
||||
else:
|
||||
return {'type': TYPE_15_WRONG}
|
||||
|
||||
if move_size == 4:
|
||||
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]}
|
||||
else:
|
||||
return {'type': TYPE_15_WRONG}
|
||||
else:
|
||||
return {'type': TYPE_15_WRONG}
|
||||
|
||||
if is_continuous_seq(move):
|
||||
return {'type': TYPE_8_SERIAL_SINGLE, 'rank': move[0], 'len': len(move)}
|
||||
|
||||
if move_size == 5:
|
||||
if len(move_dict) == 2:
|
||||
return {'type': TYPE_7_3_2, 'rank': move[2]}
|
||||
else:
|
||||
return {'type': TYPE_15_WRONG}
|
||||
|
||||
count_dict = collections.defaultdict(int)
|
||||
for c, n in move_dict.items():
|
||||
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 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])}
|
||||
|
||||
mdkeys = sorted(move_dict.keys())
|
||||
if len(move_dict) == count_dict.get(2) and is_continuous_seq(mdkeys):
|
||||
return {'type': TYPE_9_SERIAL_PAIR, 'rank': mdkeys[0], 'len': len(mdkeys)}
|
||||
|
||||
if len(move_dict) == count_dict.get(3) and is_continuous_seq(mdkeys):
|
||||
return {'type': TYPE_10_SERIAL_TRIPLE, 'rank': mdkeys[0], 'len': len(mdkeys)}
|
||||
|
||||
# Check Type 11 (serial 3+1) and Type 12 (serial 3+2)
|
||||
if count_dict.get(3, 0) >= MIN_TRIPLES:
|
||||
serial_3 = list()
|
||||
single = list()
|
||||
pair = list()
|
||||
|
||||
for k, v in move_dict.items():
|
||||
if v == 3:
|
||||
serial_3.append(k)
|
||||
elif v == 1:
|
||||
single.append(k)
|
||||
elif v == 2:
|
||||
pair.append(k)
|
||||
else: # no other possibilities
|
||||
return {'type': TYPE_15_WRONG}
|
||||
|
||||
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(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}
|
||||
|
||||
return {'type': TYPE_15_WRONG}
|
|
@ -0,0 +1,219 @@
|
|||
from douzero.env.utils import MIN_SINGLE_CARDS, MIN_PAIRS, MIN_TRIPLES, select
|
||||
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)
|
||||
|
||||
for i in self.cards_list:
|
||||
self.cards_dict[i] += 1
|
||||
|
||||
self.single_card_moves = []
|
||||
self.gen_type_1_single()
|
||||
self.pair_moves = []
|
||||
self.gen_type_2_pair()
|
||||
self.triple_cards_moves = []
|
||||
self.gen_type_3_triple()
|
||||
self.bomb_moves = []
|
||||
self.gen_type_4_bomb()
|
||||
self.final_bomb_moves = []
|
||||
self.gen_type_5_king_bomb()
|
||||
|
||||
def _gen_serial_moves(self, cards, min_serial, repeat=1, repeat_num=0):
|
||||
if repeat_num < min_serial: # at least repeat_num is min_serial
|
||||
repeat_num = 0
|
||||
|
||||
single_cards = sorted(list(set(cards)))
|
||||
seq_records = list()
|
||||
moves = list()
|
||||
|
||||
start = i = 0
|
||||
longest = 1
|
||||
while i < len(single_cards):
|
||||
if i + 1 < len(single_cards) and single_cards[i + 1] - single_cards[i] == 1:
|
||||
longest += 1
|
||||
i += 1
|
||||
else:
|
||||
seq_records.append((start, longest))
|
||||
i += 1
|
||||
start = i
|
||||
longest = 1
|
||||
|
||||
for seq in seq_records:
|
||||
if seq[1] < min_serial:
|
||||
continue
|
||||
start, longest = seq[0], seq[1]
|
||||
longest_list = single_cards[start: start + longest]
|
||||
|
||||
if repeat_num == 0: # No limitation on how many sequences
|
||||
steps = min_serial
|
||||
while steps <= longest:
|
||||
index = 0
|
||||
while steps + index <= longest:
|
||||
target_moves = sorted(longest_list[index: index + steps] * repeat)
|
||||
moves.append(target_moves)
|
||||
index += 1
|
||||
steps += 1
|
||||
|
||||
else: # repeat_num > 0
|
||||
if longest < repeat_num:
|
||||
continue
|
||||
index = 0
|
||||
while index + repeat_num <= longest:
|
||||
target_moves = sorted(longest_list[index: index + repeat_num] * repeat)
|
||||
moves.append(target_moves)
|
||||
index += 1
|
||||
|
||||
return moves
|
||||
|
||||
def gen_type_1_single(self):
|
||||
self.single_card_moves = []
|
||||
for i in set(self.cards_list):
|
||||
self.single_card_moves.append([i])
|
||||
return self.single_card_moves
|
||||
|
||||
def gen_type_2_pair(self):
|
||||
self.pair_moves = []
|
||||
for k, v in self.cards_dict.items():
|
||||
if v >= 2:
|
||||
self.pair_moves.append([k, k])
|
||||
return self.pair_moves
|
||||
|
||||
def gen_type_3_triple(self):
|
||||
self.triple_cards_moves = []
|
||||
for k, v in self.cards_dict.items():
|
||||
if v >= 3:
|
||||
self.triple_cards_moves.append([k, k, k])
|
||||
return self.triple_cards_moves
|
||||
|
||||
def gen_type_4_bomb(self):
|
||||
self.bomb_moves = []
|
||||
for k, v in self.cards_dict.items():
|
||||
if v == 4:
|
||||
self.bomb_moves.append([k, k, k, k])
|
||||
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])
|
||||
return self.final_bomb_moves
|
||||
|
||||
def gen_type_6_3_1(self):
|
||||
result = []
|
||||
for t in self.single_card_moves:
|
||||
for i in self.triple_cards_moves:
|
||||
if t[0] != i[0]:
|
||||
result.append(t+i)
|
||||
return result
|
||||
|
||||
def gen_type_7_3_2(self):
|
||||
result = list()
|
||||
for t in self.pair_moves:
|
||||
for i in self.triple_cards_moves:
|
||||
if t[0] != i[0]:
|
||||
result.append(t+i)
|
||||
return result
|
||||
|
||||
def gen_type_8_serial_single(self, repeat_num=0):
|
||||
return self._gen_serial_moves(self.cards_list, MIN_SINGLE_CARDS, repeat=1, repeat_num=repeat_num)
|
||||
|
||||
def gen_type_9_serial_pair(self, repeat_num=0):
|
||||
single_pairs = list()
|
||||
for k, v in self.cards_dict.items():
|
||||
if v >= 2:
|
||||
single_pairs.append(k)
|
||||
|
||||
return self._gen_serial_moves(single_pairs, MIN_PAIRS, repeat=2, repeat_num=repeat_num)
|
||||
|
||||
def gen_type_10_serial_triple(self, repeat_num=0):
|
||||
single_triples = list()
|
||||
for k, v in self.cards_dict.items():
|
||||
if v >= 3:
|
||||
single_triples.append(k)
|
||||
|
||||
return self._gen_serial_moves(single_triples, MIN_TRIPLES, repeat=3, repeat_num=repeat_num)
|
||||
|
||||
def gen_type_11_serial_3_1(self, repeat_num=0):
|
||||
serial_3_moves = self.gen_type_10_serial_triple(repeat_num=repeat_num)
|
||||
serial_3_1_moves = list()
|
||||
|
||||
for s3 in serial_3_moves: # s3 is like [3,3,3,4,4,4]
|
||||
s3_set = set(s3)
|
||||
new_cards = [i for i in self.cards_list if i not in s3_set]
|
||||
|
||||
# Get any s3_len items from cards
|
||||
subcards = select(new_cards, len(s3_set))
|
||||
|
||||
for i in subcards:
|
||||
serial_3_1_moves.append(s3 + i)
|
||||
|
||||
return list(k for k, _ in itertools.groupby(serial_3_1_moves))
|
||||
|
||||
def gen_type_12_serial_3_2(self, repeat_num=0):
|
||||
serial_3_moves = self.gen_type_10_serial_triple(repeat_num=repeat_num)
|
||||
serial_3_2_moves = list()
|
||||
pair_set = sorted([k for k, v in self.cards_dict.items() if v >= 2])
|
||||
|
||||
for s3 in serial_3_moves:
|
||||
s3_set = set(s3)
|
||||
pair_candidates = [i for i in pair_set if i not in s3_set]
|
||||
|
||||
# Get any s3_len items from cards
|
||||
subcards = select(pair_candidates, len(s3_set))
|
||||
for i in subcards:
|
||||
serial_3_2_moves.append(sorted(s3 + i * 2))
|
||||
|
||||
return serial_3_2_moves
|
||||
|
||||
def gen_type_13_4_2(self):
|
||||
four_cards = list()
|
||||
for k, v in self.cards_dict.items():
|
||||
if v == 4:
|
||||
four_cards.append(k)
|
||||
|
||||
result = list()
|
||||
for fc in four_cards:
|
||||
cards_list = [k for k in self.cards_list if k != fc]
|
||||
subcards = select(cards_list, 2)
|
||||
for i in subcards:
|
||||
result.append([fc]*4 + i)
|
||||
return list(k for k, _ in itertools.groupby(result))
|
||||
|
||||
def gen_type_14_4_22(self):
|
||||
four_cards = list()
|
||||
for k, v in self.cards_dict.items():
|
||||
if v == 4:
|
||||
four_cards.append(k)
|
||||
|
||||
result = list()
|
||||
for fc in four_cards:
|
||||
cards_list = [k for k, v in self.cards_dict.items() if k != fc and v>=2]
|
||||
subcards = select(cards_list, 2)
|
||||
for i in subcards:
|
||||
result.append([fc] * 4 + [i[0], i[0], i[1], i[1]])
|
||||
return result
|
||||
|
||||
# generate all possible moves from given cards
|
||||
def gen_moves(self):
|
||||
moves = []
|
||||
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_5_king_bomb())
|
||||
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_12_serial_3_2())
|
||||
moves.extend(self.gen_type_13_4_2())
|
||||
moves.extend(self.gen_type_14_4_22())
|
||||
return moves
|
|
@ -0,0 +1,106 @@
|
|||
# return all moves that can beat rivals, moves and rival_move should be same type
|
||||
import collections
|
||||
|
||||
def common_handle(moves, rival_move):
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
if move[0] > rival_move[0]:
|
||||
new_moves.append(move)
|
||||
return new_moves
|
||||
|
||||
def filter_type_1_single(moves, rival_move):
|
||||
return common_handle(moves, rival_move)
|
||||
|
||||
|
||||
def filter_type_2_pair(moves, rival_move):
|
||||
return common_handle(moves, rival_move)
|
||||
|
||||
|
||||
def filter_type_3_triple(moves, rival_move):
|
||||
return common_handle(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):
|
||||
rival_move.sort()
|
||||
rival_rank = rival_move[1]
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
move.sort()
|
||||
my_rank = move[1]
|
||||
if my_rank > rival_rank:
|
||||
new_moves.append(move)
|
||||
return new_moves
|
||||
|
||||
def filter_type_7_3_2(moves, rival_move):
|
||||
rival_move.sort()
|
||||
rival_rank = rival_move[2]
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
move.sort()
|
||||
my_rank = move[2]
|
||||
if my_rank > rival_rank:
|
||||
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])
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
mymove = collections.Counter(move)
|
||||
my_rank = max([k for k, v in mymove.items() if v == 3])
|
||||
if my_rank > rival_rank:
|
||||
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])
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
mymove = collections.Counter(move)
|
||||
my_rank = max([k for k, v in mymove.items() if v == 3])
|
||||
if my_rank > rival_rank:
|
||||
new_moves.append(move)
|
||||
return new_moves
|
||||
|
||||
def filter_type_13_4_2(moves, rival_move):
|
||||
rival_move.sort()
|
||||
rival_rank = rival_move[2]
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
move.sort()
|
||||
my_rank = move[2]
|
||||
if my_rank > rival_rank:
|
||||
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
|
||||
for k, v in rival.items():
|
||||
if v == 4:
|
||||
rival_rank = k
|
||||
new_moves = list()
|
||||
for move in moves:
|
||||
mymove = collections.Counter(move)
|
||||
for k, v in mymove.items():
|
||||
if v == 4:
|
||||
my_rank = k
|
||||
if my_rank > rival_rank:
|
||||
new_moves.append(move)
|
||||
return new_moves
|
|
@ -0,0 +1,33 @@
|
|||
import itertools
|
||||
|
||||
# global parameters
|
||||
MIN_SINGLE_CARDS = 5
|
||||
MIN_PAIRS = 3
|
||||
MIN_TRIPLES = 2
|
||||
|
||||
# action types
|
||||
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_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_12_SERIAL_3_2 = 12
|
||||
TYPE_13_4_2 = 13
|
||||
TYPE_14_4_22 = 14
|
||||
TYPE_15_WRONG = 15
|
||||
|
||||
# betting round action
|
||||
PASS = 0
|
||||
CALL = 1
|
||||
RAISE = 2
|
||||
|
||||
# return all possible results of selecting num cards from cards list
|
||||
def select(cards, num):
|
||||
return [list(i) for i in itertools.combinations(cards, num)]
|
Loading…
Reference in New Issue