Guest User

Python Tetris

a guest
Apr 3rd, 2020
17
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/python3
  2.  
  3. from tkinter import Canvas, Label, Tk, StringVar, Button, LEFT
  4. from random import choice, randint
  5.  
  6. class GameCanvas(Canvas):
  7. def clean_line(self, boxes_to_delete):
  8. for box in boxes_to_delete:
  9. self.delete(box)
  10. self.update()
  11.  
  12. def drop_boxes(self, boxes_to_drop):
  13. for box in boxes_to_drop:
  14. self.move(box, 0, Tetris.BOX_SIZE)
  15. self.update()
  16.  
  17. def completed_lines(self, y_coords):
  18. cleaned_lines = 0
  19. y_coords = sorted(y_coords)
  20. for y in y_coords:
  21. if sum(1 for box in self.find_withtag('game') if self.coords(box)[3] == y) == \
  22. ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE):
  23. self.clean_line([box
  24. for box in self.find_withtag('game')
  25. if self.coords(box)[3] == y])
  26.  
  27. self.drop_boxes([box
  28. for box in self.find_withtag('game')
  29. if self.coords(box)[3] < y])
  30. cleaned_lines += 1
  31. return cleaned_lines
  32.  
  33. def game_board(self):
  34. board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)\
  35. for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
  36. for box in self.find_withtag('game'):
  37. x, y, _, _ = self.coords(box)
  38. board[int(y // Tetris.BOX_SIZE)][int(x // Tetris.BOX_SIZE)] = 1
  39. return board
  40. def boxes(self):
  41. return self.find_withtag('game') == self.find_withtag(fill="blue")
  42.  
  43. class Shape():
  44. def __init__(self, coords = None):
  45. if not coords:
  46. self.__coords = choice(Tetris.SHAPES)
  47. else:
  48. self.__coords = coords
  49.  
  50. @property
  51. def coords(self):
  52. return self.__coords
  53.  
  54. def rotate(self):
  55. self.__coords = self.__rotate()
  56.  
  57. def rotate_directions(self):
  58. rotated = self.__rotate()
  59. directions = [(rotated[i][0] - self.__coords[i][0],
  60. rotated[i][1] - self.__coords[i][1]) for i in range(len(self.__coords))]
  61.  
  62. return directions
  63.  
  64. @property
  65. def matrix(self):
  66. return [[1 if (j, i) in self.__coords else 0 \
  67. for j in range(max(self.__coords, key=lambda x: x[0])[0] + 1)] \
  68. for i in range(max(self.__coords, key=lambda x: x[1])[1] + 1)]
  69.  
  70. def drop(self, board, offset):
  71. # print("\n\n\n")
  72. # print('\n'.join(''.join(map(str, b)) for b in board))
  73. # print("\n\n\n")
  74. off_x, off_y = offset
  75. # print(off_x,off_y)
  76. last_level = len(board) - len(self.matrix) + 1
  77. for level in range(off_y, last_level):
  78. for i in range(len(self.matrix)):
  79. for j in range(len(self.matrix[0])):
  80. if board[level+i][off_x+j] == 1 and self.matrix[i][j] == 1:
  81. return level - 1
  82. return last_level - 1
  83.  
  84. def __rotate(self):
  85. max_x = max(self.__coords, key=lambda x:x[0])[0]
  86. new_original = (max_x, 0)
  87.  
  88. rotated = [(new_original[0] - coord[1],
  89. new_original[1] + coord[0]) for coord in self.__coords]
  90.  
  91. min_x = min(rotated, key=lambda x:x[0])[0]
  92. min_y = min(rotated, key=lambda x:x[1])[1]
  93. return [(coord[0] - min_x, coord[1] - min_y) for coord in rotated]
  94.  
  95. class Piece():
  96. def __init__(self, canvas, start_point, shape = None):
  97. self.__shape = shape
  98. if not shape:
  99. self.__shape = Shape()
  100. self.canvas = canvas
  101. self.boxes = self.__create_boxes(start_point)
  102.  
  103. @property
  104. def shape(self):
  105. return self.__shape
  106.  
  107. def move(self, direction):
  108. if all(self.__can_move(self.canvas.coords(box), direction) for box in self.boxes):
  109. x, y = direction
  110. for box in self.boxes:
  111. self.canvas.move(box,
  112. x * Tetris.BOX_SIZE,
  113. y * Tetris.BOX_SIZE)
  114. return True
  115. return False
  116.  
  117. def rotate(self):
  118. directions = self.__shape.rotate_directions()
  119. if all(self.__can_move(self.canvas.coords(self.boxes[i]), directions[i]) for i in range(len(self.boxes))):
  120. self.__shape.rotate()
  121. for i in range(len(self.boxes)):
  122. x, y = directions[i]
  123. self.canvas.move(self.boxes[i],
  124. x * Tetris.BOX_SIZE,
  125. y * Tetris.BOX_SIZE)
  126.  
  127. @property
  128. def offset(self):
  129. return (min(int(self.canvas.coords(box)[0]) // Tetris.BOX_SIZE for box in self.boxes),
  130. min(int(self.canvas.coords(box)[1]) // Tetris.BOX_SIZE for box in self.boxes))
  131.  
  132. def predict_movement(self, board):
  133. level = self.__shape.drop(board, self.offset)
  134. min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
  135. return (0, level - (min_y // Tetris.BOX_SIZE))
  136.  
  137. def predict_drop(self, board):
  138. level = self.__shape.drop(board, self.offset)
  139. self.remove_predicts()
  140.  
  141. min_y = min([self.canvas.coords(box)[1] for box in self.boxes])
  142. for box in self.boxes:
  143. x1, y1, x2, y2 = self.canvas.coords(box)
  144. box = self.canvas.create_rectangle(x1,
  145. level * Tetris.BOX_SIZE + (y1 - min_y),
  146. x2,
  147. (level + 1) * Tetris.BOX_SIZE + (y1 - min_y),
  148. fill="yellow",
  149. tags = "predict")
  150.  
  151. def remove_predicts(self):
  152. for i in self.canvas.find_withtag('predict'):
  153. self.canvas.delete(i)
  154. self.canvas.update()
  155.  
  156. def __create_boxes(self, start_point):
  157. boxes = []
  158. off_x, off_y = start_point
  159. for coord in self.__shape.coords:
  160. x, y = coord
  161. box = self.canvas.create_rectangle(x * Tetris.BOX_SIZE + off_x,
  162. y * Tetris.BOX_SIZE + off_y,
  163. x * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_x,
  164. y * Tetris.BOX_SIZE + Tetris.BOX_SIZE + off_y,
  165. fill="blue",
  166. tags="game")
  167. boxes += [box]
  168.  
  169. return boxes
  170.  
  171. def __can_move(self, box_coords, new_pos):
  172. x, y = new_pos
  173. x = x * Tetris.BOX_SIZE
  174. y = y * Tetris.BOX_SIZE
  175. x_left, y_up, x_right, y_down = box_coords
  176.  
  177. overlap = set(self.canvas.find_overlapping((x_left + x_right) / 2 + x,
  178. (y_up + y_down) / 2 + y,
  179. (x_left + x_right) / 2 + x,
  180. (y_up + y_down) / 2 + y))
  181. other_items = set(self.canvas.find_withtag('game')) - set(self.boxes)
  182.  
  183. if y_down + y > Tetris.GAME_HEIGHT or \
  184. x_left + x < 0 or \
  185. x_right + x > Tetris.GAME_WIDTH or \
  186. overlap & other_items:
  187. # print("y_down + y > Tetris.GAME_HEIGHT : {}".format(y_down + y > Tetris.GAME_HEIGHT))
  188. # print("x_left + x < 0 : {}".format(x_left + x < 0))
  189. # print("x_right + x > Tetris.GAME_WIDTH : {}".format(x_right + x > Tetris.GAME_WIDTH))
  190. # print("overlap & other_items : {}".format(overlap & other_items))
  191. return False
  192. return True
  193.  
  194. class Tetris():
  195. SHAPES = ([(0, 0), (1, 0), (0, 1), (1, 1)], # Square
  196. [(0, 0), (1, 0), (2, 0), (3, 0)], # Line
  197. [(2, 0), (0, 1), (1, 1), (2, 1)], # Right L
  198. [(0, 0), (0, 1), (1, 1), (2, 1)], # Left L
  199. [(0, 1), (1, 1), (1, 0), (2, 0)], # Right Z
  200. [(0, 0), (1, 0), (1, 1), (2, 1)], # Left Z
  201. [(1, 0), (0, 1), (1, 1), (2, 1)]) # T
  202.  
  203. BOX_SIZE = 20
  204.  
  205. GAME_WIDTH = 300
  206. GAME_HEIGHT = 500
  207. GAME_START_POINT = GAME_WIDTH / 2 / BOX_SIZE * BOX_SIZE - BOX_SIZE
  208.  
  209. def __init__(self, predictable = False):
  210. self._level = 1
  211. self._score = 0
  212. self._blockcount = 0
  213. self.speed = 500
  214. self.predictable = predictable
  215.  
  216. self.root = Tk()
  217. self.root.geometry("500x550")
  218. self.root.title('Tetris')
  219. self.root.bind("<Key>", self.game_control)
  220. self.__game_canvas()
  221. self.__level_score_label()
  222. self.__next_piece_canvas()
  223.  
  224. def game_control(self, event):
  225. if event.char in ["a", "A", "\uf702"]:
  226. self.current_piece.move((-1, 0))
  227. self.update_predict()
  228. elif event.char in ["d", "D", "\uf703"]:
  229. self.current_piece.move((1, 0))
  230. self.update_predict()
  231. elif event.char in ["s", "S", "\uf701"]:
  232. self.hard_drop()
  233. elif event.char in ["w", "W", "\uf700"]:
  234. self.current_piece.rotate()
  235. self.update_predict()
  236.  
  237. def new_game(self):
  238. self.level = 1
  239. self.score = 0
  240. self.blockcount = 0
  241. self.speed = 500
  242.  
  243. self.canvas.delete("all")
  244. self.next_canvas.delete("all")
  245.  
  246. self.__draw_canvas_frame()
  247. self.__draw_next_canvas_frame()
  248.  
  249. self.current_piece = None
  250. self.next_piece = None
  251.  
  252. self.game_board = [[0] * ((Tetris.GAME_WIDTH - 20) // Tetris.BOX_SIZE)\
  253. for _ in range(Tetris.GAME_HEIGHT // Tetris.BOX_SIZE)]
  254.  
  255. self.update_piece()
  256.  
  257. def update_piece(self):
  258. if not self.next_piece:
  259. self.next_piece = Piece(self.next_canvas, (20,20))
  260.  
  261. self.current_piece = Piece(self.canvas, (Tetris.GAME_START_POINT, 0), self.next_piece.shape)
  262. self.next_canvas.delete("all")
  263. self.__draw_next_canvas_frame()
  264. self.next_piece = Piece(self.next_canvas, (20,20))
  265. self.update_predict()
  266.  
  267. def start(self):
  268. self.new_game()
  269. self.root.after(self.speed, None)
  270. self.drop()
  271. self.root.mainloop()
  272.  
  273. def drop(self):
  274. if not self.current_piece.move((0,1)):
  275. self.current_piece.remove_predicts()
  276. self.completed_lines()
  277. self.game_board = self.canvas.game_board()
  278. self.update_piece()
  279.  
  280. if self.is_game_over():
  281. return
  282. else:
  283. self._blockcount += 1
  284. self.score += 1
  285.  
  286. self.root.after(self.speed, self.drop)
  287.  
  288. def hard_drop(self):
  289. self.current_piece.move(self.current_piece.predict_movement(self.game_board))
  290.  
  291. def update_predict(self):
  292. if self.predictable:
  293. self.current_piece.predict_drop(self.game_board)
  294.  
  295. def update_status(self):
  296. self.status_var.set(f"Level: {self.level}, Score: {self.score}")
  297. self.status.update()
  298.  
  299. def is_game_over(self):
  300. if not self.current_piece.move((0,1)):
  301.  
  302. self.play_again_btn = Button(self.root, text="Play Again", command=self.play_again)
  303. self.quit_btn = Button(self.root, text="Quit", command=self.quit)
  304. self.play_again_btn.place(x = Tetris.GAME_WIDTH + 10, y = 200, width=100, height=25)
  305. self.quit_btn.place(x = Tetris.GAME_WIDTH + 10, y = 300, width=100, height=25)
  306. return True
  307. return False
  308.  
  309. def play_again(self):
  310. self.play_again_btn.destroy()
  311. self.quit_btn.destroy()
  312. self.start()
  313.  
  314. def quit(self):
  315. self.root.quit()
  316.  
  317. def completed_lines(self):
  318. y_coords = [self.canvas.coords(box)[3] for box in self.current_piece.boxes]
  319. completed_line = self.canvas.completed_lines(y_coords)
  320. if completed_line == 1:
  321. self.score += 400
  322. elif completed_line == 2:
  323. self.score += 1000
  324. elif completed_line == 3:
  325. self.score += 3000
  326. elif completed_line >= 4:
  327. self.score += 12000
  328.  
  329. def __game_canvas(self):
  330. self.canvas = GameCanvas(self.root,
  331. width = Tetris.GAME_WIDTH,
  332. height = Tetris.GAME_HEIGHT)
  333. self.canvas.pack(padx=5 , pady=10, side=LEFT)
  334.  
  335.  
  336.  
  337. def __level_score_label(self):
  338. self.status_var = StringVar()
  339. self.status = Label(self.root,
  340. textvariable=self.status_var,
  341. font=("Helvetica", 10, "bold"))
  342. #self.status.place(x = Tetris.GAME_WIDTH + 10, y = 100, width=100, height=25)
  343. self.status.pack()
  344.  
  345. def __next_piece_canvas(self):
  346. self.next_canvas = Canvas(self.root,
  347. width = 100,
  348. height = 100)
  349. self.next_canvas.pack(padx=5 , pady=10)
  350.  
  351. def __draw_canvas_frame(self):
  352. self.canvas.create_line(10, 0, 10, self.GAME_HEIGHT, fill = "red", tags = "line")
  353. self.canvas.create_line(self.GAME_WIDTH-10, 0, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
  354. self.canvas.create_line(10, self.GAME_HEIGHT, self.GAME_WIDTH-10, self.GAME_HEIGHT, fill = "red", tags = "line")
  355.  
  356. def __draw_next_canvas_frame(self):
  357. self.next_canvas.create_rectangle(10, 10, 90, 90, tags="frame")
  358.  
  359.  
  360. #set & get
  361. def __get_level(self):
  362. return self._level
  363.  
  364. def __set_level(self, level):
  365. self.speed = 500 - (level - 1) * 25
  366. self._level = level
  367. self.update_status()
  368.  
  369. def __get_score(self):
  370. return self._score
  371.  
  372. def __set_score(self, score):
  373. self._score = score
  374. self.update_status()
  375.  
  376. def __get_blockcount(self):
  377. return self._blockcount
  378.  
  379. def __set_blockcount(self, blockcount):
  380. self.level = blockcount // 5 + 1
  381. self._blockcount = blockcount
  382.  
  383. level = property(__get_level, __set_level)
  384. score = property(__get_score, __set_score)
  385. blockcount = property(__get_blockcount, __set_blockcount)
  386.  
  387. if __name__ == '__main__':
  388. game = Tetris(predictable = True)
  389. game.start()
RAW Paste Data