Skip to content

Commit

Permalink
Show value of each choice. New way of getting tehai.
Browse files Browse the repository at this point in the history
  • Loading branch information
shinkuan committed Feb 25, 2024
1 parent 0aa81a4 commit 33f6b68
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 79 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/mitm_playwright.bat
/playwright_test.py
/unlocker_test.py
/unlocker_passthrough.py
/objects
/players/bot
/players/docker
Expand Down
52 changes: 27 additions & 25 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@
from textual.screen import Screen

from liqi import LiqiProto, MsgType
from mjai.player import MjaiPlayerClient
from majsoul2mjai import MajsoulBridge
from libriichi_helper import meta_to_recommend, state_to_tehai
from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE
from action import Action
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
from playwright.sync_api import Playwright, sync_playwright

submission = 'players/bot.zip'
PORT_NUM = 28680
Expand Down Expand Up @@ -132,9 +129,6 @@ def refresh_log(self) -> None:
self.liqi_log.update(self.app.liqi_msg_dict[self.flow_id][-1])
self.liqi_log_container.scroll_end()
self.liqi_msg_idx += 1
for idx, tehai_label in enumerate(self.tehai_labels):
tehai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]])
self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai])
liqi_msg = self.app.liqi_msg_dict[self.flow_id][-1]
if liqi_msg['type'] == MsgType.Notify:
if liqi_msg['method'] == '.lq.ActionPrototype':
Expand All @@ -153,46 +147,54 @@ def refresh_log(self) -> None:
elif self.syncing:
self.query_one("#loading_indicator").remove()
self.syncing = False
if AUTOPLAY:
if AUTOPLAY and len(self.app.mjai_msg_dict[self.flow_id]) > 0:
logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1])
self.app.set_timer(2, self.autoplay)
if self.mjai_msg_idx < len(self.app.mjai_msg_dict[self.flow_id]):
player_state = self.app.bridge[self.flow_id].mjai_client.bot.state()
tehai, tsumohai = state_to_tehai(player_state)
for idx, tehai_label in enumerate(self.tehai_labels):
tehai_label.update(TILE_2_UNICODE_ART_RICH[tehai[idx]])
self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[tsumohai])
latest_mjai_msg = self.app.mjai_msg_dict[self.flow_id][-1]
self.app.mjai_msg_dict[self.flow_id][-1]['meta'] = meta_to_recommend(latest_mjai_msg['meta'])
self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id])
self.mjai_log_container.scroll_end()
self.mjai_msg_idx += 1
self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"]
self.akagi_action.label = latest_mjai_msg["type"]
for akagi_action_class in self.akagi_action.classes:
self.akagi_action.remove_class(akagi_action_class)
self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"])
self.akagi_action.add_class("action_"+latest_mjai_msg["type"])
for akagi_pai_class in self.akagi_pai.classes:
self.akagi_pai.remove_class(akagi_pai_class)
self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"])
if "consumed" in self.app.mjai_msg_dict[self.flow_id][-1]:
self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["consumed"])
if "pai" in self.app.mjai_msg_dict[self.flow_id][-1]:
self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]])
self.akagi_pai.add_class("pai_"+latest_mjai_msg["type"])
if "consumed" in latest_mjai_msg:
self.akagi_pai.label = str(latest_mjai_msg["consumed"])
if "pai" in latest_mjai_msg:
self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[latest_mjai_msg["pai"]])
self.akagi_container.mount(Label(VERTICLE_RULE, id="consumed_rule"))
self.consume_ids.append("#"+"consumed_rule")
i=0
for c in self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]:
for c in latest_mjai_msg["consumed"]:
self.akagi_container.mount(Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i)))
self.consume_ids.append("#"+"consumed_"+c+str(i))
i+=1
elif "pai" in self.app.mjai_msg_dict[self.flow_id][-1]:
elif "pai" in latest_mjai_msg:
for consume_id in self.consume_ids:
self.query_one(consume_id).remove()
self.consume_ids = []
self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["pai"])
self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]])
self.akagi_pai.label = str(latest_mjai_msg["pai"])
self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[latest_mjai_msg["pai"]])
else:
self.akagi_pai.label = "None"
self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH["?"])
# Action
logger.info(f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}")
logger.info(f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}")
logger.info(f"Current tehai: {tehai}")
logger.info(f"Current tsumohai: {tsumohai}")
if not self.syncing and ENABLE_PLAYWRIGHT and AUTOPLAY:
logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1])
self.app.set_timer(0.05, self.autoplay)
logger.log("CLICK", latest_mjai_msg)
# self.app.set_timer(0.05, self.autoplay)
self.autoplay(tehai, tsumohai)

