/paperio/local_runner/clients.py

https://github.com/MailRuChamps/miniaicups · Python · 250 lines · 196 code · 54 blank · 0 comment · 31 complexity · 100f23aeb8b1879eab63a0a3c7132d2f MD5 · raw file

  1. import os
  2. import asyncio
  3. import datetime
  4. import gzip
  5. import json
  6. import random
  7. from subprocess import Popen, PIPE
  8. import pyglet
  9. from constants import CONSTS
  10. class Client(object):
  11. def get_command(self):
  12. pass
  13. def close(self):
  14. pass
  15. def send_message(self, t, d):
  16. pass
  17. def save_log_to_disk(self, log, path):
  18. pass
  19. def get_solution_id(self):
  20. return random.randint(11000, 12000)
  21. class KeyboardClient(Client):
  22. @property
  23. def KEY_COMMAND_MAP(self):
  24. return {
  25. pyglet.window.key.MOTION_LEFT: CONSTS.LEFT,
  26. pyglet.window.key.MOTION_RIGHT: CONSTS.RIGHT,
  27. pyglet.window.key.MOTION_DOWN: CONSTS.DOWN,
  28. pyglet.window.key.MOTION_UP: CONSTS.UP,
  29. }
  30. def __init__(self, window):
  31. self.last_pressed_button = pyglet.window.key.MOTION_LEFT
  32. @window.event
  33. def on_key_press(symbol, _):
  34. self.last_pressed_button = symbol
  35. async def get_command(self):
  36. return {'command': self.KEY_COMMAND_MAP.get(self.last_pressed_button, None)}
  37. def save_log_to_disk(self, log, path):
  38. pass
  39. class KeyboardClient2(KeyboardClient):
  40. @property
  41. def KEY_COMMAND_MAP(self):
  42. return {
  43. pyglet.window.key.A: CONSTS.LEFT,
  44. pyglet.window.key.D: CONSTS.RIGHT,
  45. pyglet.window.key.S: CONSTS.DOWN,
  46. pyglet.window.key.W: CONSTS.UP,
  47. }
  48. def __init__(self, window):
  49. self.last_pressed_button = pyglet.window.key.A
  50. @window.event
  51. def on_key_release(symbol, _):
  52. self.last_pressed_button = symbol
  53. class SimplePythonClient(Client):
  54. def __init__(self):
  55. self.command = None
  56. self.tick = 0
  57. self.next_change = None
  58. self.next_dir = 0
  59. self.width = None
  60. self.x_cells_count = None
  61. self.y_cells_count = None
  62. self.lines = []
  63. self.position = None
  64. def change_command(self):
  65. commands = [CONSTS.LEFT, CONSTS.DOWN, CONSTS.RIGHT, CONSTS.UP]
  66. command = commands[self.next_dir % 4]
  67. self.next_dir += 1
  68. self.command = command
  69. def get_next_point(self):
  70. x, y = self.position
  71. if self.command == CONSTS.UP:
  72. return x, y + self.width
  73. if self.command == CONSTS.DOWN:
  74. return x, y - self.width
  75. if self.command == CONSTS.LEFT:
  76. return x - self.width, y
  77. if self.command == CONSTS.RIGHT:
  78. return x + self.width, y
  79. def is_border(self, point):
  80. x, y = point
  81. return x < round(self.width / 2) or \
  82. x > self.x_cells_count * self.width + round(self.width / 2) or \
  83. y < round(self.width / 2) or \
  84. y > self.y_cells_count * self.width + round(self.width / 2)
  85. def is_empty_next_point(self):
  86. if not self.position:
  87. return True
  88. next_point = self.get_next_point()
  89. return next_point not in self.lines and not self.is_border(next_point)
  90. async def get_command(self):
  91. if not self.next_change or self.next_change == 0 or not self.is_empty_next_point():
  92. self.next_change = random.randint(1, 4)
  93. self.change_command()
  94. attempts = 0
  95. while not self.is_empty_next_point() and attempts < 3:
  96. self.change_command()
  97. attempts += 1
  98. self.tick += 1
  99. self.next_change -= 1
  100. return {'command': self.command}
  101. def save_log_to_disk(self, log, path):
  102. pass
  103. def send_message(self, t, d):
  104. if t == 'start_game':
  105. self.width = d['width']
  106. self.x_cells_count = d['x_cells_count']
  107. self.y_cells_count = d['y_cells_count']
  108. if t == 'tick':
  109. p_data = d['players']['i']
  110. self.lines = p_data['lines']
  111. self.position = p_data['position']
  112. if t == 'end_game':
  113. pass
  114. class TcpClient(Client):
  115. EXECUTION_LIMIT = datetime.timedelta(seconds=CONSTS.MAX_EXECUTION_TIME)
  116. def __init__(self, reader, writer):
  117. self.reader = reader
  118. self.writer = writer
  119. self.execution_time = datetime.timedelta()
  120. self.solution_id = None
  121. def save_log_to_disk(self, log, path):
  122. location = path.format(str(self.solution_id) + '.gz')
  123. with gzip.open(location, 'wb') as f:
  124. f.write(json.dumps(log).encode())
  125. return {
  126. 'filename': os.path.basename(location),
  127. 'is_private': True,
  128. 'location': location
  129. }
  130. async def set_solution_id(self):
  131. hello_json = await asyncio.wait_for(self.reader.readline(), timeout=CONSTS.REQUEST_MAX_TIME)
  132. try:
  133. self.solution_id = json.loads(hello_json.decode('utf-8')).get('solution_id')
  134. except ValueError:
  135. pass
  136. return bool(self.solution_id)
  137. def send_message(self, t, d):
  138. msg = {
  139. 'type': t,
  140. 'params': d
  141. }
  142. msg_bytes = '{}\n'.format(json.dumps(msg)).encode()
  143. self.writer.write(msg_bytes)
  144. async def get_command(self):
  145. try:
  146. before = datetime.datetime.now()
  147. z = await asyncio.wait_for(self.reader.readline(), timeout=CONSTS.REQUEST_MAX_TIME)
  148. if not z:
  149. raise ConnectionError('Connection closed')
  150. self.execution_time += (datetime.datetime.now() - before)
  151. if self.execution_time > self.EXECUTION_LIMIT:
  152. raise Exception('sum timeout error')
  153. except asyncio.TimeoutError:
  154. raise asyncio.TimeoutError('read timeout error')
  155. try:
  156. z = json.loads(z.decode())
  157. except ValueError:
  158. z = {'debug': 'cant pars json'}
  159. return z
  160. def close(self):
  161. self.writer.close()
  162. def get_solution_id(self):
  163. return self.solution_id
  164. class FileClient(Client):
  165. def __init__(self, path_to_script, path_to_log=None):
  166. self.process = Popen(path_to_script, stdout=PIPE, stdin=PIPE)
  167. self.last_message = None
  168. if path_to_log is None:
  169. base_dir = os.getcwd()
  170. now = datetime.datetime.now().strftime('%Y_%m_%d-%H-%M-%S.log.gz')
  171. self.path_to_log = os.path.join(base_dir, now)
  172. else:
  173. self.path_to_log = path_to_log
  174. def send_message(self, t, d):
  175. msg = {
  176. 'type': t,
  177. 'params': d
  178. }
  179. msg_bytes = '{}\n'.format(json.dumps(msg)).encode()
  180. self.process.stdin.write(msg_bytes)
  181. self.process.stdin.flush()
  182. async def get_command(self):
  183. try:
  184. line = self.process.stdout.readline().decode('utf-8')
  185. state = json.loads(line)
  186. return state
  187. except Exception as e:
  188. return {'debug': str(e)}
  189. def save_log_to_disk(self, log, _):
  190. with gzip.open(self.path_to_log, 'w') as f:
  191. f.write(json.dumps(log).encode())
  192. return {
  193. 'filename': os.path.basename(self.path_to_log),
  194. 'is_private': True,
  195. 'location': self.path_to_log
  196. }