diff --git a/19-q3q4/huawei kunpen race/README.md b/19-q3q4/huawei kunpen race/README.md new file mode 100644 index 0000000..cae40f3 --- /dev/null +++ b/19-q3q4/huawei kunpen race/README.md @@ -0,0 +1,16 @@ +## 华为云鲲鹏开发者大赛 + +这是 2019 年参加 “华为云鲲鹏开发者大赛” 时编写的一些代码: + +`brifuture` 文件夹里面是我编写的策略。只编写了一天,效果并不好,还有很多策略没有实现,打不过 AI,最好的成绩只有 300 多分。 + +`server` 文件夹里面是模拟官方的服务器,根据官方的文档说明编写的自定义的服务器,大部分功能都实现了,但仍有小部分的 bug 未解决。 + +**Note:解题策略和服务器仅供参考。** + +----- +官方资源: + +[赛题说明文档](https://bbs.huaweicloud.com/forum/thread-21027-1-1.html) + +[试题工程](https://developer-res-cbc-cn.obs.cn-north-1.myhwclouds.com/competition/%E5%8D%8E%E4%B8%BA%E4%BA%91%E9%B2%B2%E9%B9%8F%E5%BC%80%E5%8F%91%E8%80%85%E5%A4%A7%E8%B5%9B-%E8%AF%95%E9%A2%98%E5%B7%A5%E7%A8%8B0801.zip) \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/.gitignore b/19-q3q4/huawei kunpen race/brifuture/.gitignore new file mode 100644 index 0000000..4c89497 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/.gitignore @@ -0,0 +1 @@ +brifuture.zip diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/__init__.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/comunicate/__init__.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/comunicate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/comunicate/client.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/comunicate/client.py new file mode 100644 index 0000000..fa5e4c1 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/comunicate/client.py @@ -0,0 +1,176 @@ +# encoding:utf8 +''' +客户端对数据的接受和对已生成数据的发送,选手可以不关注此模块 + +''' + +import socket +import json + +from ballclient.service import service +import ballclient.service.constants as constants +import traceback +from ballclient.util import logger +from time import sleep + +_socket = None + +SOCKET_CACHE = 1024 * 20 + + +def try_again(func): + def wraper(*args, **kwargs): + connect_time = 1 + while connect_time <= 300: + try: + return func(*args, **kwargs) + except Exception: + if connect_time % 10 == 0: + logger.warning("connect server failed.....connect_time:%s" % connect_time) + connect_time += 1 + sleep(0.2) + logger.fatal("can not connect with server. %s,%s" % args) + exit(1) + return wraper + +@try_again +def connect_socket(ip=None, port=None): + global _socket + _socket.connect((ip, port)) + + +def start(ip=None, port=None): + global _socket + _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + data = None + try: + connect_socket(ip,port) + + logger.debug("Tri to register.") + register() + while 1: + data = _receive() + if data is None: + break + # logger.debug(data) + + if data['msg_name'] == "round": + message = service.round(data) + send_dict(message) + elif data['msg_name'] == "leg_start": + service.leg_start(data) + elif data['msg_name'] == "leg_end": + service.leg_end(data) + elif data['msg_name'] == "game_over": + service.game_over(data) + logger.info("Server Exit") + return + else: + print("invalid msg_name.") + except socket.error: + print("can not connect with server. %s,%s" % (ip, port)) + exit() + except Exception as e: + print("[start] some error happend: %s. the receive data: %s" % (e, data) ) + traceback.print_exc() + finally: + if _socket: + _socket.close() + logger.info("Client Closed") + + +def register(): + ''' + register logic + :return: + ''' + data = {"msg_name": "registration", + "msg_data": {"team_name": constants.team_name, + "team_id": constants.team_id}} + # print(f"[Register] {data}") + send_dict(data) + + +def send_dict(data): + data_str = json.dumps(data) + _socket.sendall(add_str_len(data_str).encode()) + + +class Receiver(object): + def __init__(self): + self._cache = "" + self.remainBytes = 0 + + def __call__(self, *args, **kwargs): + while 1: + d = None + try: + d = _socket.recv(SOCKET_CACHE).decode() + # logger.debug(f"Data: {d}, {len(d)}") + if len(d) == 0: + return None + if self.remainBytes == 0: + + if d[0:5].isdigit() and d[5] == "{": + need = int(d[0:5]) + self.remainBytes = need - (len(d) - 5) + if(self.remainBytes == 0): + self._cache = "" + data = remove_json_num(d) + return json.loads(data) + else: + self._cache = d + else: + self.remainBytes -= len(d) + if self.remainBytes == 0: + data = remove_json_num(self._cache + d) + # logger.debug(self._cache + d) + self._cache = "" + return json.loads(data) + else: + self._cache += d + except ConnectionResetError: + print("can not connect with server.") + exit() + except Exception as e: + print("[Receiver] receive data error. cach the data and wait for next.", e) + traceback.print_exc() + break + + +_receive = Receiver() + +def add_str_len(msg_data): + length = str(len(msg_data)) + index = 5 - len(length) + if index < 0: + raise Exception("the return msg data is too long. the length > 99999.") + return '0' * index + length + msg_data + + +def remove_json_num(msg): + return msg[5:] + + +""" +# def _receive(): +# d = _socket.recv(SOCKE_CACH) +# data = remove_json_num(d) +# return json.loads(data) + + +def receive_game_data(): + try: + data = _receive() + if data['msg_name'] == "game_over": + service.exec_game_over() + return 0 + else: + return_msg = service.exec_round(data) + send_msg = add_str_len(json.dumps(return_msg)) + _socket.sendall(send_msg) + except: + print('error receive data......') + + return 1 +""" diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/main.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/main.py new file mode 100644 index 0000000..26cbc9c --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/main.py @@ -0,0 +1,30 @@ +# encoding:utf8 +''' +入口方法,选手不关注 +''' + + +from ballclient import util +import sys + +if __name__ == "__main__": + print(sys.argv) + if len(sys.argv) != 4: + print("The parameters has error. (TeamID server_ip server_port)") + exit() + team_id = sys.argv[1] + server_ip = sys.argv[2] + port = sys.argv[3] + print("start client....................") + if team_id.isdigit() and port.isdigit(): + team_id = int(team_id) + port = int(port) + util.createShareLogger("brifuture_%s.log" % team_id, stream=True) + else: + print("team_id and port must be digit.") + exit() + + import ballclient.service.constants as constants + from ballclient.comunicate import client + constants.team_id = team_id + client.start( server_ip, port) diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/__init__.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/clientteam.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/clientteam.py new file mode 100644 index 0000000..838f09b --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/clientteam.py @@ -0,0 +1,153 @@ +from .gameteam import BasePlayer, BaseTeam, _ForceBeat, _ForceThink + +class ClientPlayer(BasePlayer): + PrivilegeForce = _ForceBeat + + def __init__(self, p): + BasePlayer.__init__(self, p["id"], p["team"]) + self.x = p["x"] + self.y = p["y"] + self.score = p["score"] + self.visible = True + self.lastAction = None + self.nextPos = (self.x, self.y) + self.posStack = [] # 保留最近 n 次的位置 + self.visualArea = [[0, 0], [0, 0]] + + def distance(self, x, y): + return abs(self.x - x) + abs(self.y - y) + + def isInvision(self, x, y): + if abs(self.x - x) <= ClientTeam.Vision and abs(self.y - y) <= ClientTeam.Vision: + return True + return False + + def update(self, p): + self.lastX = self.x + self.lastY = self.y + BasePlayer.__init__(self, p["id"], p["team"]) + self.x = p["x"] + self.y = p["y"] + self.score = p["score"] + + def __repr__(self): + return ' ' % (self.team, self.id, self.x, self.y) + +class ClientTeam(BaseTeam): + + Width = 20 + Height = 20 + Vision = 3 + + def __init__(self, id = 0): + BaseTeam.__init__(self) + self.meteor = None + self.tunnel = None + self.wormhole = None + self.id = id + + # key 是 player 的 id, value 是 ClientPlayer 对象 + self.players = {} + self.powers = [] # {'point': 1, 'x': 12, 'y': 2} + self.playerNum = 0 + self.privilege = False + + def addPlayer(self, playerInfo, replace=True): + player = ClientPlayer(playerInfo) + if not replace and player.id in self.players: + self.players[player.id].update(playerInfo) + else: + self.players[player.id] = player + self.playerNum = len(self.players) + + def nearmate(self, x, y, dis = 5): + count = 0 + for p in self.players.values(): + if abs(p.x - x) <= dis and abs(p.y - y) <= dis: + count += 1 + return count + + def isMeteor(self, x, y): + """Return True if meteor, + if x, y is out of boundary, return True + """ + for m in self.meteor: + if x == m["x"] and y == m["y"]: + return True + + if x < 0 or x >= ClientTeam.Width or y < 0 or y >= ClientTeam.Height: + return True + return False + + def isTunnel(self, x, y): + """Return Tunnel Dict if found, else None + {"x": 3, "y": 1, "direction": "down"} + """ + for t in self.tunnel: + if t["x"] == x and t["y"] == y: + return t + return None + + def isWormhole(self, x, y): + """Return wormhole if found, else None + {"name": "a","x": 4, "y": 1} + """ + for wh in self.wormhole: + if wh["x"] == x and wh["y"] == y: + return wh + return None + + def peerWormhole(self, wh): + """Return Peer wormhole + """ + name = wh["name"] + if name.isupper(): + exp = name.lower() + else: + exp = name.upper() + + for h in self.wormhole: + if h["name"] == exp: + return h + + def isPlayer(self, x, y, exceptPlayer=None): + for p in self.players.values(): + if p == exceptPlayer: + continue + if p.x == x and p.y == y: + return p + if p.nextPos[0] == x and p.nextPos[1] == y: + return p + return None + + def isPower(self, x, y): + """Return Power point if found + """ + for p in self.powers: + if p["x"] == x and p["y"] == y: + return p["point"] + return None + + def tunnelNext(self, tunnel): + """direct 是 tunnel 的方向, + x y 是 tunnel 的位置 + """ + direct = tunnel["direction"] + x = tunnel["x"] + y = tunnel["y"] + if direct == "right": + x += 1 + elif direct == "left": + x -= 1 + elif direct == "down": + y += 1 + elif direct == "up": + y -= 1 + # cascade tunnel + t = self.isTunnel(x, y) + if t is not None: + return self.tunnelNext(t) + return x, y + +class ClientMap(): + pass \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/constants.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/constants.py new file mode 100644 index 0000000..5141379 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/constants.py @@ -0,0 +1,4 @@ +#global variable here +team_id = 1125 + +team_name = "qqcy" # decided by yourself. \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/gameteam.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/gameteam.py new file mode 100644 index 0000000..d9d31fa --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/gameteam.py @@ -0,0 +1,93 @@ +_ForceBeat = "beat" +_ForceThink = "think" + +class BasePlayer(): + StatusAlive = "alive" + StatusDead = "Dead" + + ActionNone = "blank" + ActionNormal = ["up", "down", "left", "right"] + + def __init__(self, id, teamid): + self.score = 0 + self._sleep = 0 + self.x = 0 + self.y = 0 + self.action = BasePlayer.ActionNone + self.force = None + self.status = BasePlayer.StatusAlive + self.id = id + self.team = teamid + self.group = None + + @property + def sleep(self): + return 1 if self._sleep else 0 + + @staticmethod + def testMove(x, y, action): + if action == "up": + return x, y - 1 + elif action == "down": + return x, y + 1 + elif action == "left": + return x - 1, y + elif action == "right": + return x + 1, y + else: + return x, y + + def _beforeMove(self): + self.lastX = self.x + self.lastY = self.y + + def undoMove(self): + self.x = self.lastX + self.y = self.lastY + + def moveUp(self): + self._beforeMove() + self.y -= 1 + + def moveDown(self): + self._beforeMove() + self.y += 1 + + def moveLeft(self): + self._beforeMove() + self.x -= 1 + + def moveRight(self): + self._beforeMove() + self.x += 1 + +class BaseTeam(): + ForceBeat = _ForceBeat + ForceThink = _ForceThink + + def __init__(self): + self.id = 0 + self.conn = None + self.name = None + self.remain_life = 0 + self.registered = False + self.players = [] + self.playerId = [] + self.force = BaseTeam.ForceBeat + + @property + def point(self): + po = 0 + for p in self.players: + po += p.score + return po + +import random + +class BasePower(): + def __init__(self, id, x, y): + self.id = id + self.x = x + self.y = y + self.value = random.randint(1, 5) + self.exist = True \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/mapelement.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/mapelement.py new file mode 100644 index 0000000..a2d8250 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/mapelement.py @@ -0,0 +1,104 @@ +from .gameteam import BasePlayer, BaseTeam, _ForceBeat, _ForceThink, BasePower +from ballclient.util import logger + +class Player(BasePlayer): + PrivilegeForce = _ForceBeat + + """包含 id, teamid,位置(x y) + """ + def __init__(self, id, teamid): + BasePlayer.__init__(self, id, teamid) + + + def setAction(self, action): + if action in Player.ActionNormal: + self.action = action + else: + self.action = Player.ActionNone + + def triMove(self): + """According to action, change x, y + """ + return Player.testMove(self.x, self.y, self.action) + + @staticmethod + def playerKillAnother(killer, killed): + """Player killer killed Player killed + """ + killer.score += killed.score + killed.score = 0 + killed._sleep = 1 + killed.group.remain_life -= 1 + + + def meetPlayer(self, player): + if player.team == self.team: + return + + # TODO + if player.force == Player.PrivilegeForce: + Player.playerKillAnother(player, self) + else: + Player.playerKillAnother(self, player) + + + def __repr__(self): + return f' ' + +class Team(BaseTeam): + """Team + """ + # 视野 + Vision = 3 + + def __init__(self): + BaseTeam.__init__(self) + + def getPlayer(self, pid): + """Return player if found + """ + for p in self.players: + if p.id == pid: + return p + return None + + def setForce(self, force): + self.force = force + for p in self.players: + p.force = force + + def addPlayer(self, pid, x, y): + p = Player(pid, self.id) + p.x = x + p.y = y + p.force = self.force + p.group = self + self.players.append(p) + self.playerId.append(pid) + return p + + def round_player_pack(self, another): + pack = [] + for p in self.players: + pack.append(p.pack()) + for ap in another.players: + for p in self.players: + if abs(p.x - ap.x) <= Team.Vision and abs(p.y - ap.y) <= Team.Vision: + pack.append(ap.pack()) + break + return pack + + def __repr__(self): + return f'' + +class Power(BasePower): + def __init__(self, id, x, y): + BasePower.__init__(self, id, x, y) + + def isVisibleToPlayer(self, player): + if abs(player.x - self.x) <= Team.Vision and abs(player.y - self.y) <= Team.Vision: + return True + return False + + def __repr__(self): + return f'' diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/service.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/service.py new file mode 100644 index 0000000..6964dee --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/service.py @@ -0,0 +1,122 @@ +# encoding:utf8 +''' +业务方法模块,需要选手实现 + +选手也可以另外创造模块,在本模块定义的方法中填入调用逻辑。这由选手决定 + +所有方法的参数均已经被解析成json,直接使用即可 + +所有方法的返回值为dict对象。客户端会在dict前面增加字符个数。 +''' +import ballclient.service.constants as constants +from ballclient.util import logger +from ballclient.service.clientteam import ClientPlayer, ClientTeam +from ballclient.service.strategy import Strategy, Strategy2 + +myteam = ClientTeam(constants.team_id) +enemy = ClientTeam() +strategy = Strategy2(myteam, enemy) + +def leg_start(msg): + ''' + :param msg: + :return: None + ''' + msg_data = msg["msg_data"] + msg_map = msg_data['map'] + ClientTeam.Vision = msg_map['vision'] + ClientTeam.Width = msg_map['width'] + ClientTeam.Height = msg_map['height'] + + for t in msg_data['teams']: + if t["id"] == constants.team_id: + for pid in t['players']: + myteam.addPlayer({"score": 0, "x": 0, "y": 0, "id": pid, "team": myteam.id}) + ClientPlayer.PrivilegeForce = t["force"] + else: + enemy.id = t["id"] + + myteam.meteor = msg_map['meteor'] + myteam.tunnel = msg_map['tunnel'] + myteam.wormhole = msg_map['wormhole'] + + # logger.debug(f"Leg start: {msg_data}") + +def round(msg): + ''' + :param msg: dict + :return: + return type: dict + ''' + msg_data = msg['msg_data'] + round_id = msg_data['round_id'] + action = [] + + if 'players' in msg_data: + players = msg_data['players'] + # logger.debug("========Code Needs Update=======") + enemy.players = {} + if 'power' in msg_data: + myteam.powers = msg_data['power'] + else: + myteam.powers = [] + mode = msg_data["mode"] + myteam.privilege = (mode == ClientPlayer.PrivilegeForce) + for team in msg_data["teams"]: + if team['id'] == constants.team_id: + myteam.remain_life = team['remain_life'] + else: + enemy.remain_life = team['remain_life'] + # logger.info(f"[R{round_id}]Myteam is in privilege mode") + + for player in players: + if player['team'] == constants.team_id: + # my player + myteam.addPlayer(player, replace=False) + else: + enemy.addPlayer(player) + # logger.debug(f"------{myteam.players}------") + strategy.makeAction(action, round_id) + + + # logger.info(f"=====\nround data {msg_data}\n {myteam.powers}=======") + + result = { + "msg_name": "action", + "msg_data": { + "round_id": round_id, + 'actions': action + } + } + return result + + +legInfo = [] +def leg_end(msg): + ''' + :param msg: + { + "msg_name" : "leg_end", + "msg_data" : { + "teams" : [ + { + "id" : 1001, #队ID + "point" : 770 #本leg的各队所得点数 + }, + { + "id" : 1002, + "point" : 450 + } + ] + } + } + ''' + logger.info("leg end") + teams = msg["msg_data"]['teams'] + for team in teams: + legInfo.append("teams: %s, point: %d" % (team['id'], team['point'])) + + +def game_over(msg): + logger.info(legInfo) + logger.info("game over!") diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/strategy.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/strategy.py new file mode 100644 index 0000000..806c81b --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/service/strategy.py @@ -0,0 +1,422 @@ +from ballclient.service.clientteam import ClientPlayer, ClientTeam +from ballclient.util import logger +import random + +""" +TODO 追击敌人时,多只鲲一起协作 +2. 鲲原地打转问题 + +3. 增加一个可视区域,Privilege 尽量保证可视区域最大 +4. 己方不能重叠 + +- 计算权重时,不考虑方向了 +- 权重的计算公式 +weight funciton (x-1)^3-x+2 or y= 4.25-5.04 x + 1.75 x^2 +""" +class Strategy(): + Direction = ['right', 'left', 'down', 'up'] + WeightMeteor = 3 + WeightTunnel = 0.5 + WeightWormhole = 0.9 + WeightTunnelPeer = 1.2 + WeightWormholePeer = 2.0 + + WeightTunnelUnprivilege = 15 + WeightWormholeUnprivilege = 18 + WeightEnemyUnprivilege = 200 + WeightPower = 160 + WeightEnemy = 120 + + WeightEmpty = 0.5 + + RightHint = 0x800 + LeftHint = 0x400 + DownHint = 0x200 + UpHint = 0x100 + + RightDownHint = 0x80 + RightUpHint = 0x40 + LeftDownHint = 0x20 + LeftUpHint = 0x10 + + DownRightHint = 0x8 + DownLeftHint = 0x4 + UpRightHint = 0x2 + UpLeftHint = 0x1 + + def __init__(self, myteam, enemy): + self._direction = None + self.weight = 0 + self.myteam = myteam + self.enemy = enemy + self._weightDir = [0] * 4 + self.pos2 = [ + [1, 1], [2, 1], [1, 2], [2, 2] + ] + self.pos3 = [ + # fisrt vx, second vy + [1, 1], [2, 1], [1, 2], [2, 2], + [3, 1], [1, 3], [3, 2], [2, 3], [3, 3] + ] + + @property + def direction(self): + if self._direction is None: + return [] + return [self._direction] + + def ableToGo(self, x, y): + if self.myteam.isMeteor(x, y): + # logger.debug(f"[R{self.round} - abletogo ({x}, {y})] Meteor False") + return False + if self.enemy.isPlayer(x, y) is not None: + return self.myteam.privilege + return True + + def _calcTunnelWeight(self, player, tunnel, direct, **kwargs): + # TODO judge direction and next + + nx, ny = self.myteam.tunnelNext(tunnel) + if nx == player.x and ny == player.y: + # logger.debug(f"tunnel next is current loc") + return - 100000 + tdis = abs(nx - player.x - 1) + abs(ny - player.y - 1) + if tdis > ClientTeam.Vision: + tdis = abs(tdis - ClientTeam.Vision) + 1 + elif tdis == 0: + tdis += 1 + # tmp = self._calcWeight(player, nx, ny, dis=tdis, trans=False, direct=direct) + tmpList = [0, 0, 0, 0] + # if (direct & Strategy.RightHint) == Strategy.RightHint: # 右 + # direct = 0x8c0 + # if (direct & Strategy.LeftHint) == Strategy.LeftHint: # 左 + # direct = 0x430 + # if (direct & Strategy.DownHint) == Strategy.DownHint: # 下 + # direct = 0x20c + # if (direct & Strategy.UpHint) == Strategy.UpHint: # 上 + # direct = 0x103 + self.weightDirection(player, {"x": nx, "y": ny, "vx": 1, "vy": 1}, weightDir=tmpList, direct=direct, trans = False) + # logger.debug(tmpList) + tmp = sum(tmpList) + if self.myteam.privilege == False: + res = (tmp * Strategy.WeightTunnelPeer + random.randint(6, 8) * Strategy.WeightTunnelUnprivilege) + else: + res = (tmp * Strategy.WeightTunnelPeer + random.randint(2, 3) * Strategy.WeightTunnel) + return res + + def _calcWormholeWeight(self, player, wormhole, direct): + # TODO weight change + pwh = self.myteam.peerWormhole(wormhole) + nx = pwh["x"] + ny = pwh["y"] + + tmpList = [0, 0, 0, 0] + self.weightDirection(player, {"x": nx, "y": ny, "vx": 1, "vy": 1}, weightDir=tmpList, direct=0xfff) + tmp = sum(tmpList) + if self.myteam.privilege == False: + return random.randint(10, 15) * Strategy.WeightWormholeUnprivilege + tmp * Strategy.WeightWormholePeer + return random.randint(1, 2) * Strategy.WeightWormhole + tmp * Strategy.WeightWormholePeer + + def _calcWeightIgnoreDis(self, player, x, y, trans = True, direct = 0xfff): + """x, y tried pos(next) + @return weight + 不需要计算距离 + """ + tunnel = self.myteam.isTunnel(x, y) + if tunnel is not None: + if not trans: + return Strategy.WeightEmpty + # logger.debug(f"[R{self.round}] Tunnel ({x}, {y})") + return self._calcTunnelWeight(player, tunnel, direct) + + wormhole = self.myteam.isWormhole(x, y) + if wormhole is not None: + # logger.debug(f"[R{self.round}] Wormhole ({x}, {y})") + return self._calcWormholeWeight(player, wormhole, direct) + + # dis = pow(dis, 2) + if self.myteam.isMeteor(x, y): + # logger.debug(f"[R{self.round}] Meteor ({x}, {y})") + return -0.5 * Strategy.WeightMeteor + + power = self.myteam.isPower(x, y) + if power is not None: + # logger.debug(f"[R{self.round}] Pos({x}, {y}) Power {power}") + return power * Strategy.WeightPower + + ene = self.enemy.isPlayer(x, y) + if ene is not None: + if self.myteam.privilege == False: + score = (- ene.score - player.score) * Strategy.WeightEnemyUnprivilege - 80 * self.enemy.nearmate(x, y, dis=3) + else: + nearMate = self.myteam.nearmate(x, y, dis=ClientTeam.Vision) * self.myteam.remain_life + score = (ene.score + 10) * Strategy.WeightEnemy * (0.5 + nearMate) + # logger.debug(f"Tri to catch enemy({x}, {y}), near: {nearMate}") + + # logger.debug(f"[R{self.round}] Enemy ({x}, {y}) score: {score}") + return score + + teammate = self.myteam.isPlayer(x, y, player) + if teammate is not None: + # TODO A -> X <- B + return - 100 + + return Strategy.WeightEmpty * random.randint(0, 3) # empty + + def _calcWeight(self, player, x, y, dis = 1, trans = True, direct = 0xfff): + return self._calcWeightIgnoreDis(player, x, y, trans, direct=direct) / dis + + def weightDirection(self, player, coord, weightDir, specdis = None, direct = 0xfff, **kwargs): + """vx vy 都是距离,正值 + 从目标点 (x, y) 开始,计算附近的位置的权值 + coord {"x": x, "y": y, "vx": vx, "vy": vy} + weightDir array of 4 elements: weightDir 中的值会被累加 + """ + x = coord["x"] + y = coord["y"] + vx = coord["vx"] + vy = coord["vy"] + + txr = x + vx # 往右 + txl = x - vx # 往左 + tyd = y + vy # 往下 + tyu = y - vy # 往上 + # 直线距离 4.25-5.04 x + 1.75 x^2 + otherDis = 1.75 * vx * vx - 5.04 * vx + 4.25 + if (direct & Strategy.RightHint) == Strategy.RightHint: # 右 + weightDir[0] += self._calcWeight(player, txr, y, dis=otherDis, direct=Strategy.RightHint, **kwargs) + if (direct & Strategy.LeftHint) == Strategy.LeftHint: # 左 + weightDir[1] += self._calcWeight(player, txl, y, dis=otherDis, direct=Strategy.LeftHint, **kwargs) + + otherDis = 1.75 * vy * vy - 5.04 * vy + 4.25 + if (direct & Strategy.DownHint) == Strategy.DownHint: # 下 + weightDir[2] += self._calcWeight(player, x, tyd, dis=otherDis, direct=Strategy.DownHint, **kwargs) + if (direct & Strategy.UpHint) == Strategy.UpHint: # 上 + weightDir[3] += self._calcWeight(player, x, tyu, dis=otherDis, direct=Strategy.UpHint, **kwargs) + # otherDis = ((vx + vy - 0.5) * (vx + vy - 0.5) + abs(vx - vy) + 1) + otherDis = 1.75 * (vx+vy) * (vx+vy) - 5.04 * (vx+vy) + 4.25 + if vy < vx or (vx < 2 and vy < 2): + # 以横向为主 + if (direct & Strategy.RightDownHint) == Strategy.RightDownHint: + weightDir[0] += self._calcWeight(player, txr, tyd, otherDis, **kwargs) # 右下 + if (direct & Strategy.RightUpHint) == Strategy.RightUpHint: + weightDir[0] += self._calcWeight(player, txr, tyu, otherDis, **kwargs) # 右上 + + if (direct & Strategy.LeftDownHint) == Strategy.LeftDownHint: + weightDir[1] += self._calcWeight(player, txl, tyd, otherDis, **kwargs) # 左下 + if (direct & Strategy.LeftUpHint) == Strategy.LeftUpHint: + weightDir[1] += self._calcWeight(player, txl, tyu, otherDis, **kwargs) # 左上 + elif vy > vx or (vx < 2 and vy < 2): + # 以纵向为主 + if (direct & Strategy.DownRightHint) == Strategy.DownRightHint: + weightDir[2] += self._calcWeight(player, txr, tyd, otherDis, **kwargs) # 下右 + if (direct & Strategy.DownLeftHint) == Strategy.DownLeftHint: + weightDir[2] += self._calcWeight(player, txl, tyd, otherDis, **kwargs) # 下左 + + if (direct & Strategy.UpRightHint) == Strategy.UpRightHint: + weightDir[3] += self._calcWeight(player, txr, tyu, otherDis, **kwargs) # 上右 + if (direct & Strategy.UpLeftHint) == Strategy.UpLeftHint: + weightDir[3] += self._calcWeight(player, txl, tyu, otherDis, **kwargs) # 上左 + else: + pass + + + def compare(self, player): + """ + """ + mweight = -1000000 + d = -1 + + if player.lastAction is not None and self._weightDir[0] == self._weightDir[1] and self._weightDir[1] == self._weightDir[2] \ + and self._weightDir[2] == self._weightDir[3]: + self._weightDir[Strategy.Direction.index(player.lastAction)] += 30 + + for i in range(4): + if mweight < self._weightDir[i]: + mweight = self._weightDir[i] + d = i + + logger.debug("[R%d %s - %s] <===> %s" + % (self.round, player, Strategy.Direction[d], self._weightDir)) + return Strategy.Direction[d] + + def makeAction(self, action, round): + self.round = round + + for player in self.myteam.players.values(): + # logger.debug(f"[R{self.round}] {player}") + x = player.x + y = player.y + # iterate + # 四种组合,先右后左,先下后上 + for i in range(4): + self._weightDir[i] = 0 + + if not self.ableToGo(x+1, y): + self._weightDir[0] = -100000 + if not self.ableToGo(x-1, y): + self._weightDir[1] = -100000 + + if not self.ableToGo(x, y+1): + self._weightDir[2] = -100000 + if not self.ableToGo(x, y-1): + self._weightDir[3] = -100000 + + for p in self.pos3: + self.weightDirection(player, + coord = {"x": player.x, "y":player.y, "vx": p[0], "vy": p[1]}, + weightDir=self._weightDir, + direct=0xfff) + player.lastAction = self.compare(player) + player.nextPos = self.playerNextPos(player) + # if len(player.posStack) < 5: + + for player in self.myteam.players.values(): + # logger.debug(f"[R{self.round} {self.myteam.privilege}]{player}: {res}") + action.append({"team": player.team, "player_id": player.id, + "move": [player.lastAction]}) + + def playerNextPos(self, player): + if player.lastAction == 'right': + return (player.x + 1, player.y) + elif player.lastAction == 'left': + return (player.x - 1, player.y) + elif player.lastAction == 'up': + return (player.x, player.y - 1) + elif player.lastAction == 'down': + return (player.x, player.y + 1) + + +class Strategy2(Strategy): + + def __init__(self, myteam, enemy): + Strategy.__init__(self, myteam, enemy) + self.pos3 = [ + # fisrt vx, second vy + [1, 0], [0, 1], [1, 1], + [2, 0], [0, 2], [2, 1], [1, 2], [2, 2], + [3, 0], [0, 3], [3, 1], [1, 3], [3, 2], [2, 3], [3, 3] + ] + + def makeAction(self, action, round): + self.round = round + + for player in self.myteam.players.values(): + # logger.debug(f"[R{self.round}] {player}") + x = player.x + y = player.y + # iterate + # 四种组合,先右后左,先下后上 + for i in range(4): + self._weightDir[i] = 0 + + if not self.ableToGo(x+1, y): + self._weightDir[0] = -100000 + if not self.ableToGo(x-1, y): + self._weightDir[1] = -100000 + + if not self.ableToGo(x, y+1): + self._weightDir[2] = -100000 + if not self.ableToGo(x, y-1): + self._weightDir[3] = -100000 + + for p in self.pos3: + coord = {"x": player.x, "y":player.y, "vx": p[0], "vy": p[1]} + coord["tx"] = player.x + p[0] + coord["ty"] = player.y + p[1] + self.weightPos(player, coord = coord, weightDir=self._weightDir, direct=0xfff) + coord["tx"] = player.x + p[0] + coord["ty"] = player.y - p[1] + self.weightPos(player, coord = coord, weightDir=self._weightDir, direct=0xfff) + coord["tx"] = player.x - p[0] + coord["ty"] = player.y + p[1] + self.weightPos(player, coord = coord, weightDir=self._weightDir, direct=0xfff) + coord["tx"] = player.x - p[0] + coord["ty"] = player.y - p[1] + self.weightPos(player, coord = coord, weightDir=self._weightDir, direct=0xfff) + + player.lastAction = self.compare(player) + player.nextPos = self.playerNextPos(player) + # if len(player.posStack) < 5: + # TODO make a controller to control all actions + + for player in self.myteam.players.values(): + # logger.debug(f"[R{self.round} {self.myteam.privilege}]{player}: {res}") + action.append({"team": player.team, "player_id": player.id, + "move": [player.lastAction]}) + + def _calcWeightPos(self, player, x, y, dis=1, dir=-1, trans=True): + tunnel = self.myteam.isTunnel(x, y) + if tunnel is not None: + if not trans: + return Strategy.WeightEmpty + # logger.debug(f"[R{self.round}] Tunnel ({x}, {y})") + return self._calcTunnelWeight(player, tunnel, 0xfff) + + wormhole = self.myteam.isWormhole(x, y) + if wormhole is not None: + # logger.debug(f"[R{self.round}] Wormhole ({x}, {y})") + return self._calcWormholeWeight(player, wormhole, 0xfff) + + # dis = pow(dis, 2) + if self.myteam.isMeteor(x, y): + # logger.debug(f"[R{self.round}] Meteor ({x}, {y})") + return -0.5 * Strategy.WeightMeteor + + power = self.myteam.isPower(x, y) + if power is not None: + # logger.debug(f"[R{self.round}] Pos({x}, {y}) Power {power}") + return power * Strategy.WeightPower + + ene = self.enemy.isPlayer(x, y) + if ene is not None: + if self.myteam.privilege == False: + score = (- ene.score - player.score) * Strategy.WeightEnemyUnprivilege - 80 * self.enemy.nearmate(x, y, dis=3) + else: + nearMate = self.myteam.nearmate(x, y, dis=ClientTeam.Vision) * self.myteam.remain_life + score = (ene.score + 10) * Strategy.WeightEnemy * (0.5 + nearMate) + # logger.debug(f"Tri to catch enemy({x}, {y}), near: {nearMate}") + + # logger.debug(f"[R{self.round}] Enemy ({x}, {y}) score: {score}") + return score + + return Strategy.WeightEmpty * random.randint(0, 3) # empty + + def weightPos(self, player, coord, weightDir, **kwargs): + """ + coord x, y 起始点坐标, vx vy 距离,可以为 0,tx ty 目标点坐标 + """ + x = coord["x"] + y = coord["y"] + vx = coord["vx"] + vy = coord["vy"] + tx = coord["tx"] + ty = coord["ty"] + distance = 1.75 * (vx+vy) * (vx+vy) - 5.04 * (vx+vy) + 4.25 + dir = self.judegDir(x, y, vx, vy, tx, ty) + if dir[0] > -1: + weightDir[dir[0]] += self._calcWeightIgnoreDis(player, tx, ty) / distance + if dir[1] > -1: + weightDir[dir[1]] += self._calcWeightIgnoreDis(player, tx, ty) / distance + + def judegDir(self, x, y, vx, vy, tx, ty): + dirX = -1 + dirY = -1 + # warning: vy == vx 出现了两次 + if vy <= vx: + if tx > x: + # right + dirX = 0 + elif tx < x: + # left + dirX = 1 + # return dirX + if vx <= vy: + if ty > y: + # down + dirY = 2 + elif ty < y: + # up + dirY = 3 + # return dirY + return dirX, dirY \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/ballclient/util.py b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/util.py new file mode 100644 index 0000000..2455aaf --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/ballclient/util.py @@ -0,0 +1,42 @@ +import logging + + +from pathlib import Path + +_BASE_DIR = Path("/var") +_LOG_DIR = _BASE_DIR / "log" + +_LOG_DIR.mkdir(parents=True, exist_ok=True) + +## For Translating + +import logging +def createLogger(name: str, stream = False, level = logging.DEBUG): + """create logger, specify name, for example: test.log + suffix is not necessary but helpful + """ + + # log_file = (_LOG_DIR / name) + + logger = logging.getLogger() + # fh = logging.FileHandler(log_file.absolute()) + fh = logging.FileHandler("%s/%s" % (_LOG_DIR, name)) + fh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s")) + fh.setLevel(level) + logger.setLevel(level) + logger.addHandler(fh) + + if stream: + sh = logging.StreamHandler() + sh.setLevel(level) + sh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(filename)s %(lineno)s: %(message)s")) + logger.addHandler(sh) + + return logger + +logger = None + +def createShareLogger(name: str, stream = False, level = logging.DEBUG): + # print(name) + global logger + logger = createLogger(name, stream=stream, level=level) \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/start.bat b/19-q3q4/huawei kunpen race/brifuture/client/start.bat new file mode 100644 index 0000000..5e4a1f1 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/start.bat @@ -0,0 +1,6 @@ +@echo off +set PATHONPATH=%cd% +python -m ballclient.main %1 %2 %3 +@echo on + +EXIT \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/client/start.sh b/19-q3q4/huawei kunpen race/brifuture/client/start.sh new file mode 100644 index 0000000..3956e96 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python3 -m ballclient.main $1 $2 $3 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/test/__init__.py b/19-q3q4/huawei kunpen race/brifuture/client/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/test/server.py b/19-q3q4/huawei kunpen race/brifuture/client/test/server.py new file mode 100644 index 0000000..e0ecef0 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/test/server.py @@ -0,0 +1,31 @@ +# encoding:utf8 +import socket +import threading + +HOST = "127.0.0.1" +PORT = 3000 + +if __name__ == "__main__": + _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + _socket.bind((HOST, PORT)) + _socket.listen(5) + while 1: + conn, addr = _socket.accept() # 接受TCP连接,并返回新的套接字与IP地址 + print('Connected by', addr) # 输出客户端的IP地址 + data = conn.recv(1024) # 把接收的数据实例化 + conn.sendall('00000{"msg_name":"leg_start","b":2}') + threading._sleep(0.2) + while 1: + conn.sendall('''12345{"msg_name":"round","msg_data":{"round_id":2,"players":[{"id":0,"team":1,"x":0,"y":1, "sleep":1,"score":20}]}}'''); + threading._sleep(0.2) + print "start receive" + data = conn.recv(1024) + print "receive:%s" % data + data = data[5:] + import json + + m = json.loads(data) + print "*****************************" + print m + print "*****************************" +conn.close() # 关闭连接 diff --git a/19-q3q4/huawei kunpen race/brifuture/client/test/service.py b/19-q3q4/huawei kunpen race/brifuture/client/test/service.py new file mode 100644 index 0000000..8309821 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/test/service.py @@ -0,0 +1,22 @@ +#encoding:utf8 +''' +测试 +''' +import random + +current_direction = "" + + +def exec_round(msg): + self_balls = msg['msg_data']["self_balls"] + current_round = msg['msg_data']['round_id'] + result = [] + + for current in self_balls: + result.append( + {"ball_id": current['ball_id'], "force_x": random.randint(1, 10), + "force_y": random.randint(1, 10)}) + + result = {"msg_name": "action", + "msg_data": {"round_id": current_round, "actions": result}} + return result diff --git a/19-q3q4/huawei kunpen race/brifuture/client/test/test.py b/19-q3q4/huawei kunpen race/brifuture/client/test/test.py new file mode 100644 index 0000000..9fd9819 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/test/test.py @@ -0,0 +1,14 @@ +a = '{"msg_name": "action", "msg_data": {"round_id": 2, "action": [{"player_id": 0, "move": ["up"], "team": 1}]}}' + +import json + +import ballclient.comunicate.client as client + +print client.add_str_len(a) + +a = '''{"msg_name": "action", "msg_data": {"round_id": 2, "action": [ + {"player_id": 0, "move": ["up"], "team": 1}]}}''' + + +print type(json.loads(a)) + diff --git a/19-q3q4/huawei kunpen race/brifuture/client/test/test2.py b/19-q3q4/huawei kunpen race/brifuture/client/test/test2.py new file mode 100644 index 0000000..56ef6dc --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/client/test/test2.py @@ -0,0 +1,17 @@ +import ballclient.comunicate.client as client +import json + +a = '{"msg_name": "action", "msg_data": {"round_id": 659, "actions": [{"force_y": 8, "force_x": 10, "ball_id": 0}, {"force_y": 10, "force_x": 9, "ball_id": 1}, {"force_y": 5, "force_x": 1, "ball_id": 2}, {"force_y": 2, "force_x": 5, "ball_id": 3}]}}' +a ='''{"msg_name": "round", + "msg_data": { + "round_id": 2, + "players": [ + { + "id": 0, "team": 1, "x": 0, "y": 1, sleep: 1, score: 20 + } + } +}''' + +b = json.dumps(a) + +print client.add_str_len(b) diff --git a/19-q3q4/huawei kunpen race/brifuture/gameclient.bat b/19-q3q4/huawei kunpen race/brifuture/gameclient.bat new file mode 100644 index 0000000..93398b9 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/gameclient.bat @@ -0,0 +1,6 @@ +pushd %CD% +cd /d client +start.bat %1 %2 %3 +popd + +EXIT \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/brifuture/gameclient.sh b/19-q3q4/huawei kunpen race/brifuture/gameclient.sh new file mode 100644 index 0000000..b3b13c2 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/gameclient.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd "$(dirname "$0")" +cd client +sh start.sh $1 $2 $3 diff --git a/19-q3q4/huawei kunpen race/brifuture/packclient.sh b/19-q3q4/huawei kunpen race/brifuture/packclient.sh new file mode 100644 index 0000000..9b1541e --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/packclient.sh @@ -0,0 +1,2 @@ +#!/bin/sh +zip -r brifuture.zip gameclient.sh client diff --git a/19-q3q4/huawei kunpen race/brifuture/testclient.sh b/19-q3q4/huawei kunpen race/brifuture/testclient.sh new file mode 100644 index 0000000..931f0d8 --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/testclient.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd client +python -m ballclient.main $1 $2 $3 diff --git a/19-q3q4/huawei kunpen race/brifuture/wingame.bat b/19-q3q4/huawei kunpen race/brifuture/wingame.bat new file mode 100644 index 0000000..ca07a2b --- /dev/null +++ b/19-q3q4/huawei kunpen race/brifuture/wingame.bat @@ -0,0 +1,4 @@ +pushd %CD% +cd /d bin +client.exe %1 %2 %3 +popd \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/game.csv b/19-q3q4/huawei kunpen race/server/game.csv new file mode 100644 index 0000000..add9133 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/game.csv @@ -0,0 +1 @@ +map_r2m1.txt,qqcy,1163,0,AI 1.1,1123,0 \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/main.py b/19-q3q4/huawei kunpen race/server/main.py new file mode 100644 index 0000000..678e8ed --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/main.py @@ -0,0 +1,4 @@ +from simserver import startServer +import sys +print("=======", sys.argv) +startServer() \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/map_r2m1.txt b/19-q3q4/huawei kunpen race/server/map_r2m1.txt new file mode 100644 index 0000000..3c1f984 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/map_r2m1.txt @@ -0,0 +1,36 @@ +{ + "game":{ + "revive_player_pos":false, + "revive_times":4, + "vision":3, + "power_create_num":10, + "timeout":800 + }, + "map": + { + "height":20, + "width":20, + "map_str":" + .X........X........A + ..................## + ...1...1....1...1... + ..1..............1.. + .......##..##....... + .....^>>>>>>>>>..... + .....^.......bv..... + O.1.#^........v#.1.O + ....#^..2112..v#.... + .....^..1551..v..... + .....^..1551..v..... + ....#^..2112..v#.... + O.1.#^........v#.1.O + .....^a.......v..... + .....<<<<<<<< 0.8: + found = False + for po in self._powers: + if po.x == i and po.y == j: + found = True + if found: continue + + self.addPower(i, j) + pc += 1 + logger.debug(f"generate Power at ({i}, {j})") + + def isPower(self, x: int, y: int) -> Power: + """Return Power action if True or None will return + """ + for p in self._powers: + if x == p.x and y == p.y and p.exist: + return p + return None + + @property + def powers(self): + return [x.pack() for x in self._powers] + + @property + def powerLen(self): + return len(self._powers) + + + def tunnelType(self, x: int, y: int): + t = self._map_str_list[y][x] + if t == GameMap.PosTunnelUp: + return "up" + elif t == GameMap.PosTunnelDown: + return "down" + elif t == GameMap.PosTunnelLeft: + return "left" + elif t == GameMap.PosTunnelRight: + return "right" + + def addPower(self, i, j, value = None): + p = Power(self.powerId, i, j) + self.powerId += 1 + self._powers.append(p) + if value is not None: + p.value = value + + def _parse(self, map_str_list: list, width: int): + self.width = width + self.height = width + + for j, map_str in enumerate(map_str_list): + for i in range(width): + if map_str[i] == GameMap.PosMeteor: + self._pushMeteor(i, j) + elif map_str[i] in GameMap.PosTunnels: + self._pushTunnel(i, j, map_str[i]) + elif map_str[i] in GameMap.PosWormhole: + self._pushWormhole(i, j, map_str[i]) + elif map_str[i] in GameMap.PosTeam: + self._pushTeam(i, j, map_str[i]) + elif map_str[i] == BaseGameMap.PosEmpty: + continue + else: + try: + value = int(map_str[i]) + self.addPower(i, j, value) + except Exception as e: + logger.warning(e) + + +class GameMap(BaseGameMap): + """ + Property: meteor, tunnel, wormhole, + teamInitPos(teamA, teamB), vision, height + """ + + PosTunnelDesc = ["up", "down", "left", "right"] + + def __init__(self, mapFile: str): + BaseGameMap.__init__(self) + self.map = {} + self._players = {} + with open(mapFile) as f: + f.readline() + content = "" + while True: + text = f.readline().strip() + content += text + # logger.debug(text) + if text.find("},") > -1: + break + # logger.debug(content[7:-1]) + self.map["game"] = json.loads(content[7:-1]) + Team.Vision = self.map["game"]["vision"] + BaseGameMap.PowerCreateNum =self.map["game"]["power_create_num"] + self.map["map"] = {} + for text in f: + text = text.strip() + if text.find("height") > -1: + i = text.index("height") + self.map["map"]["height"] = int(text[i + 2 +len("length"):-1]) + if text.find("width") > -1: + i = text.index("width") + self.map["map"]["width"] = int(text[i + 1 +len("length"):-1]) + if text.find("map_str") > -1: + break + # map_str_list 每一行存放的是 该行的 map 信息 + map_str_list = [] + + for i, text in enumerate(f): + text = text.strip() + if text.find("\"") > -1: break + map_str_list.append(text) + i += 1 + # logger.debug(map_str_list) + self._parse(map_str_list, self.map["map"]["width"]) + self._map_str_list = map_str_list + + def isPlayer(self, x: int, y: int): + """Return Player Instance if there is, or None will be returned + """ + for p in self._players.values(): + if x == p.x and y == p.y: + return p + return None + + + def addPlayer(self, pid, player: Player): + self._players[pid] = player + + + def getPowers(self, team: Team): + power = [] + for po in self._powers: + see = False + for pe in team.players: + # pe = Player() + if pe.status == Player.StatusDead: + continue + if po.isVisibleToPlayer(pe): + see = True + break + if see: + power.append(po.pack()) + return power + + @property + def mines(self): + return [x.save_pack() for x in self._powers if x.exist] + + @property + def playerList(self): + return self._players.values() + + def eatPower(self, player: Player): + """dep + """ + dp = None + for power in self._powers: + if power["x"] == player.x and power["y"] == player.y: + player.score += power.value + dp = power + break + if dp is not None: + self._powers.remove(dp) + \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/simserver/gameteam.py b/19-q3q4/huawei kunpen race/server/simserver/gameteam.py new file mode 100644 index 0000000..901da70 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/gameteam.py @@ -0,0 +1,113 @@ +_ForceBeat = "beat" +_ForceThink = "think" + +class BasePlayer(): + StatusAlive = "alive" + StatusDead = "Dead" + + ActionNone = "blank" + ActionNormal = ["up", "down", "left", "right"] + + def __init__(self, id: int, teamid: int): + self.score = 0 + self._sleep = 0 + self.x = 0 + self.y = 0 + self.action = BasePlayer.ActionNone + self.force = None + self.status = BasePlayer.StatusAlive + self.id = id + self.team = teamid + self.group = None + + @property + def sleep(self): + return 1 if self._sleep else 0 + + @staticmethod + def testMove(x, y, action): + if action == "up": + return x, y - 1 + elif action == "down": + return x, y + 1 + elif action == "left": + return x - 1, y + elif action == "right": + return x + 1, y + else: + return x, y + + def _beforeMove(self): + self.lastX = self.x + self.lastY = self.y + + def undoMove(self): + self.x = self.lastX + self.y = self.lastY + + def moveUp(self): + self._beforeMove() + self.y -= 1 + + def moveDown(self): + self._beforeMove() + self.y += 1 + + def moveLeft(self): + self._beforeMove() + self.x -= 1 + + def moveRight(self): + self._beforeMove() + self.x += 1 + +class BaseTeam(): + ForceBeat = _ForceBeat + ForceThink = _ForceThink + + def __init__(self): + self.id = 0 + self.conn = None + self.name = None + self.remain_life = 0 + self.registered = False + self.players = [] + self.playerId = [] + self.force = BaseTeam.ForceBeat + + @property + def point(self): + po = 0 + for p in self.players: + po += p.score + return po + + + def save_pack_round(self): + return { + "point": self.point, + "remain_life": self.remain_life, + "team_id": self.id + } + + def leg_start_pack(self, force): + self.setForce(force) + p = { + "id": self.id, + "players": self.playerId, + "force": self.force + } + return p + + def setForce(self, force): + self.force = force + +import random + +class BasePower(): + def __init__(self, id, x, y): + self.id = id + self.x = x + self.y = y + self.value = random.randint(1, 5) + self.exist = True \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/simserver/interface.md b/19-q3q4/huawei kunpen race/server/simserver/interface.md new file mode 100644 index 0000000..5883753 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/interface.md @@ -0,0 +1,145 @@ +## Interface SD Communicate Protocol + +1. registration() --> 客户端发送注册消息 + +>> Loop Race Leg + +2. leg_start() <---- 独立子场(leg)开始消息。 + +>> Loop Round 裁判程序给各参赛选手广播的当前会和的游戏相关的全部信息 + +3. round() <---- + +4. action() ----> + +<< Loop Round end + +5. leg_end() + +<< Loop Race Leg end + +6. game_over() + +> 注:服务器和客户端启动顺序不确定 + +### Message + +小写字符串。格式:消息长度+消息体 + +#### registration : 客户端发送的注册消息 + +```json +00158{"msg_name": "registration", "msg_data": {"team_id": 1000, "team_name": "test_demo"}} +``` + +#### leg_start: leg 开始消息 + +裁判程序给各参赛选手广播地图相关信息,以及选手的分组信息。 + +vision : 默认视野4,即视野范围9*9的方形空间 + +```json +{ + "msg_name": "leg_start", + "msg_data": { + "map": { + "width": 15, + "height": 15, + "vision": 3, + "meteor": [ + {"x": 1, "y": 1}, + {"x": 1, "y": 4} + ], + "tunnel": [ + {"x": 3, "y": 1, "direction": "down"}, + {"x": 3, "y": 4, "direction": "down"} + ], + "wormhole": [ + {"name": "a","x": 4, "y": 1}, + {"name": "A", "x": 4, "y": 4 } + ] + + }, + "teams" : [ + {"id": 1001, "players": [0, 1, 2, 3], "force": "beat"}, + {"id": 1002, "players": [4, 5, 6, 7], "force": "think"}, + + ] + } +} +``` + + +#### round + +裁判程序给各参赛选手广播的当前会和的游戏相关的全部信息。 + +```json +{ + "msg_name": "round", + "msg_data": { + "round_id": 2, + "mode": "beat", + "power": [ + {"x":5,"y":2, "point":1}, + {"x":5,"y":5, "point":2} + ], + "players": [ + { + "id":0,"score":0,"sleep":0,"team":1001,"x":0,"y":1 + } + ], + "teams": [ + {"id":1001,"point":0, "remain_life":2}, + {"id":1002,"point":0, "remain_life":3} + + ] + } +} +``` + + +#### action + +参赛选手反馈在本回合准备执行的动作。每个参赛选手每回合只反馈一条消息,包含所有player的动作请求。 + +```json +{ + "msg_name": "action", + "msg_data": { + "round_id": 2, + "actions": [ + {"team": 1002, "player_id": 5, "move": ["up"]} + ] + } +} +``` + +#### leg_end + +```json +{ + "msg_name" : "leg_end", + "msg_data" : { + "teams" : [ + { + "id" : 1001, + "point" : 770 + }, + { + "id" : 1002, + "point" : 450 + } + ] + } + +} +``` + +#### game_over + +```json +{ + "msg_name" : "game_over" +} +``` \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/simserver/mapelement.py b/19-q3q4/huawei kunpen race/server/simserver/mapelement.py new file mode 100644 index 0000000..40f9879 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/mapelement.py @@ -0,0 +1,150 @@ +from .gameteam import BasePlayer, BaseTeam, _ForceBeat, _ForceThink, BasePower +from .util import logger + +class Player(BasePlayer): + PrivilegeForce = _ForceBeat + + """包含 id, teamid,位置(x y) + """ + def __init__(self, id: int, teamid: int): + BasePlayer.__init__(self, id, teamid) + + + def setAction(self, action): + if action in Player.ActionNormal: + self.action = action + else: + self.action = Player.ActionNone + + def triMove(self): + """According to action, change x, y + """ + return Player.testMove(self.x, self.y, self.action) + + @staticmethod + def playerKillAnother(killer: BasePlayer, killed: BasePlayer): + """Player killer killed Player killed + """ + killer.score += killed.score + killed.score = 0 + killed._sleep = 1 + killed.group.remain_life -= 1 + + + + def eatPower(self, power: BasePower): + self.score += power.value + logger.debug(f"===> {self} eat {power}") + power.exist = False + + def meetPlayer(self, player: BasePlayer): + if player.team == self.team: + return + + # TODO + if player.force == Player.PrivilegeForce: + Player.playerKillAnother(player, self) + else: + Player.playerKillAnother(self, player) + + def pack(self): + return { + "id": self.id, + "score": self.score, + "sleep": self._sleep, + "team": self.team, + "x": self.x, + "y": self.y, + # "status": self.status + } + + def save_pack_round(self): + return { + # "moveOn": self.action, + "moveOn": "blank", + "player_id": self.id, + "team_id": self.team, + "point": self.score, + "sleep": self._sleep, + "status": self.status, + "x": self.x, + "y": self.y + } + + def __repr__(self): + return f' ' + +class Team(BaseTeam): + """Team + """ + # 视野 + Vision = 3 + def __init__(self): + BaseTeam.__init__(self) + + def getPlayer(self, pid): + """Return player if found + """ + for p in self.players: + if p.id == pid: + return p + return None + + def setForce(self, force): + self.force = force + for p in self.players: + p.force = force + + def addPlayer(self, pid: int, x: int, y: int): + p = Player(pid, self.id) + p.x = x + p.y = y + p.force = self.force + p.group = self + self.players.append(p) + self.playerId.append(pid) + return p + + def round_player_pack(self, another: BaseTeam): + """返回当前队伍的 Player 及能够看到的对方队伍的 Player + """ + pack = [] + for p in self.players: + pack.append(p.pack()) + for ap in another.players: + for p in self.players: + if abs(p.x - ap.x) <= Team.Vision and abs(p.y - ap.y) <= Team.Vision: + pack.append(ap.pack()) + break + return pack + + def __repr__(self): + return f'' + +class Power(BasePower): + def __init__(self, id, x, y): + BasePower.__init__(self, id, x, y) + + def isVisibleToPlayer(self, player: Player) -> bool: + if abs(player.x - self.x) <= Team.Vision and abs(player.y - self.y) <= Team.Vision: + return True + return False + + def pack(self): + return { + "point": self.value, + "x": self.x, + "y": self.y + } + + def save_pack(self): + m = { + "id": self.id, + "value": self.value, + "x": self.x, + "y": self.y + } + return m + + def __repr__(self): + return f'' diff --git a/19-q3q4/huawei kunpen race/server/simserver/params.py b/19-q3q4/huawei kunpen race/server/simserver/params.py new file mode 100644 index 0000000..e69de29 diff --git a/19-q3q4/huawei kunpen race/server/simserver/replay.txt b/19-q3q4/huawei kunpen race/server/simserver/replay.txt new file mode 100644 index 0000000..80b8208 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/replay.txt @@ -0,0 +1,33 @@ +01692{"belts":[{"dir":"up","x":5,"y":5},{"dir":"right","x":6,"y":5},{"dir":"right","x":7,"y":5},{"dir":"right","x":8,"y":5},{"dir":"right","x":9,"y":5},{"dir":"right","x":10,"y":5},{"dir":"right","x":11,"y":5},{"dir":"right","x":12,"y":5},{"dir":"right","x":13,"y":5},{"dir":"right","x":14,"y":5},{"dir":"up","x":5,"y":6},{"dir":"down","x":14,"y":6},{"dir":"up","x":5,"y":7},{"dir":"down","x":14,"y":7},{"dir":"up","x":5,"y":8},{"dir":"down","x":14,"y":8},{"dir":"up","x":5,"y":9},{"dir":"down","x":14,"y":9},{"dir":"up","x":5,"y":10},{"dir":"down","x":14,"y":10},{"dir":"up","x":5,"y":11},{"dir":"down","x":14,"y":11},{"dir":"up","x":5,"y":12},{"dir":"down","x":14,"y":12},{"dir":"up","x":5,"y":13},{"dir":"down","x":14,"y":13},{"dir":"left","x":5,"y":14},{"dir":"left","x":6,"y":14},{"dir":"left","x":7,"y":14},{"dir":"left","x":8,"y":14},{"dir":"left","x":9,"y":14},{"dir":"left","x":10,"y":14},{"dir":"left","x":11,"y":14},{"dir":"left","x":12,"y":14},{"dir":"left","x":13,"y":14},{"dir":"down","x":14,"y":14}],"block_name":"leg_start","gates":[{"name":"A","x":19,"y":0},{"name":"b","x":13,"y":6},{"name":"a","x":6,"y":13},{"name":"B","x":0,"y":19}],"map_size":{"height":20,"width":20},"max_round":30,"mode":"light","switch_round":15,"teams":[{"team_id":1152,"team_name":"qqcy","team_player_id_list":[0,1,2,3,4,5,6,7]},{"team_id":1123,"team_name":"AI 1.1","team_player_id_list":[]}],"walls":[{"x":18,"y":1},{"x":19,"y":1},{"x":7,"y":4},{"x":8,"y":4},{"x":11,"y":4},{"x":12,"y":4},{"x":4,"y":7},{"x":15,"y":7},{"x":4,"y":8},{"x":15,"y":8},{"x":4,"y":11},{"x":15,"y":11},{"x":4,"y":12},{"x":15,"y":12},{"x":7,"y":15},{"x":8,"y":15},{"x":11,"y":15},{"x":12,"y":15},{"x":0,"y":18},{"x":1,"y":18}]} +01427{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":38,"value":3,"x":18,"y":18},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":7},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":7},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":12},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":12},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":9,"y":19},{"moveOn":"up","player_id":7,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":18,"y":19}],"round_id":0,"teams":[{"point":0,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01393{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":6},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":6},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":11},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":11},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":9,"y":18},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":18}],"round_id":1,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01393{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":5},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":5},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":10},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":10},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":9,"y":17},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":17}],"round_id":2,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":4},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":4},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":9},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":9},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":9,"y":16},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":16}],"round_id":3,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":3},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":3},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":8},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":8},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":9,"y":15},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":15}],"round_id":4,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":2},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":7},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":7},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":14},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":14}],"round_id":5,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":1},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":6},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":6},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":13}],"round_id":6,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":5},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":5},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":12}],"round_id":7,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":4},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":4},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":11}],"round_id":8,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":3},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":3},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":10}],"round_id":9,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01391{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":41,"value":2,"x":18,"y":8},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":2},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":3,"sleep":0,"status":"alive","x":18,"y":9}],"round_id":10,"teams":[{"point":3,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01358{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":1},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":8}],"round_id":11,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01358{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":7}],"round_id":12,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01358{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":6}],"round_id":13,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01358{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"light","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":5}],"round_id":14,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01357{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":4}],"round_id":15,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01357{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":3}],"round_id":16,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01357{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":17,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01357{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":18,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01357{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":19,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":20,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":21,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":22,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":23,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":24,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":25,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":26,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":27,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":28,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01854{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"up","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"up","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"up","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"up","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"up","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"up","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":29,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +01878{"block_name":"round","mines":[{"id":30,"value":5,"x":16,"y":5},{"id":31,"value":3,"x":1,"y":5},{"id":32,"value":3,"x":11,"y":3},{"id":33,"value":2,"x":8,"y":12},{"id":34,"value":2,"x":4,"y":10},{"id":35,"value":4,"x":7,"y":12},{"id":36,"value":4,"x":10,"y":11},{"id":37,"value":2,"x":15,"y":10},{"id":39,"value":1,"x":12,"y":3},{"id":40,"value":5,"x":15,"y":15},{"id":42,"value":3,"x":19,"y":15},{"id":43,"value":5,"x":16,"y":17},{"id":44,"value":2,"x":1,"y":4},{"id":45,"value":1,"x":3,"y":7},{"id":46,"value":3,"x":8,"y":17},{"id":47,"value":3,"x":12,"y":2},{"id":48,"value":2,"x":10,"y":12},{"id":49,"value":2,"x":9,"y":11},{"id":50,"value":1,"x":19,"y":18},{"id":51,"value":4,"x":12,"y":9},{"id":52,"value":5,"x":14,"y":4},{"id":53,"value":5,"x":11,"y":2},{"id":54,"value":5,"x":11,"y":6},{"id":55,"value":2,"x":17,"y":12},{"id":56,"value":1,"x":7,"y":13},{"id":57,"value":1,"x":6,"y":11},{"id":58,"value":3,"x":8,"y":7},{"id":59,"value":4,"x":10,"y":18}],"mode":"dark","players":[{"moveOn":"blank","player_id":0,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"blank","player_id":1,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"blank","player_id":2,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":0,"y":0},{"moveOn":"blank","player_id":3,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":19,"y":2},{"moveOn":"blank","player_id":4,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":1,"y":0},{"moveOn":"blank","player_id":5,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":10,"y":0},{"moveOn":"blank","player_id":6,"team_id":1152,"point":0,"sleep":0,"status":"alive","x":4,"y":13},{"moveOn":"blank","player_id":7,"team_id":1152,"point":5,"sleep":0,"status":"alive","x":18,"y":2}],"round_id":30,"teams":[{"point":5,"remain_life":4,"team_id":1152},{"point":0,"remain_life":4,"team_id":1123}]} +00078{"block_name":"leg_end","teams":[{"id":1152,"point":5},{"id":1123,"point":0}]} diff --git a/19-q3q4/huawei kunpen race/server/simserver/server.py b/19-q3q4/huawei kunpen race/server/simserver/server.py new file mode 100644 index 0000000..ba75c06 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/server.py @@ -0,0 +1,297 @@ +""" +""" + +import json +from .util import logger +import traceback +from .gamemap import GameMap, Player, Team +from .sockserver import SockServer, sendMsg +import time + +class BaseSimServer(): + ModeExchangRound = 10 + LegMaxRound = 20 + # ModeExchangRound = 150 + # LegMaxRound = 300 + TotalLife = 4 + + DebugMode = True + + PlayerAIdStart = 0 + PlayerBIdStart = 4 + + def __init__(self, port: int, mapFile: str): + self._ss = SockServer(port) + self.teamA = Team() + self.teamB = Team() + self.mapFile = mapFile + self.mapObj = GameMap(mapFile) + BaseSimServer.TotalLife = self.mapObj.map["game"]["revive_times"] + self.round = 0 + self.teamCount = 0 + + from .tracesaver import TraceSaver + self.traceSaver = TraceSaver(self) + + def save_gamecsvFile(self): + content = [self.mapFile, self.teamA.name, self.teamA.id, self.teamA.point, + self.teamB.name, self.teamB.id, self.teamB.point] + with open("game.csv", "w") as f: + logger.debug(f"content write: {content}") + f.write(",".join(map(str, content))) + + def start(self): + data = None + try: + # start of registration + # wait Team A + while self.teamCount < 2: + if not self.teamA.registered: + self._register(self.teamA, self.mapObj.teamA, BaseSimServer.PlayerAIdStart) + + # wait Team B + if not self.teamB.registered: + self._register(self.teamB, self.mapObj.teamB, BaseSimServer.PlayerBIdStart) + + logger.info(f"===========Registration finished=========") + + self.save_gamecsvFile() + + # end of registration + + self._loop_leg() + + # send game over + self._broadcast({"msg_name": "game_over"}) + logger.info(f"Game Over, A: {self.teamA}, B: {self.teamB}") + except Exception as e: + logger.warning(f"[Start] Error: {e}, data: {data}") + traceback.print_exc() + finally: + self.traceSaver.close() + self._ss.sock.close() + + # if SimServer.DebugMode: + # self.start() + + def _register(self, team: Team, initPos: dict, offset: int): + conn, _ = self._ss.accept() + team.conn = conn + data = self._ss.recv(conn) + # logger.debug(data) + if data["msg_name"] != "registration": + logger.info(f"Expect Registration, not recved: {data}") + return + team.id = data["msg_data"]["team_id"] + team.name = data["msg_data"]["team_name"] + team.remain_life = SimServer.TotalLife + team.registered = True + + for i, xy in enumerate(initPos): + p = team.addPlayer(i + offset, xy["x"], xy["y"]) + self.mapObj.addPlayer(i + offset, p) + logger.info(f"Player created: {p}") + + logger.info(f"Registered: {team.id}, pos inited") + self.teamCount += 1 + + def _leg_start(self, aForce, bForce): + teams = [ + self.teamA.leg_start_pack(aForce), + self.teamB.leg_start_pack(bForce) + ] + logger.debug(f"leg_start: {teams}") + map = { + "width": self.mapObj.width, + "height": self.mapObj.height, + "vision": Team.Vision, + "meteor": self.mapObj.meteor, + "tunnel": self.mapObj.tunnel, + "wormhole": self.mapObj.wormhole + } + msg = { + "msg_name": "leg_start", + "msg_data": { + "map": map, + "teams": teams + } + } + self._sendA(msg) + self._sendB(msg) + self.traceSaver.leg_start() + + def _loop_leg(self): + pass + + + def _leg_end(self): + msg = { + "msg_name": "leg_end", + "msg_data": { + "teams": [ + { "id": self.teamA.id, "point": self.teamA.point }, + { "id": self.teamB.id, "point": self.teamB.point }, + ] + } + } + self._broadcast(msg) + self.traceSaver.leg_end() + + def _sendA(self, msg): + return sendMsg(self.teamA.conn, msg) + + def _sendB(self, msg): + return sendMsg(self.teamB.conn, msg) + + def _broadcast(self, msg): + self._sendA(msg) + self._sendB(msg) + + + +class SimServer(BaseSimServer): + + def __init__(self, port : int, mapFile: str): + BaseSimServer.__init__(self, port, mapFile) + self.running = False + self.leg = 0 + Player.PrivilegeForce = Team.ForceBeat + # self.mapObj.addPowers(20) + + def _loop_leg(self, whole = False): + # start of leg start + self._leg_start(Team.ForceBeat, Team.ForceThink) + # 上半场, 先火后水 + self._half_leg_mode(Team.ForceBeat) # mode A + self._half_leg_mode(Team.ForceThink, SimServer.ModeExchangRound) # mode B + for p in self.mapObj.playerList: + p.setAction("") + self.traceSaver.round() + self._leg_end() # send leg end + logger.info(f"End Start Leg 1") + + if not whole: + return + # start of leg start + self._leg_start(Team.ForceThink, Team.ForceBeat) + # 下半场 先水后火 + self._half_leg_mode(Team.ForceThink) # mode A + self._half_leg_mode(Team.ForceBeat, SimServer.ModeExchangRound) # mode B + self._leg_end() # send leg end + logger.info(f"End Start Leg 2") + + def _half_leg_mode(self, privilegeForce, roundOff = 0): + # loop round + self.round = roundOff + Player.PrivilegeForce = privilegeForce + logger.info(f"PrivilegeForce is {privilegeForce}") + # privilegeForce == Team.ForceBeat and teamA.force is Team.ForceBeat, A 是优势 + firstTeam = self.teamA + secondTeam = self.teamB + if self.teamA.force == Player.PrivilegeForce: + firstTeam = self.teamB + secondTeam = self.teamA + while self.round < SimServer.ModeExchangRound + roundOff: + logger.debug(f"-----Round: {self.round}----") + + self.traceSaver.round() + + self._teamRound(firstTeam, secondTeam) + aData = self._ss.recv(firstTeam.conn) + aData["team"] = firstTeam # for debug + self._recv_actions(aData) + self._move_team(firstTeam) + + self._teamRound(secondTeam, firstTeam) + bData = self._ss.recv(secondTeam.conn) + bData["team"] = secondTeam # for debug + self._recv_actions(bData) + self._move_team(secondTeam) + + logger.debug(f"------actionA - {aData}------\n----actionB - {bData}-----") + + # TODO uncomment following + if self.round % 8 == 0 and self.mapObj.powerLen < 15: + self.mapObj.addPowers(self.mapObj.map["game"]["power_create_num"]) + + # input("wait") + # if aData["round_id"]: # check round id + + self.round += 1 + time.sleep(0.01) + # end of half_leg, mode needs exchange + + def _teamRound(self, team: Team, another: Team): + """Send msg to team, another for reference + """ + players = team.round_player_pack(another) + + teams = [ + { "id": team.id, "point": team.point, "remain_life": team.remain_life}, + { "id": another.id, "point": another.point, "remain_life": another.remain_life} + ] + msg = { + "msg_name": "round", + "msg_data": { + "round_id": self.round , + "mode": Player.PrivilegeForce, + "power": self.mapObj.getPowers(team), + "players": players, + "teams": teams + } + } + logger.debug(f"---Round---: {team} Recv [{msg}]") + sendMsg(team.conn, msg) + + def _recv_actions(self, action: dict): + if action["msg_name"] != "action": + logger.warning(f"Round: {self.round}, Recved msg not action, msg: {action}") + return + for act in action["msg_data"]["actions"]: + if act["team"] != action["team"].id: continue + + pid = act["player_id"] + player = action["team"].getPlayer(pid) + player_actname = "" + if len(act["move"]) == 1: + player_actname = act["move"][0] + + player.setAction(player_actname) + + def _move_team(self, team: Team): + for p in team.players: + self._move_player(p) + + def _move_player(self, player: Player): + if player.action == Player.ActionNone: return + + # 获取下一个位置 + x, y = player.triMove() + if x < 0 or x >= self.mapObj.height \ + or y < 0 or y >= self.mapObj.width \ + or self.mapObj.isMeteor(x, y): + return + player.x = x + player.y = y + if self.mapObj.isTunnel(x, y): + t = self.mapObj.tunnelType(x, y) + player.setAction(t) + # tunnel + self._move_player(player) + return + + if self.mapObj.isWormhole(x, y) and player.action is not Player.ActionNone: + whpeer = self.mapObj.wormholePeer(x, y) + player.x = whpeer["x"] + player.y = whpeer["y"] + return + + power = self.mapObj.isPower(x, y) + if power is not None: + player.eatPower(power) + return + + another = self.mapObj.isPlayer(x, y) + if another is not None: + player.meetPlayer(another) + return diff --git a/19-q3q4/huawei kunpen race/server/simserver/sockserver.py b/19-q3q4/huawei kunpen race/server/simserver/sockserver.py new file mode 100644 index 0000000..204f7b0 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/sockserver.py @@ -0,0 +1,71 @@ +import socket +from .util import logger +import traceback +import json + +SOCKET_CACHE = 1024 * 10 + +def add_str_len(msg_data, offset = 0): + length = str(len(msg_data) + offset) + index = 5 - len(length) + if index < 0: + raise Exception("the return msg data is too long. the length > 99999.") + return '0' * index + length + msg_data + + +def remove_json_num(msg): + return msg[5:] + +class SockServer: + + def __init__(self, port: int): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.bind(("0.0.0.0", port)) + self.sock.listen(2) + self.remainBytes = 0 + + def accept(self): + conn, addr = self.sock.accept() + logger.debug(f"{addr} Connect") + return conn, addr + + def recv(self, conn: socket.socket): + """传入 Connection 对象,返回其接受到的内容 (dict) + @note为了获得完整的数据,在此处添加一个循环 + """ + while 1: + d = None + try: + d = conn.recv(SOCKET_CACHE) + if d[-1] == 0: + d = d[:-1] + d = d.decode() + if self.remainBytes == 0: + if d[0:5].isdigit() and d[5] == "{": + need = int(d[0:5]) + self.remainBytes = need - (len(d) - 5) + # logger.debug(f"remain: {self.remainBytes}, len: {len(d)}") + if(self.remainBytes <= 0): + self._cache = "" + data = remove_json_num(d) + return json.loads(data) + else: + self._cache = d + else: + self.remainBytes -= len(d) + if self.remainBytes == 0: + data = remove_json_num(self._cache + d) + logger.debug(self._cache + d) + self._cache = "" + return json.loads(data) + else: + self._cache += d + except ConnectionResetError as e: + pass + except Exception as e: + logger.warning(f"[Receiver] receive data error. cach the data and wait for next. {e}") + # logger.debug(d) + traceback.print_exc() + +def sendMsg(conn: socket.socket, msg: dict): + return conn.send(add_str_len(json.dumps(msg)).encode()) \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/simserver/tracesaver.py b/19-q3q4/huawei kunpen race/server/simserver/tracesaver.py new file mode 100644 index 0000000..64ddc43 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/tracesaver.py @@ -0,0 +1,130 @@ +import json +from .sockserver import add_str_len +from .server import SimServer +from .mapelement import Team, Player +# add_str_len(json.dumps(msg) + +RoundModeMap = {} +RoundModeMap[Team.ForceBeat] = "light" ## 火 +RoundModeMap[Team.ForceThink] = "dark" + +import os + +WormholeNameMap = { + "A": 65, + "a": 65, + "B": 66, + "b": 66, + "C": 67, + "c": 67, + "D": 68, + "d": 68, +} + +class TraceSaver(): + RoundModeThink = "light" + RoundModeBeat = "dark" + # TODO save trace necessary + def __init__(self, simserver: SimServer): + self.ss = simserver + try: + # os.remove("replay.txt") + self._file = open("replay.txt", "w") + except: + raise ValueError("File Error") + + def close(self): + self._file.close() + + def leg_start(self): + belts = [] + for t in self.ss.mapObj.tunnel: + belts.append({"dir": t["direction"],"x": t["x"],"y": t["y"]}) + + gates = [] + for wh in self.ss.mapObj.wormhole: + # name to char 65 == a + n = WormholeNameMap[wh["name"]] + gates.append({"name": n,"x": wh["x"], "y": wh["y"]}) + + teams = [ + { + "team_id": self.ss.teamA.id, + "team_name": self.ss.teamA.name, + "team_player_id_list": self.ss.teamA.playerId + }, + { + "team_id": self.ss.teamB.id, + "team_name": self.ss.teamB.name, + "team_player_id_list": self.ss.teamB.playerId + } + ] + + walls = self.ss.mapObj.meteor + + block = { + "belts": belts, + "block_name": "leg_start", + "gates": gates, + "map_size": { + "height": self.ss.mapObj.height, + "width": self.ss.mapObj.width + }, + "max_round": SimServer.LegMaxRound, + "mode": RoundModeMap[Player.PrivilegeForce], + "switch_round": SimServer.ModeExchangRound, + "teams": teams, + "walls": walls + } + self._write(block) + + def round(self): + mines = self.ss.mapObj.mines + players = [] + for p in self.ss.mapObj.playerList: + players.append(p.save_pack_round()) + + teams = [ + self.ss.teamA.save_pack_round(), + self.ss.teamB.save_pack_round() + ] + effects = { + "belts": [], + "create_mines": [], + "distroy_mines": [], + "kills": [] + } + toRemove = [] + for k, v in effects.items(): + if len(v) == 0: + toRemove.append(k) + + for k in toRemove: + effects.pop(k) + + block = { + "block_name": "round", + "mines": mines, + "mode": RoundModeMap[Player.PrivilegeForce], + "players": players, + "round_id": self.ss.round, + "teams": teams, + # "effects": effects + } + if len(effects) > 0: + block["effects"] = effects + self._write(block) + + def leg_end(self): + block = { + "block_name": "leg_end", + "teams": [ + { "id": self.ss.teamA.id, "point": self.ss.teamA.point }, + { "id": self.ss.teamB.id, "point": self.ss.teamB.point }, + ] + } + self._write(block) + + def _write(self, msg, offset = 2): + # offset 为 2,匹配原 server + self._file.write(add_str_len(json.dumps(msg, separators=(',', ':')), offset = offset)+"\n") diff --git a/19-q3q4/huawei kunpen race/server/simserver/util.py b/19-q3q4/huawei kunpen race/server/simserver/util.py new file mode 100644 index 0000000..bc1311b --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/simserver/util.py @@ -0,0 +1,46 @@ +import logging + + +from pathlib import Path + +_BASE_DIR = Path("./tmp/clients") +_LOG_DIR = _BASE_DIR / "logs" +_CONFIG_DIR = _BASE_DIR / "configs" + +_LOG_DIR.mkdir(parents=True, exist_ok=True) +_CONFIG_DIR.mkdir(parents=True, exist_ok=True) + +## For Translating + +LANGUAGE_DIR = (Path(__file__).parent / "locale").resolve() +import gettext +def initGetText(domain="myfacilities") -> gettext.gettext: + gettext.bindtextdomain(domain, LANGUAGE_DIR) + gettext.textdomain(domain) + gettext.find(domain, "locale", languages=["zh_CN", "en_US"]) + return gettext.gettext + +import logging +def createLogger(name: str, stream = False, level = logging.DEBUG): + """create logger, specify name, for example: test.log + suffix is not necessary but helpful + """ + + log_file = _LOG_DIR / name + + logger = logging.getLogger() + fh = logging.FileHandler(log_file) + fh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(filename)s %(lineno)s: %(message)s")) + fh.setLevel(level) + logger.setLevel(level) + logger.addHandler(fh) + + if stream: + sh = logging.StreamHandler() + sh.setLevel(level) + sh.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(filename)s %(lineno)s: %(message)s")) + logger.addHandler(sh) + + return logger + +logger = createLogger("server.log", stream=True) \ No newline at end of file diff --git a/19-q3q4/huawei kunpen race/server/test/test_gamemap.py b/19-q3q4/huawei kunpen race/server/test/test_gamemap.py new file mode 100644 index 0000000..1838385 --- /dev/null +++ b/19-q3q4/huawei kunpen race/server/test/test_gamemap.py @@ -0,0 +1,11 @@ +import sys +from pathlib import Path + +pdir = Path(".") +sys.path.insert(0, str(pdir.absolute())) +from simserver.gamemap import GameMap + +gm = GameMap("map_r2m1.txt") + +print(gm.teamA) +print(gm.teamB) \ No newline at end of file diff --git "a/19-q3q4/huawei kunpen race/\345\215\216\344\270\272\344\272\221\351\262\262\351\271\217\345\274\200\345\217\221\350\200\205\345\244\247\350\265\233\350\265\233\351\242\230\346\226\207\346\241\243\350\257\264\346\230\216-0801.docx" "b/19-q3q4/huawei kunpen race/\345\215\216\344\270\272\344\272\221\351\262\262\351\271\217\345\274\200\345\217\221\350\200\205\345\244\247\350\265\233\350\265\233\351\242\230\346\226\207\346\241\243\350\257\264\346\230\216-0801.docx" new file mode 100644 index 0000000..49ea6a8 Binary files /dev/null and "b/19-q3q4/huawei kunpen race/\345\215\216\344\270\272\344\272\221\351\262\262\351\271\217\345\274\200\345\217\221\350\200\205\345\244\247\350\265\233\350\265\233\351\242\230\346\226\207\346\241\243\350\257\264\346\230\216-0801.docx" differ