except Exception as e:
logger.error(e)
Expand All @@ -204,8 +206,8 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None:
AUTOPLAY = event.value
pass

def autoplay(self) -> None:
self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai)
def autoplay(self, tehai, tsumohai) -> None:
self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], tehai, tsumohai)
pass

def action_quit(self) -> None:
Expand Down
121 changes: 121 additions & 0 deletions libriichi_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import numpy as np

def meta_to_recommend(meta: dict) -> dict:
# """
# {
# "q_values":[
# -9.09196,
# -9.46696,
# -8.365397,
# -8.849772,
# -9.43571,
# -10.06071,
# -9.295085,
# -0.73649096,
# -9.27946,
# -9.357585,
# 0.3221028,
# -2.7794597
# ],
# "mask_bits":2697207348,
# "is_greedy":true,
# "eval_time_ns":357088300
# }
# """

recommend = []

mask_unicode = ['🀇', '🀈', '🀉', '🀊', '🀋', '🀌', '🀍', '🀎', '🀏', '🀙', '🀚', '🀛', '🀜', '🀝', '🀞', '🀟', '🀠', '🀡', '🀐', '🀑', '🀒', '🀓', '🀔', '🀕', '🀖', '🀗', '🀘', '🀀', '🀁', '🀂', '🀃', '🀆', '🀅', '🀄', '🀋', '🀝', '🀔', '立直', '吃(下)', '吃(中)', '吃(上)', '碰', '槓(選擇)', '和', '流局', '跳過']

def mask_bits_to_binary_string(mask_bits):
binary_string = bin(mask_bits)[2:]
binary_string = binary_string.zfill(46)
return binary_string

def mask_bits_to_bool_list(mask_bits):
binary_string = mask_bits_to_binary_string(mask_bits)
bool_list = []
for bit in binary_string[::-1]:
bool_list.append(bit == '1')
return bool_list

def eq(l, r):
# Check for approximate equality using numpy's floating-point epsilon
return np.abs(l - r) <= np.finfo(float).eps

def softmax(arr, temperature=1.0):
arr = np.array(arr, dtype=float) # Ensure the input is a numpy array of floats

if arr.size == 0:
return arr # Return the empty array if input is empty

if not eq(temperature, 1.0):
arr /= temperature # Scale by temperature if temperature is not approximately 1

# Shift values by max for numerical stability
max_val = np.max(arr)
arr = arr - max_val

# Apply the softmax transformation
exp_arr = np.exp(arr)
sum_exp = np.sum(exp_arr)

softmax_arr = exp_arr / sum_exp

return softmax_arr

def scale_list(list):
scaled_list = softmax(list)
return scaled_list
q_values = meta['q_values']
mask_bits = meta['mask_bits']
mask = mask_bits_to_bool_list(mask_bits)
scaled_q_values = scale_list(q_values)
q_value_idx = 0

true_count = 0
for i in range(46):
if mask[i]:
true_count += 1

for i in range(46):
if mask[i]:
recommend.append((mask_unicode[i], scaled_q_values[q_value_idx]))
q_value_idx += 1

recommend = sorted(recommend, key=lambda x: x[1], reverse=True)
return recommend

def state_to_tehai(state) -> tuple[list[str], str]:
tehai34 = state.tehai # with tsumohai, no aka marked
akas = state.akas_in_hand
tsumohai = state.last_self_tsumo()
return _state_to_tehai(tehai34, akas, tsumohai)

def _state_to_tehai(tile34: int, aka: list[bool], tsumohai: str|None) -> tuple[list[str], str]:
pai_str = [
"1m", "2m", "3m", "4m", "5m", "6m", "7m", "8m", "9m",
"1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p",
"1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s",
"E", "S", "W", "N", "P", "F", "C", "?"
]
aka_str = [
"5mr", "5pr", "5sr"
]
tile_list = []
for tile_id, tile_count in enumerate(tile34):
for _ in range(tile_count):
tile_list.append(pai_str[tile_id])
for idx, aka in enumerate(aka):
if aka:
tile_list[tile_list.index("5" + ["m", "p", "s"][idx])] = aka_str[idx]
if len(tile_list)%3 == 2 and tsumohai is not None:
tile_list.remove(tsumohai)
else:
tsumohai = "?"
len_tile_list = len(tile_list)
if len_tile_list < 13:
tile_list += ["?"]*(13-len_tile_list)

return (tile_list, tsumohai)

