competitive_tetris.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #!/usr/bin/python3
  2. """
  3. Competitive tetris server
  4. """
  5. import random
  6. import math
  7. import pygame
  8. import zmq
  9. import flatbuffers
  10. import tetrominos
  11. import game_state
  12. import wire_protocol.Command
  13. import wire_protocol.CommandRequest
  14. import wire_protocol.CommandResponse
  15. import wire_protocol.Playfield
  16. import wire_protocol.CommandResponseError
  17. import wire_protocol.GameState
  18. pygame.font.init()
  19. # GLOBALS VARS
  20. S_WIDTH = 800
  21. S_HEIGHT = 700
  22. PLAY_WIDTH = 300 # meaning 300 // 10 = 30 width per block
  23. PLAY_HEIGHT = 600 # meaning 600 // 20 = 30 height per block
  24. BLOCK_SIZE = 30
  25. TOP_LEFT_X = (S_WIDTH - PLAY_WIDTH) // 2
  26. TOP_LEFT_Y = S_HEIGHT - PLAY_HEIGHT
  27. def draw_text_middle(surface, text, size, color):
  28. font = pygame.font.SysFont(
  29. pygame.font.get_default_font(), size, bold=True)
  30. label = font.render(text, 1, color)
  31. surface.blit(label, (TOP_LEFT_X + PLAY_WIDTH /2 - (label.get_width()/2), TOP_LEFT_Y + PLAY_HEIGHT/2 - label.get_height()/2))
  32. def draw_grid(surface, grid):
  33. sx = TOP_LEFT_X
  34. sy = TOP_LEFT_Y
  35. for i in range(len(grid)):
  36. pygame.draw.line(surface, (128,128,128), (sx, sy + i*BLOCK_SIZE), (sx+PLAY_WIDTH, sy+ i*BLOCK_SIZE))
  37. for j in range(len(grid[i])):
  38. pygame.draw.line(surface, (128, 128, 128), (sx + j*BLOCK_SIZE, sy),(sx + j*BLOCK_SIZE, sy + PLAY_HEIGHT))
  39. def draw_next_shape(shape, surface):
  40. font = pygame.font.SysFont(pygame.font.get_default_font(), 30)
  41. label = font.render("Next Shape", 1, (255,255,255))
  42. sx = TOP_LEFT_X + PLAY_WIDTH + 0
  43. sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100
  44. shape_raw = tetrominos.SHAPES_RAW[shape][0]
  45. for i, line in enumerate(shape_raw):
  46. row = list(line)
  47. for j, column in enumerate(row):
  48. if column == "0":
  49. pygame.draw.rect(
  50. surface, (255,0,0),
  51. (sx + j*BLOCK_SIZE, sy + i*BLOCK_SIZE,
  52. BLOCK_SIZE, BLOCK_SIZE),
  53. 0)
  54. surface.blit(label, (sx + 10, sy - 30))
  55. def draw_game_state_to_window(surface, state):
  56. surface.fill((0, 0, 0))
  57. pygame.font.init()
  58. font = pygame.font.SysFont(pygame.font.get_default_font(), 60)
  59. label = font.render("Tetris", 1, (255, 255, 255))
  60. surface.blit(label,
  61. (TOP_LEFT_X + PLAY_WIDTH / 2 - (label.get_width() / 2), 30))
  62. # current score
  63. font = pygame.font.SysFont(pygame.font.get_default_font(), 30)
  64. label = font.render(f"Score: {state.data.score}", 1, (255,255,255))
  65. sx = TOP_LEFT_X + PLAY_WIDTH + 50
  66. sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100
  67. surface.blit(label, (sx + 20, sy + 160))
  68. field = state.get_field()
  69. for row in range(field.shape[0]):
  70. for col in range(field.shape[1]):
  71. color = (0,0,0)
  72. tile = field[row,col]
  73. if tile == 1:
  74. color = (0,255,0)
  75. elif tile == 2:
  76. color = (255,0,0)
  77. pygame.draw.rect(surface, color,
  78. (TOP_LEFT_X + col*BLOCK_SIZE,
  79. TOP_LEFT_Y + row*BLOCK_SIZE,
  80. BLOCK_SIZE, BLOCK_SIZE), 0)
  81. pygame.draw.rect(surface, (255, 0, 0),
  82. (TOP_LEFT_X, TOP_LEFT_Y, PLAY_WIDTH, PLAY_HEIGHT), 5)
  83. draw_grid(surface, field)
  84. def handle_shift_left(state):
  85. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  86. next_state = state.shift_left()
  87. if next_state is None:
  88. error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
  89. else:
  90. state = next_state
  91. builder = flatbuffers.Builder()
  92. wire_protocol.CommandResponse.Start(builder)
  93. wire_protocol.CommandResponse.AddError(builder, error_code)
  94. rsp = wire_protocol.CommandResponse.End(builder)
  95. builder.Finish(rsp)
  96. return state, builder
  97. def handle_shift_right(state):
  98. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  99. next_state = state.shift_right()
  100. if next_state is None:
  101. error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
  102. else:
  103. state = next_state
  104. builder = flatbuffers.Builder()
  105. wire_protocol.CommandResponse.Start(builder)
  106. wire_protocol.CommandResponse.AddError(builder, error_code)
  107. rsp = wire_protocol.CommandResponse.End(builder)
  108. builder.Finish(rsp)
  109. return state, builder
  110. def handle_rotate(state):
  111. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  112. next_state = state.rotate()
  113. if next_state is None:
  114. error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
  115. else:
  116. state = next_state
  117. builder = flatbuffers.Builder()
  118. wire_protocol.CommandResponse.Start(builder)
  119. wire_protocol.CommandResponse.AddError(builder, error_code)
  120. rsp = wire_protocol.CommandResponse.End(builder)
  121. builder.Finish(rsp)
  122. return state, builder
  123. def handle_down(state):
  124. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  125. next_state = state.down()
  126. if next_state is None:
  127. error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
  128. else:
  129. state = next_state
  130. builder = flatbuffers.Builder()
  131. wire_protocol.CommandResponse.Start(builder)
  132. wire_protocol.CommandResponse.AddError(builder, error_code)
  133. rsp = wire_protocol.CommandResponse.End(builder)
  134. builder.Finish(rsp)
  135. return state, builder
  136. def handle_land(state):
  137. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  138. next_state = state.land()
  139. if next_state is None:
  140. error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
  141. else:
  142. state = next_state
  143. builder = flatbuffers.Builder()
  144. wire_protocol.CommandResponse.Start(builder)
  145. wire_protocol.CommandResponse.AddError(builder, error_code)
  146. rsp = wire_protocol.CommandResponse.End(builder)
  147. builder.Finish(rsp)
  148. return state, builder
  149. def handle_other(state):
  150. builder = flatbuffers.Builder()
  151. wire_protocol.CommandResponse.Start(builder)
  152. wire_protocol.CommandResponse.AddError(
  153. builder,
  154. wire_protocol.CommandResponseError.CommandResponseError.UNKNOWN_COMMAND)
  155. rsp = wire_protocol.CommandResponse.End(builder)
  156. builder.Finish(rsp)
  157. return state, builder
  158. def handle_get_game_state(state, millis_before_down):
  159. error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
  160. builder = flatbuffers.Builder()
  161. wire_protocol.GameState.StartGridVector(builder, state.data.grid.size)
  162. builder.head -= state.data.grid.size
  163. builder.Bytes[builder.head:(builder.head + state.data.grid.size)] = state.data.grid.tobytes()
  164. grid_vector = builder.EndVector(state.data.grid.size)
  165. wire_protocol.GameState.Start(builder)
  166. wire_protocol.GameState.AddRows(builder, state.data.grid.shape[0])
  167. wire_protocol.GameState.AddCols(builder, state.data.grid.shape[1])
  168. wire_protocol.GameState.AddNextPiece(builder, state.data.next_piece)
  169. wire_protocol.GameState.AddCurrentPiece(builder, state.data.current_piece)
  170. wire_protocol.GameState.AddPieceRotation(builder, state.data.rotation)
  171. wire_protocol.GameState.AddPieceRow(builder, state.data.row)
  172. wire_protocol.GameState.AddPieceCol(builder, state.data.col)
  173. wire_protocol.GameState.AddScore(builder, state.data.score)
  174. wire_protocol.GameState.AddMillisBeforeDown(builder, millis_before_down)
  175. wire_protocol.GameState.AddGrid(builder, grid_vector)
  176. game_state_msg = wire_protocol.GameState.End(builder)
  177. wire_protocol.CommandResponse.Start(builder)
  178. wire_protocol.CommandResponse.AddError(builder, error_code)
  179. wire_protocol.CommandResponse.AddGameState(builder, game_state_msg)
  180. rsp = wire_protocol.CommandResponse.End(builder)
  181. builder.Finish(rsp)
  182. return state, builder
  183. def game_loop(win, poller):
  184. is_game_running = True
  185. frame_time = 0
  186. clock = pygame.time.Clock()
  187. def _next_piece_func():
  188. return random.choice(range(len(tetrominos.SHAPES)))
  189. state = game_state.create_initial_game_state(_next_piece_func)
  190. while is_game_running:
  191. clock.tick()
  192. dt_millis = clock.get_time()
  193. frame_time += dt_millis
  194. for event in pygame.event.get():
  195. if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
  196. return None, False
  197. if event.type == pygame.QUIT:
  198. return None, False
  199. for sock, _ in poller.poll(timeout=0):
  200. req_buf = sock.recv()
  201. req = wire_protocol.CommandRequest.CommandRequest.GetRootAs(req_buf)
  202. builder = None
  203. if req.Command() == wire_protocol.Command.Command.ShiftLeft:
  204. state, builder = handle_shift_left(state)
  205. elif req.Command() == wire_protocol.Command.Command.ShiftRight:
  206. state, builder = handle_shift_right(state)
  207. elif req.Command() == wire_protocol.Command.Command.Rotate:
  208. state, builder = handle_rotate(state)
  209. elif req.Command() == wire_protocol.Command.Command.Down:
  210. state, builder = handle_down(state)
  211. elif req.Command() == wire_protocol.Command.Command.Land:
  212. state, builder = handle_land(state)
  213. elif req.Command() == wire_protocol.Command.Command.GetGameState:
  214. state, builder = handle_get_game_state(
  215. state, int(math.floor(max(0, 500 - frame_time))))
  216. else:
  217. state, builder = handle_other(state)
  218. sock.send(builder.Output())
  219. if frame_time >= 500:
  220. state, is_game_running = state.advance()
  221. frame_time = 0
  222. draw_game_state_to_window(win, state)
  223. draw_next_shape(state.data.next_piece, win)
  224. pygame.display.update()
  225. return state, True
  226. def server_loop(win):
  227. zmq_context = zmq.Context()
  228. socket = zmq_context.socket(zmq.REP)
  229. socket.bind("tcp://*:5555")
  230. poller = zmq.Poller()
  231. poller.register(socket, zmq.POLLIN)
  232. state = None
  233. run = True
  234. is_game_over_sent = True
  235. while run:
  236. win.fill((0,0,0))
  237. if state is None:
  238. draw_text_middle(
  239. win, "Waiting for connection.", 60, (255,255,255))
  240. else:
  241. draw_text_middle(
  242. win,
  243. f"Score = {state.data.score}. "
  244. "Reconnect?", 60, (255,255,255))
  245. pygame.display.update()
  246. for event in pygame.event.get():
  247. if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
  248. run = False
  249. elif event.type == pygame.QUIT:
  250. run = False
  251. run_game_loop = False
  252. for sock, _ in poller.poll(timeout=0):
  253. req_buf = sock.recv()
  254. req = wire_protocol.CommandRequest.CommandRequest.GetRootAs(req_buf)
  255. builder = None
  256. if req.Command() == wire_protocol.Command.Command.StartGame:
  257. builder = flatbuffers.Builder()
  258. wire_protocol.CommandResponse.Start(builder)
  259. wire_protocol.CommandResponse.AddError(
  260. builder, wire_protocol.CommandResponseError.CommandResponseError.OK)
  261. rsp = wire_protocol.CommandResponse.End(builder)
  262. builder.Finish(rsp)
  263. sock.send(builder.Output())
  264. run_game_loop = True
  265. elif not is_game_over_sent:
  266. is_game_over_sent = True
  267. builder = flatbuffers.Builder()
  268. wire_protocol.CommandResponse.Start(builder)
  269. wire_protocol.CommandResponse.AddError(
  270. builder,
  271. wire_protocol.CommandResponseError.CommandResponseError.GAME_OVER)
  272. rsp = wire_protocol.CommandResponse.End(builder)
  273. builder.Finish(rsp)
  274. sock.send(builder.Output())
  275. else:
  276. builder = flatbuffers.Builder()
  277. wire_protocol.CommandResponse.Start(builder)
  278. wire_protocol.CommandResponse.AddError(
  279. builder,
  280. wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND)
  281. rsp = wire_protocol.CommandResponse.End(builder)
  282. builder.Finish(rsp)
  283. sock.send(builder.Output())
  284. if run_game_loop:
  285. state, run = game_loop(win, poller)
  286. pygame.display.quit()
  287. if __name__ == "__main__":
  288. win = pygame.display.set_mode((S_WIDTH, S_HEIGHT))
  289. pygame.display.set_caption("Tetris")
  290. server_loop(win)