From 1d056023930307219b906497c2dfd9bc37d6f1c4 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Mon, 22 Jul 2019 23:14:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BF=D0=BE=D0=BD=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/README.md | 33 ++++------ paperio/dockers/scala/Dockerfile | 2 +- paperio/examples/java9_strategy.java | 2 +- paperio/examples/java_strategy.java | 2 +- paperio/local_runner/game_objects/bonuses.py | 2 +- paperio/local_runner/game_objects/game.py | 64 +++++++++++++------ paperio/local_runner/game_objects/player.py | 2 +- paperio/local_runner/game_objects/scene.py | 38 +++++++---- .../local_runner/game_objects/territory.py | 61 ++++++++++-------- paperio/local_runner/helpers.py | 9 +-- paperio/local_runner/localrunner.py | 13 +++- 11 files changed, 135 insertions(+), 93 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index d77d73f..ccb28fd 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -10,23 +10,16 @@ Решения можно присылать на любом языке программирования из списка поддерживаемых: -* C# .zip,.cs -* C++11 .zip,.h,.cpp -* C++14 .zip,.h,.hpp,.cpp -* C++17 .zip,.h,.hpp,.cpp -* Elixir .zip,.ex -* Go .zip,.go -* Haskell .zip,.hs -* Java1.8 .zip,.java -* Java1.9 .zip, .java -* Kotlin .zip,.kt -* Node JS .zip,.js -* PHP7 .zip,.php -* Python 2.7 .zip,.py -* Python 3.6 .zip,.py -* Rust .zip,.rs -* Scala .zip,.scala -* Swift .zip, .swift +* C++11 / .zip, .h, .cpp +* C++17 / .zip, .h, .hpp, .cpp +* C# / .zip, .cs +* Java1.9 / .zip, .java +* Go / .zip, .go +* Python 2.7 / .zip, .py +* Python 3.6 / .zip, .py +* PHP7 / .zip, .php +* Node JS / .zip, .js +* Swift / .zip, .swift Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. @@ -62,9 +55,9 @@ 1. **Игрок** - двигающийся квадрат, который управляется **ботом** участника. Положение квадрата на карте определяется координатами его центра (x, y). Скорость квадрата задается параметром SPEED и может быть на время изменена, путем взятия бонуса. -2. **Территория** - захваченная ботом область карты, на которой бот находится в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. +2. **Территория** - захваченная ботом область карты, на которой бот находиться в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. -3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становится нейтральной. +3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. 4. **Бонус** - в игре имеется 3 вида бонусов - **Ускорение**, **Замедление** и **Пила**: * **Ускорение** - увеличивает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; @@ -102,7 +95,6 @@ * При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; * При пересечении границ карты; * При захвате противниками всей вашей территории; -* При попадании лучом в игрока Побеждает игрок, набравший наибольшее количество очков. @@ -157,6 +149,7 @@ while True: * `territory` — массив координат клеток, принадлежащих территории игрока * `position` — текущее положение игрока * `lines` — массив координат клеток шлейфа + * `direction` — направление движения игрока ("left", "right", "up", "down") * `bonuses` — массив активных бонусов игрока * `type` — тип бонуса ('n' - Ускорение (Нитро), 's' - Замедление, 'saw' - Пила) * `ticks` — сколько еще клеток будет активен бонус diff --git a/paperio/dockers/scala/Dockerfile b/paperio/dockers/scala/Dockerfile index f425d83..ad5ca7c 100644 --- a/paperio/dockers/scala/Dockerfile +++ b/paperio/dockers/scala/Dockerfile @@ -1,4 +1,4 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base MAINTAINER Konstantin Aristov WORKDIR /opt/client diff --git a/paperio/examples/java9_strategy.java b/paperio/examples/java9_strategy.java index a0d3f5d..a664d6d 100644 --- a/paperio/examples/java9_strategy.java +++ b/paperio/examples/java9_strategy.java @@ -14,7 +14,7 @@ public static void main(String args[]) { String[] commands = {"left", "right", "up", "down"}; Scanner scanner = new Scanner(System.in); while (true) { - String input = scanner.next(); + String input = scanner.nextLine(); String command = Main.getRandom(commands); System.out.printf("{\"command\": \"%s\"}\n", command); } diff --git a/paperio/examples/java_strategy.java b/paperio/examples/java_strategy.java index 39aa2ac..39ac646 100644 --- a/paperio/examples/java_strategy.java +++ b/paperio/examples/java_strategy.java @@ -11,7 +11,7 @@ public static void main(String args[]) { String[] commands = {"left", "right", "up", "down"}; Scanner scanner = new Scanner(System.in); while (true) { - String input = scanner.next(); + String input = scanner.nextLine(); String command = Main.getRandom(commands); System.out.printf("{\"command\": \"%s\"}\n", command); } diff --git a/paperio/local_runner/game_objects/bonuses.py b/paperio/local_runner/game_objects/bonuses.py index 3732ee6..78a08d3 100644 --- a/paperio/local_runner/game_objects/bonuses.py +++ b/paperio/local_runner/game_objects/bonuses.py @@ -36,7 +36,7 @@ def generate_coordinates(players, busy_points): return x, y def draw(self): - draw_square_with_image((self.x, self.y), self.color, self.image_path, self.active_ticks) + draw_square_with_image((self.x, self.y), self.color, self.image_path) def is_ate(self, player, captured): return (self.x, self.y) == (player.x, player.y) or (self.x, self.y) in captured diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index c54b234..39804c1 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -1,5 +1,7 @@ import os +import asyncio import json +import copy import gzip import random @@ -161,7 +163,6 @@ async def game_loop_wrapper(self, *args, **kwargs): self.send_game_start() while True: is_game_over = await self.game_loop(*args, **kwargs) - print('tick: {}'.format(self.tick)) if is_game_over or self.tick >= MAX_TICK_COUNT: self.send_game_end() self.game_save() @@ -178,14 +179,28 @@ def get_players_states(self, player=None): def get_bonuses_states(self): return [b.get_state() for b in self.bonuses] + def collision_resolution(self, players_to_captured): + res = {p: copy.copy(c) for p, c in players_to_captured.items()} + for p1, captured1 in players_to_captured.items(): + for p2, captured2 in players_to_captured.items(): + if p1 != p2: + res[p1].difference_update(captured2) + return res + + async def get_command_wrapper(self, player): + command = await player.get_command(self.tick) + if command: + player.change_direction(command) + async def game_loop(self, *args, **kwargs): self.send_game_tick() + futures = [] for player in self.players: if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: - command = await player.get_command(self.tick) - if command: - player.change_direction(command) + futures.append(asyncio.ensure_future(self.get_command_wrapper(player))) + if futures: + await asyncio.wait(futures) for player in self.players: player.move() @@ -195,6 +210,7 @@ async def game_loop(self, *args, **kwargs): if is_loss: self.losers.append(self.players[index]) + players_to_captured = {} for player in self.players: player.remove_saw_bonus() @@ -202,10 +218,17 @@ async def game_loop(self, *args, **kwargs): player.update_lines() captured = player.territory.capture(player.lines) + players_to_captured[player] = captured if len(captured) > 0: player.lines.clear() player.score += NEUTRAL_TERRITORY_SCORE * len(captured) + + players_to_captured = self.collision_resolution(players_to_captured) + for player in self.players: + if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + captured = players_to_captured.get(player, set()) + player.tick_action() for bonus in self.bonuses[:]: @@ -237,10 +260,12 @@ async def game_loop(self, *args, **kwargs): 'points': removed, 'killed': False }) - for p in self.players: - if p != player: - removed = p.territory.remove_points(captured) - player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) + if captured: + player.territory.points.update(captured) + for p in self.players: + if p != player: + removed = p.territory.remove_points(captured) + player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) for player in self.losers: if player in self.players: @@ -252,7 +277,7 @@ async def game_loop(self, *args, **kwargs): return len(self.players) == 0 def save_scores(self): - d = {p.client.get_solution_id(): p.score for p in self.losers} + d = {p.client.get_solution_id(): p.score for p in self.losers + self.players} with open(self.SCORES_LOCATION, 'w') as f: f.write(json.dumps(d)) @@ -281,7 +306,7 @@ def save_visio(self): def save_debug(self): return [ - p.save_log(self.DEBUG_LOCATION) for p in self.losers + p.save_log(self.DEBUG_LOCATION) for p in self.losers + self.players ] def game_save(self): @@ -303,19 +328,19 @@ def __init__(self, clients, scene, timeout): self.scene = scene self.timeout = timeout - def show_bonuses(self): + def append_bonuses_to_leaderboard(self): for player in self.players: if len(player.bonuses) > 0: for bonus in player.bonuses: label = '{} - {} - {}'.format(player.name, bonus.name, bonus.get_remaining_ticks()) self.scene.append_label_to_leaderboard(label, player.color) - def show_losers(self): + def append_losers_to_leaderboard(self): for player in self.losers: label = '{} выбыл, результат: {}'.format(player.name, player.score) self.scene.append_label_to_leaderboard(label, player.color) - def show_score(self): + def append_scores_to_leaderboard(self): for player in self.players: label = '{} результат: {}'.format(player.name, player.score) self.scene.append_label_to_leaderboard(label, player.color) @@ -324,6 +349,12 @@ def draw_bonuses(self): for bonus in self.bonuses: bonus.draw() + def draw_leaderboard(self): + self.append_losers_to_leaderboard() + self.append_scores_to_leaderboard() + self.append_bonuses_to_leaderboard() + self.scene.draw_leaderboard() + def draw(self): for player in self.players: player.territory.draw() @@ -343,12 +374,7 @@ def draw(self): self.scene.show_game_over(timeout=True) self.draw_bonuses() - - self.scene.draw_leaderboard() - self.show_losers() - self.show_score() - self.show_bonuses() - self.scene.reset_leaderboard() + self.draw_leaderboard() async def game_loop(self, *args, **kwargs): self.scene.clear() diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 18433c0..b484b4c 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -7,7 +7,7 @@ class Player: speed = SPEED - direction = 'initial' + direction = None def __init__(self, id, x, y, name, color, client): self.id = id diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py index 7858586..ddaddd3 100644 --- a/paperio/local_runner/game_objects/scene.py +++ b/paperio/local_runner/game_objects/scene.py @@ -14,9 +14,16 @@ class Scene: leaderboard_height = 240 leaderboard_rows_count = 0 + labels_buffer = [] + game_over_label = pyglet.text.Label('GAME OVER', font_name='Times New Roman', + font_size=30, + color=game_over_label_color, + x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, + anchor_x='center', anchor_y='center') def __init__(self): self.window = pyglet.window.Window(height=WINDOW_HEIGHT, width=WINDOW_WIDTH) + pyglet.options['debug_gl'] = False pyglet.gl.glClearColor(*self.background_color) pyglet.gl.glEnable(pyglet.gl.GL_BLEND) pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) @@ -25,25 +32,27 @@ def clear(self): self.window.clear() def append_label_to_leaderboard(self, label, color): - pyglet.text.Label(label, - font_name='Times New Roman', - font_size=16, - color=color, - x=WINDOW_WIDTH - self.leaderboard_width + 20, - y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, - anchor_x='left', anchor_y='center').draw() + if len(self.labels_buffer) > self.leaderboard_rows_count: + self.labels_buffer[self.leaderboard_rows_count].text = label + self.labels_buffer[self.leaderboard_rows_count].color = color + else: + self.labels_buffer.append( + pyglet.text.Label(label, + font_name='Times New Roman', + font_size=16, + color=color, + x=WINDOW_WIDTH - self.leaderboard_width + 20, + y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, + anchor_x='left', anchor_y='center') + ) self.leaderboard_rows_count += 1 def reset_leaderboard(self): self.leaderboard_rows_count = 0 def show_game_over(self, timeout=False): - label = 'TIMEOUT' if timeout else 'GAME OVER' - pyglet.text.Label(label, font_name='Times New Roman', - font_size=30, - color=self.game_over_label_color, - x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, - anchor_x='center', anchor_y='center').draw() + self.game_over_label.text = 'TIMEOUT' if timeout else 'GAME OVER' + self.game_over_label.draw() def draw_border(self): draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color) @@ -57,3 +66,6 @@ def draw_leaderboard(self): WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT), self.leaderboard_color) + for label in self.labels_buffer[:self.leaderboard_rows_count]: + label.draw() + self.reset_leaderboard() diff --git a/paperio/local_runner/game_objects/territory.py b/paperio/local_runner/game_objects/territory.py index 58718e2..21cb1f8 100644 --- a/paperio/local_runner/game_objects/territory.py +++ b/paperio/local_runner/game_objects/territory.py @@ -40,35 +40,43 @@ def _capture(self, boundary): y = max_y while y > min_y: if (x, y) not in self.points and in_polygon(x, y, poligon_x_arr, poligon_y_arr): - self.points.add((x, y)) captured.append((x, y)) y -= WIDTH x -= WIDTH return captured + def is_siblings(self, p1, p2): + return p2 in get_vert_and_horiz(p1) + def get_voids_between_lines_and_territory(self, lines): boundary = self.get_boundary() voids = [] - for cur in lines: - for point in get_neighboring(cur): + for i_lp1, lp1 in enumerate(lines): + for point in get_neighboring(lp1): if point in boundary: - start_point = self.get_nearest_boundary(lines[0], boundary) - if start_point: - end_index = boundary.index(point) - start_index = boundary.index(start_point) - - try: - path = self.get_path(start_index, end_index, boundary) - except (nx.NetworkXNoPath, nx.NodeNotFound): - continue - - if len(path) > 1 and path[0] == path[-1]: - path = path[1:] - - path = [boundary[index] for index in path] - lines_path = lines[:lines.index(cur) + 1] - - voids.append(lines_path + path) + prev = None + for lp2 in lines[:i_lp1 + 1]: + start_point = self.get_nearest_boundary(lp2, boundary) + if start_point: + if prev and (self.is_siblings(prev, start_point) or prev == start_point): + prev = start_point + continue + end_index = boundary.index(point) + start_index = boundary.index(start_point) + + try: + path = self.get_path(start_index, end_index, boundary) + except (nx.NetworkXNoPath, nx.NodeNotFound): + continue + + if len(path) > 1 and path[0] == path[-1]: + path = path[1:] + + path = [boundary[index] for index in path] + lines_path = lines[lines.index(lp2):i_lp1 + 1] + + voids.append(lines_path + path) + prev = start_point return voids def capture_voids_between_lines(self, lines): @@ -77,26 +85,25 @@ def capture_voids_between_lines(self, lines): for point in get_neighboring(cur): if point in lines: end_index = lines.index(point) - path = lines[index:end_index] + path = lines[index:end_index + 1] if len(path) >= 8: captured.extend(self._capture(path)) return captured def capture(self, lines): - captured = [] + captured = set() if len(lines) > 1: if lines[-1] in self.points: voids = self.get_voids_between_lines_and_territory(lines) - captured.extend(self.capture_voids_between_lines(lines)) + captured.update(self.capture_voids_between_lines(lines)) for line in lines: if line not in self.points: - self.points.add(line) - captured.append(line) + captured.add(line) for void in voids: - captured.extend(self._capture(void)) + captured.update(self._capture(void)) if len(captured) > 0: self.changed = True return captured @@ -113,7 +120,7 @@ def remove_points(self, points): return removed def get_siblings(self, point, boundary): - return [sibling for sibling in get_vert_and_horiz(point) if sibling in boundary] + return [sibling for sibling in get_neighboring(point) if sibling in boundary] def get_path(self, start_index, end_index, boundary): graph = nx.Graph() diff --git a/paperio/local_runner/helpers.py b/paperio/local_runner/helpers.py index 8b2a2b9..ad3008b 100644 --- a/paperio/local_runner/helpers.py +++ b/paperio/local_runner/helpers.py @@ -177,7 +177,7 @@ def load_image(path): return IMAGE_CACHE[path] -def draw_square_with_image(point, color, image_path, label=None, width=WIDTH): +def draw_square_with_image(point, color, image_path, width=WIDTH): draw_square(point, color, width) x, y = point @@ -187,13 +187,6 @@ def draw_square_with_image(point, color, image_path, label=None, width=WIDTH): sprite.scale = 0.75 * (width / max(sprite.height, sprite.width)) sprite.draw() - if label is not None: - pyglet.text.Label('{}'.format(label), font_name='Times New Roman', - font_size=round(WIDTH / 7), - color=(95, 99, 104, 255), - x=x + round(WIDTH / 2), y=y + round(WIDTH / 2), - anchor_x='right', anchor_y='top').draw() - def get_random_coordinates(): x = random.randint(1, X_CELLS_COUNT) * WIDTH - round(WIDTH / 2) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index e285ea4..5c13a17 100644 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -1,10 +1,11 @@ from asyncio import events import argparse +import os import pyglet from pyglet.window import key -from helpers import TERRITORY_CACHE +from helpers import TERRITORY_CACHE, load_image from clients import KeyboardClient, SimplePythonClient, FileClient from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT from game_objects.scene import Scene @@ -65,8 +66,18 @@ def on_key_release(symbol, modifiers): def stop_game(): pyglet.clock.unschedule(Runner.game_loop_wrapper) + @staticmethod + def load_sprites(): + base_dir = os.path.dirname(os.path.realpath(__file__)) + absolute_path = os.path.join(base_dir, 'sprites') + sprites = os.listdir(absolute_path) + for sprite in sprites: + if sprite.endswith('png'): + load_image('sprites/{}'.format(sprite)) + @staticmethod def run_game(): + Runner.load_sprites() Runner.game = LocalGame(clients, scene, args.timeout == 'on') Runner.game.send_game_start() pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 200)