| print('Initializing') |
| import socket |
| print('Socket imported') |
| import ssl |
| import base64 |
| import hashlib |
| import os |
| import time |
| import json |
| import threading |
| print('Threading imported') |
| import Bot |
| print('Bot imported') |
| import socks |
| import random |
| from datetime import datetime |
| from urllib.parse import unquote |
| Adapter3P = None |
| print('Initialization complete') |
|
|
| bot = None |
|
|
| result_file = None |
|
|
| host = "proxy.warfarinarknights.workers.dev" |
| path = "/" |
|
|
| CHANGCI = '41' |
| UID = 'ID358602EF-BeaNmMfB' |
| SLOWER = False |
| AWARE_LIST = ['純特呆'] |
| SANMA_CHANGCI = ['25', '153', '57'] |
| ALIVE_TICK = 12 |
| SANMA = False |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| def WriteToResult(s): |
| with open(UID + '牌谱记录.txt', 'a') as f: |
| f.write(s) |
|
|
| class WebSocketClient: |
| def __init__(self, on_message_callback=None): |
| self.sock = None |
| self.running = False |
| self.on_message = on_message_callback or self.default_on_message |
| self.receive_thread = None |
| self.max_idle_cycle = 8 |
| self.idle_cycle = self.max_idle_cycle |
| self.max_retry = 0 |
| self.last_message = '' |
| self.last_account = 'NoName' |
| |
| def default_on_message(self, msg): |
| """默认的消息处理函数""" |
| print(f"Received: {msg}") |
| |
| def login(self, account): |
| self.last_account = account |
| |
| |
| |
| |
|
|
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| |
| context = ssl.create_default_context() |
| |
| wrapped_socket = context.wrap_socket(sock, server_hostname=host) |
| wrapped_socket.connect((host, 443)) |
|
|
| key = base64.b64encode(hashlib.sha1(str(id(wrapped_socket)).encode()).digest()[:16]) |
|
|
| |
| request = ( |
| f"GET {path} HTTP/1.1\r\n" |
| f"Host: {host}\r\n" |
| f"Upgrade: websocket\r\n" |
| f"Connection: Upgrade\r\n" |
| f"Sec-WebSocket-Key: {key.decode()}\r\n" |
| f"Sec-WebSocket-Version: 13\r\n" |
| f"Origin: https://tenhou.net\r\n" |
| f"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0\r\n" |
| "\r\n" |
| ) |
| wrapped_socket.send(request.encode()) |
|
|
| response = wrapped_socket.recv(4096).decode() |
| if "101 Switching Protocols" not in response: |
| print(response) |
| raise Exception("Log in failed:", response) |
|
|
| self.sock = wrapped_socket |
| print(f'\033[33m尝试登录账号:{account}\033[0m') |
| self.send('{"tag":"HELO","name":"' + str(account) + '","sx":"F"}') |
| return wrapped_socket |
|
|
| response = wrapped_socket.recv(4096).decode() |
| if "101 Switching Protocols" not in response: |
| print(response) |
| raise Exception("Log in failed:", response) |
|
|
| self.sock = wrapped_socket |
| print(f'\033[33m尝试登录账号:{account}\033[0m') |
| self.send('{"tag":"HELO","name":"' + str(account) + '","sx":"F"}') |
| return wrapped_socket |
|
|
| def send(self, data): |
| if not self.sock: |
| raise Exception("Socket not connected") |
| print('\033[90m<-', data, '\033[0m') |
| self.last_message = data |
| frame = bytearray() |
| frame.append(0x81) |
| mask_bit = 0x80 |
| payload_len = len(data) |
| |
| if payload_len <= 125: |
| frame.append(mask_bit | payload_len) |
| elif payload_len <= 65535: |
| frame.append(mask_bit | 126) |
| frame.extend(payload_len.to_bytes(2, byteorder='big')) |
| else: |
| frame.append(mask_bit | 127) |
| frame.extend(payload_len.to_bytes(8, byteorder='big')) |
| |
| masking_key = bytearray(os.urandom(4)) |
| frame.extend(masking_key) |
| |
| masked_data = bytearray() |
| for i in range(payload_len): |
| masked_data.append(ord(data[i]) ^ masking_key[i % 4]) |
| frame.extend(masked_data) |
| |
| self.sock.send(frame) |
|
|
| def _recv_frame(self): |
| if not self.sock: |
| return None |
| |
| header = self.sock.recv(2) |
| if not header: |
| return None |
|
|
| fin = (header[0] & 0x80) != 0 |
| opcode = header[0] & 0x0F |
| mask = (header[1] & 0x80) != 0 |
| length = header[1] & 0x7F |
|
|
| if length == 126: |
| length = int.from_bytes(self.sock.recv(2), byteorder="big") |
| elif length == 127: |
| length = int.from_bytes(self.sock.recv(8), byteorder="big") |
|
|
| if mask: |
| masking_key = self.sock.recv(4) |
| data = self.sock.recv(length) |
| decoded = bytearray([data[i] ^ masking_key[i % 4] for i in range(length)]) |
| else: |
| decoded = self.sock.recv(length) |
|
|
| return decoded.decode() |
|
|
| def _receive_loop(self): |
| """接收消息的循环,运行在单独的线程中""" |
| while self.running: |
| try: |
| msg = self._recv_frame() |
| if msg: |
| print(f"\033[90m-> {msg}\033[0m") |
| self.idle_cycle = self.max_idle_cycle |
| self.on_message(self, msg) |
| time.sleep(0.1) |
| except EOFError as e: |
| if self.running: |
| print(f"Error receiving message: {e}") |
| |
| break |
|
|
| def start(self, account): |
| """启动WebSocket连接和消息接收""" |
| self.login(account) |
| self.running = True |
| self.receive_thread = threading.Thread(target=self._receive_loop) |
| self.receive_thread.daemon = True |
| self.receive_thread.start() |
| |
| def stop(self): |
| """停止WebSocket连接""" |
| self.running = False |
| if self.sock: |
| self.sock.close() |
| if self.receive_thread: |
| self.receive_thread.join(timeout=1.0) |
|
|
| def heartbeat(self): |
| self.send('<Z/>') |
| self.idle_cycle -= 1 |
| if self.idle_cycle <= 0: |
| self.max_retry -= 1 |
| if self.max_retry <= 0: |
| print('\033[91m连接已断开\033[0m') |
| os._exit(0) |
| print('\033[33m服务器无应答,尝试重新连接数据包\033[0m') |
| WriteToResult('北风(Tenhou):网络波动,似乎掉线') |
| self.idle_cycle = 1 |
| self.stop() |
| self.login(self.last_account) |
|
|
| class State: |
| def __init__(self): |
| self.seat = 0 |
| self.reached = False |
| self.is3p = False |
| self.hand = [] |
| self.tsumo = False |
| self.round_name = '' |
| self.paipu_url = '' |
| self.last_dahai_player = 0 |
| self.owari = False |
| self.is_new_round = True |
|
|
| state = State() |
| tiles_tenhou: dict[str, int] = { |
| '1m': 0, '2m': 1, '3m': 2, '4m': 3, '5m': 4, '5mr': 4, '6m': 5, '7m': 6, '8m': 7, '9m': 8, |
| '1p': 9, '2p': 10, '3p': 11, '4p': 12, '5p': 13, '5pr': 13, '6p': 14, '7p': 15, '8p': 16, '9p': 17, |
| '1s': 18, '2s': 19, '3s': 20, '4s': 21, '5s': 22, '5sr': 22, '6s': 23, '7s': 24, '8s': 25, '9s': 26, |
| 'E': 27, 'S': 28, 'W': 29, 'N': 30, 'P': 31, 'F': 32, 'C': 33 |
| } |
|
|
| def ToTenhou(state, labels: list[str]) -> list[int]: |
| ret = [] |
| hand = state.hand[:] |
| hand = sorted(hand, reverse=True) |
|
|
| for label in labels: |
| is_red = label[-1] == 'r' |
| index = tiles_tenhou[label] |
| index = [i for i in hand if i // 4 == index and (not is_red or i % 4 == 0) and i not in ret][0] |
| ret.append(index) |
|
|
| return ret |
|
|
| def ToTenhouOne(state, label, tsumogiri=False): |
| if tsumogiri: |
| return state.hand[-1] |
| return ToTenhou(state, [label])[0] |
|
|
| def RelToAbs(rel: int) -> int: |
| return (rel + state.seat) % 4 |
| |
| def AbsToRel(abs: int) -> int: |
| return (abs - state.seat) % 4 |
|
|
| def StartGame(message): |
| global state |
| mjai_messages = [{'type': 'start_game', 'id': 0}] |
| state.seat = (4 - int(message['oya'])) % 4 |
| mjai_messages[0]['id'] = state.seat |
| state.paipu_url = f'https://tenhou.net/6/?log={message.get("log","")}&tw={state.seat}' |
| return mjai_messages |
|
|
| def ParseScoreList(scores): |
| new_scores = [-1, -1, -1, -1] |
| for i in range(4): |
| new_scores[RelToAbs(i)] = scores[i] |
| return new_scores |
|
|
| def StartKyoku(message): |
| global state |
| bakaze = ['E', 'S', 'W', 'N'] |
| oya = RelToAbs(int(message['oya'])) |
| |
| seed = [int(s) for s in message['seed'].split(',')] |
| bakaze = bakaze[seed[0] // 4] |
| kyoku = seed[0] % 4 + 1 |
| honba = seed[1] |
| state.round_name = '东南西北'[seed[0] // 4] + ' ' + str(kyoku) + ' 局 ' + str(honba) + ' 本场' |
| kyotaku = seed[2] |
| dora_marker = TileName(seed[5]) |
| scores = [int(s)*100 for s in message['ten'].split(',')] |
| tehais = [['?' for _ in range(13)]] * 4 |
| state.hand = [int(s) for s in message['hai'].split(',')] |
| tehais[state.seat] = [TileName(x) for x in state.hand] |
|
|
| if bakaze == 'E' and kyoku == 1 and honba == 0: |
| if 0 in scores: |
| state.is3p = True |
| if True: |
| new_scores = [-1, -1, -1, -1] |
| for i in range(4): |
| new_scores[RelToAbs(i)] = scores[i] |
| scores = new_scores |
| original = scores[:] |
| if False: |
| print('原始分数', scores) |
| if seed[0] // 4 == '南' and kyoku == 3: |
| scores[state.seat] -= 8000 |
| else: |
| for i in range(len(scores)): |
| if scores[i] == 0: |
| continue |
| scores[i] -= 16000 |
| if i == state.seat: |
| scores[i] += 96000 |
| start_kyoku_msg = { |
| 'type': 'start_kyoku', |
| 'bakaze': bakaze, |
| 'kyoku': kyoku, |
| 'honba': honba, |
| 'kyotaku': kyotaku, |
| 'oya': oya, |
| 'dora_marker': dora_marker, |
| 'scores': scores, |
| 'tehais': tehais |
| } |
|
|
| WriteToResult({'E':'东','W':'西','S':'南','N':'北'}[str(start_kyoku_msg['bakaze'])] |
| + ' ' |
| + str(start_kyoku_msg['kyoku']) |
| + ' 局 ' |
| + str(start_kyoku_msg['honba']) |
| + ' 本场:' + ('(原始数据:'+str(original)+')' if SANMA else '') |
| + ('、'.join((str(score) + (('(自家)') if i == state.seat else '')) for i, score in enumerate(scores))) |
| + '\n') |
| |
|
|
| return [start_kyoku_msg] |
|
|
| def Tsumo(message): |
| global state |
| tag = message['tag'] |
| actor = RelToAbs(ord(tag[0]) - ord('T')) |
| mjai_messages = [{ |
| 'type': 'tsumo', |
| 'actor': actor, |
| 'pai': '?', |
| }] |
| state.tsumo = False |
| if actor == state.seat: |
| pai = int(tag[1:]) |
| mjai_messages[0]['pai'] = TileName(pai) |
| state.hand.append(pai) |
| state.tsumo = True |
| return mjai_messages |
|
|
| def Dahai(message): |
| global state |
| tag = message['tag'] |
| actor = RelToAbs(ord(str.upper(tag[0])) - ord('D')) |
| if len(tag) == 1: |
| idx = state.hand[-1] |
| else: |
| idx = int(tag[1:]) |
| pai = TileName(idx) |
| tsumogiri = tag[0] in 'defg' |
|
|
| state.tsuomo = False |
| if actor == state.seat: |
| state.hand.remove(idx) |
| state.last_dahai_player = actor |
| return [{ |
| 'type': 'dahai', |
| 'actor': actor, |
| 'pai': pai, |
| 'tsumogiri': tsumogiri, |
| }] |
|
|
| def Nuki(message, just_drew): |
| global state |
| actor = RelToAbs(int(message['who'])) |
| m = int(message['m']) |
| if (m & 0x3F) == 0x20 : |
| |
| if actor == state.seat: |
| for i in state.hand: |
| if i // 4 == 30: |
| state.hand.remove(i) |
| break |
| return [{ |
| 'type': 'nukidora', |
| 'actor': actor, |
| 'pai': 'N' |
| }] |
| |
| meld = DecodeNuki(m, just_drew) |
| if meld['type'] == 'chi': |
| target = (actor - 1) % 4 |
| elif meld['type'] in ('daiminkan', 'pon'): |
| target = state.last_dahai_player |
| else: |
| target = RelToAbs(meld['target'] % 4) |
|
|
| mjai_messages = [{ |
| 'type': meld['type'], |
| 'actor': actor, |
| 'target': target, |
| 'pai': meld['tile'], |
| 'consumed': meld['consumed'] |
| }] |
|
|
| if meld['type'] in ['kakan', 'ankan']: |
| del mjai_messages[0]['target'] |
| if meld['type'] == 'ankan': |
| del mjai_messages[0]['pai'] |
|
|
| if actor == state.seat: |
| if meld['type'] == 'kakan': |
| if mjai_messages[0]['pai'] in state.hand: |
| state.hand.remove(mjai_messages[0]['pai']) |
| else: |
| for i in meld['consumed_id']: |
| state.hand.remove(i) |
|
|
| return mjai_messages |
|
|
| def Reach(message): |
| global state |
| actor = RelToAbs(int(message['who'])) |
| return [{'type': 'reach', 'actor': actor}] |
|
|
| def ReachAccepted(message): |
| global state |
| actor = int(message['who']) |
| state.reached = actor == 0 |
| |
| deltas = [0] * 4 |
| deltas[actor] = -1000 |
| scores = [int(s) * 100 for s in message['ten'].split(',')] |
| |
| return [{ |
| 'type': 'reach_accepted', |
| 'actor': RelToAbs(actor), |
| 'deltas': ParseScoreList(deltas), |
| 'scores': ParseScoreList(scores) |
| }] |
|
|
| def Dora(message): |
| global state |
| return [{'type': 'dora', 'dora_marker': TileName(int(message['hai']))}] |
| |
| def DecodeNuki(m, just_drew): |
| global state |
| call_dir = m & 3 |
| |
| if m & 0x4: |
| call_type = "chi" |
| tile_info = m >> 10 |
| rs = [(m >> 3) & 3, 4 + ((m >> 5) & 3), 8 + ((m >> 7) & 3)] |
| elif m & 0x8 or m & 0x10: |
| call_type = "pon" if m & 0x8 else "kakan" |
| tile_info = m >> 9 |
| rs = [0, 1, 2, 3] |
| if call_type == "pon": |
| rs.remove((m >> 5) & 3) |
| |
| else: |
| call_type = "nukidora" if m & 0x20 else "ankan" if just_drew else "daiminkan" |
| tile_info = m >> 8 |
| rs = [0, 1, 2, 3] |
| |
| num_tiles = 4 if call_type in {"nukidora", "daiminkan", "ankan"} else 3 |
| |
| ix = tile_info // num_tiles |
| |
| if call_type == "chi": |
| ix = (ix // 7) * 9 + ix % 7 |
|
|
| called_tile_id = ix * 4 + (rs[0] if call_type == "kakan" else rs[tile_info % num_tiles]) |
| called_tiles_id = [(ix * 4) + r for r in rs] |
| if call_type not in ('ankan', 'kakan'): |
| called_tiles_id.remove(called_tile_id) |
| if call_type == 'kakan': |
| called_tiles_id = called_tiles_id[0:3] |
|
|
| return {'type': call_type, |
| 'target': call_dir, |
| 'tile': TileName(called_tile_id), |
| 'consumed': [TileName(x) for x in called_tiles_id], |
| 'consumed_id': called_tiles_id |
| } |
|
|
|
|
|
|
| def ParseSC(message): |
| sc = [int(s) for s in message['sc'].split(',')] |
| before = sc[0::2] |
| delta = sc[1::2] |
| after = [(x + y) * 100 for x, y in zip(before, delta)] |
| return after |
|
|
| def ParseScoreDelta(message): |
| sc = [int(s) for s in message['sc'].split(',')] |
| before = sc[0::2] |
| delta = sc[1::2] |
| after = [x * 100 for x in delta] |
| return after |
|
|
| def RotateScore(scores): |
| global state |
| res = [0, 0, 0, 0] |
| for i, x in enumerate(scores): |
| res[RelToAbs(i)] = x |
| return res |
|
|
| def Owari(message): |
| global state |
| scores = ParseSC(message) |
| rank = sorted(scores, reverse=True).index(scores[0]) + 1 |
| scores = RotateScore(scores) |
| WriteToResult('| ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ' | ' + str(rank) + ' | ' + state.paipu_url + ' |\n') |
|
|
| def Agari(message): |
| global state |
| scores = ParseSC(message) |
| deltas = ParseScoreDelta(message) |
| scores = RotateScore(scores) |
| deltas = RotateScore(deltas) |
| tiles = [int(x) for x in message['hai'].split(',')] |
| machi = int(message['machi']) |
| if message['who'] != message['fromWho'] and machi in tiles: |
| tiles.remove(machi) |
| ura_dora = [] |
| if 'doraHaiUra' in message: |
| ura_dora = [TileName(int(x)) for x in message['doraHaiUra'].split(',')] |
| return [ |
| {'type': 'hora', 'actor': RelToAbs(int(message['who'])), 'tiles': [TileName(x) for x in tiles], |
| 'target': RelToAbs(int(message['fromWho'])), 'scores': scores, 'delta': deltas, |
| 'ura_dora': ura_dora}, |
| {'type': 'end_kyoku'} |
| ] |
|
|
| def Ryukyoku(message): |
| global state |
| deltas = ParseScoreDelta(message) |
| deltas = RotateScore(deltas) |
| return [ |
| {'type': 'ryukyoku', 'delta': deltas}, |
| {'type': 'end_kyoku'} |
| ] |
|
|
| def GetSeat(tag: str) -> int: |
| draw_map = {'T': 0, 'U': 1, 'V': 2, 'W': 3} |
| discard_map = {'D': 0, 'E': 1, 'F': 2, 'G': 3} |
| if tag[0] in draw_map: |
| return draw_map[tag[0]] |
| if tag[0] in discard_map: |
| return discard_map[tag[0]] |
| return -1 |
|
|
| def TileName(tile_id): |
| if tile_id == 16: |
| return '5mr' |
| if tile_id == 16 + 36: |
| return '5pr' |
| if tile_id == 16 + 72: |
| return '5sr' |
| if tile_id // 36 >= 3: |
| return 'ESWNPFC'[tile_id % 36 // 4] |
| return str(tile_id % 36 // 4 + 1) + 'mpsz????'[tile_id // 36] |
|
|
| ACTION_NAME = { |
| 1: 'pon', |
| 4: 'chi', |
| 16: 'akari', |
| 32: 'riichi', |
| 128: 'nukidora', |
| } |
|
|
| |
| def NukidoraMsg(): |
| return '{"tag":"N","t":10}' |
|
|
| |
| def DahaiMsg(hai): |
| return '{"tag":"D","p":' + str(hai) + '}' |
|
|
| |
| def CancelNukiMsg(): |
| return '{"tag":"N"}' |
|
|
| def build(mjai_msg): |
| global state |
| tenhou_msg = {} |
| if mjai_msg['type'] == 'dahai': |
| p = ToTenhouOne(state, mjai_msg['pai'], mjai_msg['tsumogiri']) |
| tenhou_msg = {'tag': 'D', 'p': p} |
| elif mjai_msg['type'] == 'hora': |
| if state.tsumo: |
| tenhou_msg = {'tag': 'N', 'type': 7} |
| else: |
| tenhou_msg = {'tag': 'N', 'type': 6} |
| elif mjai_msg['type'] == 'reach': |
| tenhou_msg = {'tag': 'REACH'} |
| elif mjai_msg['type'] == 'ryukyoku': |
| tenhou_msg = {'tag': 'N', 'type': 9} |
| elif mjai_msg['type'] == 'ankan': |
| hai = ToTenhouOne(state, mjai_msg['consumed'][0]) // 4 * 4 |
| tenhou_msg = {'tag': 'N', 'type': 4, 'hai': hai} |
| elif mjai_msg['type'] == 'kakan': |
| hai = ToTenhouOne(state, mjai_msg['pai']) |
| tenhou_msg = {'tag': 'N', 'type': 5, 'hai': hai} |
| elif mjai_msg['type'] == 'pon': |
| hai0, hai1 = ToTenhou(state, mjai_msg['consumed']) |
| tenhou_msg = {'tag': 'N', 'type': 1, 'hai0': hai0, 'hai1': hai1} |
| elif mjai_msg['type'] == 'daiminkan': |
| tenhou_msg = {'tag': 'N', 'type': 2} |
| elif mjai_msg['type'] == 'chi': |
| hai0, hai1 = ToTenhou(state, mjai_msg['consumed']) |
| tenhou_msg = {'tag': 'N', 'type': 3, 'hai0': hai0, 'hai1': hai1} |
| elif mjai_msg['type'] == 'nukidora': |
| tenhou_msg = {'tag': 'N', 'type': 10} |
| elif mjai_msg['type'] == 'none': |
| tenhou_msg = {'tag': 'N'} |
| else: |
| return None |
| return json.dumps(tenhou_msg, separators=(',', ':')) |
|
|
| def Reinit(msg): |
| global state |
| kawas = [] |
| extra_kawas = [] |
| N_count = [0, 0, 0, 0] |
| for s in range(4): |
| kawa = [TileName(int(i)) for i in msg.get('kawa' + str(s), '').split(',')] |
| for s in range(4): |
| melds = [int(i) for i in msg.get('m' + str(s), '').split(',')] |
| for meld in melds: |
| meld = DecodeNuki(meld) |
| if meld['type'] == 'nukidora': |
| N_count[s] += 1 |
|
|
| MESSAGES = [] |
| import MjaiChecker |
|
|
| def Tehai(): |
| global state |
| tehai = sorted(state.hand[0:-1]) |
| result = '' |
| last = '' |
| for i, x in enumerate(tehai): |
| card = MjaiChecker.TileName(TileName(x)) |
| if last != '' and last[-1] != card[-1] and last[-1] in 'mps': |
| result += last[-1] |
| result += card[0] |
| last = card |
| if last[-1] in 'mps': |
| result += last[-1] |
| result += ' ' |
| return result + MjaiChecker.TileName(TileName(state.hand[-1])) |
|
|
| |
| def handle_message(client, msg): |
| global MESSAGES, state, AWARE_LIST, SLOWER, ALIVE_TICK |
| new_msg = [] |
| msg = json.loads(msg) |
| tag = msg.get('tag') |
| |
| if tag == 'HELO': |
| client.send('{"tag":"JOIN","t":"0,' + str(CHANGCI) + '"}') |
| print('\033[93m' + '成功登录,正在匹配对局' + '\033[0m') |
| |
| |
| elif tag == 'REJOIN': |
| t = msg.get('t') |
| client.send('{"tag":"JOIN","t":"' + t + '"}') |
| |
| |
| elif tag == 'TAIKYOKU' or tag == 'SAIKAI': |
| ALIVE_TICK = 720 |
| state.reached = False |
| MESSAGES = new_msg = StartGame(msg) |
| print('\033[93m' + '已匹配对局' + '\033[0m') |
| client.send('{"tag":"GOK"}') |
| client.send('{"tag":"NEXTREADY"}') |
|
|
| |
| elif tag in ('GO', 'BYE', 'FURITEN'): |
| return |
|
|
| |
| elif tag == 'ERR': |
| code = msg['code'] |
| print('\033[91m' + f'天凤服务器给出错误代码 {code}' + '\033[0m') |
| if code == '1004': |
| print('\033[91m' + f'提示:该账号在其他地方已经登录' + '\033[0m') |
| elif code == '1003': |
| print('\033[91m' + f'提示:检查 ID 是否正确,或者该账号已被封禁' + '\033[0m') |
| os._exit(0) |
| |
| |
| elif tag == 'UN': |
| if 'n0' in msg: |
| state.self_name = unquote(msg['n0']) |
| if 'dan' in msg: |
| state.dan = (['新人'] + [f'{i} 级' for i in range(9, 0, -1)] + |
| ['初段', '二段', '三段', '四段', '五段', |
| '六段', '七段', '八段', '九段', '十段', '天凤'])[int(msg['dan'].split(',')[0])] |
| with open(f'{UID}_status.txt', 'w', encoding='utf-8') as f: |
| f.write(state.self_name + '\n' + state.dan + '\n') |
| for key in msg: |
| if key[0] != 'n': |
| continue |
| name = unquote(msg[key]) |
| if name in AWARE_LIST: |
| SLOWER = True |
|
|
| f.write(name + ' ') |
| |
| |
| elif tag == 'INIT' or tag == 'REINIT': |
| state.reached = False |
| state.is_new_round = True |
| print('\033[90m检测到新的一局,之后可能会有九种九牌流局\033[0m') |
| new_msg = StartKyoku(msg) |
| MESSAGES = new_msg |
|
|
| |
| elif tag in ('AGARI', 'RYUUKYOKU'): |
| client.send('{"tag":"NEXTREADY"}') |
| if tag == 'AGARI': |
| new_msg = Agari(msg) |
| else: |
| new_msg = Ryukyoku(msg) |
| MESSAGES += new_msg |
| if 'owari' in msg: |
| Owari(msg) |
| state.owari = True |
|
|
| |
| elif tag == 'PROF': |
| pass |
| |
| |
| elif tag == 'REACH': |
| if msg['step'] == '1': |
| new_msg = Reach(msg) |
| MESSAGES += new_msg |
| else: |
| new_msg = ReachAccepted(msg) |
| MESSAGES += new_msg |
| pass |
| |
| |
| elif tag == 'DORA': |
| new_msg = Dora(msg) |
| MESSAGES += new_msg |
|
|
| |
| elif tag[0] in 'TUVW': |
| new_msg = Tsumo(msg) |
| MESSAGES += new_msg |
|
|
| |
| elif tag[0] == 'N' and 'm' in msg: |
| if state.is_new_round: |
| print('\033[90m不再是新的一局,本局不会有九种九牌流局\033[0m') |
| state.is_new_round = False |
| new_msg = Nuki(msg, MESSAGES[-1]['type'] == 'tsumo') |
| MESSAGES += new_msg |
| state.tsumo = False |
| |
| |
| elif tag[0] in 'DEFGdefg': |
| new_msg = Dahai(msg) |
| MESSAGES += new_msg |
| state.tsumo = False |
|
|
| if len(state.hand) % 3 == 2: |
| print('\033[92m' + Tehai() + ' \033[90m' + str(sorted(state.hand) + [state.hand[-1]]) + '\033[0m') |
| for msg in new_msg: |
| print('\033[93m' + MjaiChecker.TranslateSingle(msg, state.seat) + '\033[0m') |
| try: |
| |
| |
| json.dump(MESSAGES, open(UID + '_paiju.txt', 'w', encoding='utf-8')) |
| except Exception as e: |
| print('牌局保存失败', e) |
| if len(new_msg): |
| print('\033[90m发送给模型:' + json.dumps(new_msg, separators=(",", ":")) + '\033[0m') |
| res = bot.react(json.dumps(new_msg)) |
| if res: |
| if True: |
| time.sleep(random.random() * 1.5 + 0.5) |
|
|
| print('\033[90m模型决策:' + res + '\033[0m') |
| res = build(json.loads(res)) |
| res = json.loads(res) |
| if res.get('tag') == 'N' and res.get('type') == 9: |
| print('\033[90m尝试九种九牌流局\033[0m') |
| if not state.is_new_round: |
| print('\033[90m流局失败,默认摸切掉一张\033[0m') |
| res = {'tag': 'D', 'p': state.hand[-1]} |
| res = json.dumps(res, separators=(',', ':')) |
| client.send(res) |
| if state.owari: |
| print('\033[93m对局结束\033[0m') |
| os._exit(0) |
|
|
| def join_game(): |
| global ALIVE_TICK |
| try: |
| client = WebSocketClient(on_message_callback=handle_message) |
| client.start(UID) |
| ALIVE_TICK = 12 |
| |
| try: |
| while True: |
| client.heartbeat() |
| time.sleep(10) |
| ALIVE_TICK -= 1 |
| if ALIVE_TICK == 0: |
| print('\033[91m' + f'存活时间已到' + '\033[0m') |
| os._exit(0) |
| else: |
| print('\033[90m剩余存活倒计时:', ALIVE_TICK, '\033[0m') |
| except KeyboardInterrupt: |
| print("Interrupted by user") |
| |
| finally: |
| client.stop() |
|
|
| if __name__ == "__main__": |
| |
| import sys |
| args = sys.argv |
| SANMA = False |
| if len(args) >= 3: |
| UID = args[1] |
| CHANGCI = args[2] |
| for arg in args[3:]: |
| if arg == '-slow': |
| SLOWER = True |
| SANMA = CHANGCI in SANMA_CHANGCI |
| bot = Bot.Bot() if SANMA else Bot.Bot4P() |
| join_game() |
| else: |
| print('Usage: core.py <tenhou-id> <level-number> [-slow]') |
|
|