51 changes: 0 additions & 51 deletions majsoul2mjai.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ def input(self, parse_msg: dict) -> dict | None:
my_tehais = ['?']*13
for hai in range(13):
my_tehais[hai] = MS_TILE_2_MJAI_TILE[parse_msg['data']['data']['tiles'][hai]]
self.my_tehais = my_tehais
self.my_tsumohai = "?"
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))
if len(parse_msg['data']['data']['tiles']) == 13:
tehais[self.seat] = my_tehais
self.mjai_message.append(
Expand Down Expand Up @@ -210,14 +207,6 @@ def input(self, parse_msg: dict) -> dict | None:
'type': 'reach_accepted',
'actor': actor
}
if actor == self.seat:
if self.my_tsumohai != "?":
self.my_tehais.append(self.my_tsumohai)
self.my_tsumohai = "?"
else:
self.my_tehais.append("?")
self.my_tehais.remove(pai)
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))
# Reach
if parse_msg['data']['name'] == 'ActionReach':
# TODO
Expand Down Expand Up @@ -275,11 +264,6 @@ def input(self, parse_msg: dict) -> dict | None:
pass
case _:
raise
if actor == self.seat:
for pai in consumed:
self.my_tehais.remove(pai)
self.my_tehais.append("?")
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))
# AnkanKakan
if parse_msg['data']['name'] == 'ActionAnGangAddGang':
actor = parse_msg['data']['data']['seat']
Expand All @@ -296,17 +280,6 @@ def input(self, parse_msg: dict) -> dict | None:
'consumed': consumed
}
)
if actor == self.seat:
if self.my_tsumohai != "?":
self.my_tehais.append(self.my_tsumohai)
self.my_tsumohai = "?"
else:
self.my_tehais.append("?")
for pai in consumed:
self.my_tehais.remove(pai)
self.my_tehais.append("?")
self.my_tehais.remove("?")
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))
case OperationAnGangAddGang.AddGang:
pai = MS_TILE_2_MJAI_TILE[parse_msg['data']['data']['tiles']]
consumed = [pai.replace("r", "")] * 3
Expand All @@ -320,14 +293,6 @@ def input(self, parse_msg: dict) -> dict | None:
'consumed': consumed
}
)
if actor == self.seat:
if self.my_tsumohai != "?":
self.my_tehais.append(self.my_tsumohai)
self.my_tsumohai = "?"
else:
self.my_tehais.append("?")
self.my_tehais.remove(pai)
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))

if parse_msg['data']['name'] == 'ActionBaBei':
actor = parse_msg['data']['data']['seat']
Expand All @@ -338,14 +303,6 @@ def input(self, parse_msg: dict) -> dict | None:
'pai': 'N'
}
)
if actor == self.seat:
if self.my_tsumohai != "?":
self.my_tehais.append(self.my_tsumohai)
self.my_tsumohai = "?"
else:
self.my_tehais.append("?")
self.my_tehais.remove("N")
self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai))

# hora
if parse_msg['data']['name'] == 'ActionHule':
Expand All @@ -368,8 +325,6 @@ def input(self, parse_msg: dict) -> dict | None:
'type': 'end_kyoku'
}
)
self.my_tehais = ["?"]*13
self.my_tsumohai = "?"
self.react(self.mjai_client)
return None
# notile
Expand All @@ -380,8 +335,6 @@ def input(self, parse_msg: dict) -> dict | None:
'type': 'end_kyoku'
}
)
self.my_tehais = ["?"]*13
self.my_tsumohai = "?"
self.react(self.mjai_client)
return None
# ryukyoku
Expand All @@ -397,8 +350,6 @@ def input(self, parse_msg: dict) -> dict | None:
'type': 'end_kyoku'
}
)
self.my_tehais = ["?"]*13
self.my_tsumohai = "?"
self.react(self.mjai_client)
return None

Expand All @@ -413,8 +364,6 @@ def input(self, parse_msg: dict) -> dict | None:
'type': 'end_game'
}
)
self.my_tehais = ["?"]*13
self.my_tsumohai = "?"
self.react(self.mjai_client)
self.mjai_client.restart_bot(self.seat)
return None
Expand Down
4 changes: 2 additions & 2 deletions mjai/bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ def react(self, events: str) -> str:
return json.dumps({"type":"none"}, separators=(",", ":"))
else:
raw_data = json.loads(return_action)
del raw_data["meta"]
return json.dumps(raw_data, separators=(",", ":"))

def state(self):
return self.model.state

def main():
player_id = int(sys.argv[1])
Expand All @@ -40,5 +41,4 @@ def main():


if __name__ == "__main__":
# debug()
main()
2 changes: 1 addition & 1 deletion requirement.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ textual==0.46.0
playwright==1.41.0
torch>=2.2.0
--find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi
riichi
riichi>=0.1.1

0 comments on commit 33f6b68

Please sign in to comment.