fork(1) download
  1. # tetris.py
  2. # Mechanical MOOC MIT OCW 6.189
  3. # Glenn Richard
  4. # July 24, 2013
  5. # Modified August 9, 2013 to add scoreboard, <p>ause, and un<p>ause
  6. from graphics import *
  7. import random
  8.  
  9. ############################################################
  10. # BLOCK CLASS
  11. ############################################################
  12.  
  13. class Block(Rectangle):
  14. ''' Block class:
  15. Implement a block for a tetris piece
  16. Attributes: x - type: int
  17. y - type: int
  18. specify the position on the tetris board
  19. in terms of the square grid
  20. '''
  21.  
  22. BLOCK_SIZE = 30
  23. OUTLINE_WIDTH = 3
  24.  
  25. def __init__(self, pos, color):
  26. self.x = pos.x
  27. self.y = pos.y
  28.  
  29. p1 = Point(pos.x*Block.BLOCK_SIZE + Block.OUTLINE_WIDTH,
  30. pos.y*Block.BLOCK_SIZE + Block.OUTLINE_WIDTH)
  31. p2 = Point(p1.x + Block.BLOCK_SIZE, p1.y + Block.BLOCK_SIZE)
  32.  
  33. Rectangle.__init__(self, p1, p2)
  34. self.setWidth(Block.OUTLINE_WIDTH)
  35. self.setFill(color)
  36.  
  37. def can_move(self, board, dx, dy):
  38. ''' Parameters: dx - type: int
  39. dy - type: int
  40.  
  41. Return value: type: bool
  42.  
  43. checks if the block can move dx squares in the x direction
  44. and dy squares in the y direction
  45. Returns True if it can, and False otherwise
  46. HINT: use the can_move method on the Board object
  47. '''
  48. #YOUR CODE HERE
  49. return board.can_move(self.x + dx, self.y + dy)
  50.  
  51. def move(self, dx, dy):
  52. ''' Parameters: dx - type: int
  53. dy - type: int
  54.  
  55. moves the block dx squares in the x direction
  56. and dy squares in the y direction
  57. '''
  58.  
  59. self.x += dx
  60. self.y += dy
  61.  
  62. Rectangle.move(self, dx*Block.BLOCK_SIZE, dy*Block.BLOCK_SIZE)
  63.  
  64.  
  65.  
  66. ############################################################
  67. # SHAPE CLASS
  68. ############################################################
  69.  
  70. class Shape():
  71. ''' Shape class:
  72. Base class for all the tetris shapes
  73. Attributes: blocks - type: list - the list of blocks making up the shape
  74. rotation_dir - type: int - the current rotation direction of the shape
  75. shift_rotation_dir - type: Boolean - whether or not the shape rotates
  76. '''
  77.  
  78. def __init__(self, coords, color):
  79. self.blocks = []
  80. self.rotation_dir = 1
  81. ### A boolean to indicate if a shape shifts rotation direction or not.
  82. ### Defaults to false since only 3 shapes shift rotation directions (I, S and Z)
  83. self.shift_rotation_dir = False
  84.  
  85. for pos in coords:
  86. self.blocks.append(Block(pos, color))
  87.  
  88.  
  89.  
  90. def get_blocks(self):
  91. '''returns the list of blocks
  92. '''
  93. #YOUR CODE HERE
  94. return self.blocks
  95.  
  96. def draw(self, win):
  97. ''' Parameter: win - type: CanvasFrame
  98.  
  99. Draws the shape:
  100. i.e. draws each block
  101. '''
  102. for block in self.blocks:
  103. block.draw(win)
  104.  
  105. def move(self, dx, dy):
  106. ''' Parameters: dx - type: int
  107. dy - type: int
  108.  
  109. moves the shape dx squares in the x direction
  110. and dy squares in the y direction, i.e.
  111. moves each of the blocks
  112. '''
  113. for block in self.blocks:
  114. block.move(dx, dy)
  115.  
  116. def can_move(self, board, dx, dy):
  117. ''' Parameters: dx - type: int
  118. dy - type: int
  119.  
  120. Return value: type: bool
  121.  
  122. checks if the shape can move dx squares in the x direction
  123. and dy squares in the y direction, i.e.
  124. check if each of the blocks can move
  125. Returns True if all of them can, and False otherwise
  126.  
  127. '''
  128.  
  129. #YOUR CODE HERE
  130. # default implementation (MUST CHANGE)
  131. for block in self.blocks:
  132. if not block.can_move(board, dx,dy):
  133. return False
  134. return True
  135.  
  136. def get_rotation_dir(self):
  137. ''' Return value: type: int
  138.  
  139. returns the current rotation direction
  140. '''
  141. return self.rotation_dir
  142.  
  143. def can_rotate(self, board):
  144. ''' Parameters: board - type: Board object
  145. Return value: type : bool
  146.  
  147. Checks if the shape can be rotated.
  148.  
  149. 1. Get the rotation direction using the get_rotation_dir method
  150. 2. Compute the position of each block after rotation and check if
  151. the new position is valid
  152. 3. If any of the blocks cannot be moved to their new position,
  153. return False
  154.  
  155. otherwise all is good, return True
  156. '''
  157. #YOUR CODE HERE
  158. for block in self.blocks:
  159. x = self.blocks[1].x - self.rotation_dir * self.blocks[1].y + self.rotation_dir * block.y
  160. y = self.blocks[1].y + self.rotation_dir * self.blocks[1].x - self.rotation_dir * block.x
  161. dx = x - block.x
  162. dy = y - block.y
  163. if not block.can_move(board, dx, dy):
  164. return False
  165. return True
  166.  
  167. def rotate(self, board):
  168. ''' Parameters: board - type: Board object
  169.  
  170. rotates the shape:
  171. 1. Get the rotation direction using the get_rotation_dir method
  172. 2. Compute the position of each block after rotation
  173. 3. Move the block to the new position
  174.  
  175. '''
  176.  
  177. #### YOUR CODE HERE #####
  178. for block in self.blocks:
  179. x = self.blocks[1].x - self.rotation_dir * self.blocks[1].y + self.rotation_dir * block.y
  180. y = self.blocks[1].y + self.rotation_dir * self.blocks[1].x - self.rotation_dir * block.x
  181. block.move(x - block.x, y - block.y)
  182.  
  183.  
  184. ### This should be at the END of your rotate code.
  185. ### DO NOT touch it. Default behavior is that a piece will only shift
  186. ### rotation direction after a successful rotation. This ensures that
  187. ### pieces which switch rotations definitely remain within their
  188. ### accepted rotation positions.
  189. if self.shift_rotation_dir:
  190. self.rotation_dir *= -1
  191.  
  192.  
  193.  
  194. ############################################################
  195. # ALL SHAPE CLASSES
  196. ############################################################
  197.  
  198.  
  199. class I_shape(Shape):
  200. def __init__(self, center):
  201. coords = [Point(center.x - 2, center.y),
  202. Point(center.x - 1, center.y),
  203. Point(center.x , center.y),
  204. Point(center.x + 1, center.y)]
  205. Shape.__init__(self, coords, 'blue')
  206. self.shift_rotation_dir = True
  207. self.center_block = self.blocks[2]
  208.  
  209. class J_shape(Shape):
  210. def __init__(self, center):
  211. coords = [Point(center.x - 1, center.y),
  212. Point(center.x , center.y),
  213. Point(center.x + 1, center.y),
  214. Point(center.x + 1, center.y + 1)]
  215. Shape.__init__(self, coords, 'orange')
  216. self.center_block = self.blocks[1]
  217.  
  218. class L_shape(Shape):
  219. def __init__(self, center):
  220. coords = [Point(center.x - 1, center.y),
  221. Point(center.x , center.y),
  222. Point(center.x + 1, center.y),
  223. Point(center.x - 1, center.y + 1)]
  224. Shape.__init__(self, coords, 'cyan')
  225. self.center_block = self.blocks[1]
  226.  
  227.  
  228. class O_shape(Shape):
  229. def __init__(self, center):
  230. coords = [Point(center.x , center.y),
  231. Point(center.x - 1, center.y),
  232. Point(center.x , center.y + 1),
  233. Point(center.x - 1, center.y + 1)]
  234. Shape.__init__(self, coords, 'red')
  235. self.center_block = self.blocks[0]
  236.  
  237. def rotate(self, board):
  238. # Override Shape's rotate method since O_Shape does not rotate
  239. return
  240.  
  241. class S_shape(Shape):
  242. def __init__(self, center):
  243. coords = [Point(center.x , center.y),
  244. Point(center.x , center.y + 1),
  245. Point(center.x + 1, center.y),
  246. Point(center.x - 1, center.y + 1)]
  247. Shape.__init__(self, coords, 'green')
  248. self.center_block = self.blocks[0]
  249. self.shift_rotation_dir = True
  250. self.rotation_dir = -1
  251.  
  252.  
  253. class T_shape(Shape):
  254. def __init__(self, center):
  255. coords = [Point(center.x - 1, center.y),
  256. Point(center.x , center.y),
  257. Point(center.x + 1, center.y),
  258. Point(center.x , center.y + 1)]
  259. Shape.__init__(self, coords, 'yellow')
  260. self.center_block = self.blocks[1]
  261.  
  262.  
  263. class Z_shape(Shape):
  264. def __init__(self, center):
  265. coords = [Point(center.x - 1, center.y),
  266. Point(center.x , center.y),
  267. Point(center.x , center.y + 1),
  268. Point(center.x + 1, center.y + 1)]
  269. Shape.__init__(self, coords, 'magenta')
  270. self.center_block = self.blocks[1]
  271. self.shift_rotation_dir = True
  272. self.rotation_dir = -1
  273.  
  274. ############################################################
  275. # ScoreBoard CLASS
  276. ############################################################
  277.  
  278. class ScoreBoard():
  279. def __init__(self, win, width, height, score):
  280. self.width = width
  281. self.height = height
  282. self.score = score
  283. self.canvas = CanvasFrame(win, self.width, self.height)
  284. self.canvas.setBackground('white')
  285. self.score_message = Text(Point(150, 25), "Score: " + str(self.score))
  286. self.score_message.setSize(20)
  287. self.score_message.setTextColor("black")
  288. self.score_message.draw(self.canvas)
  289. def set_score(self, score):
  290. self.score_message.setText("<p>ause / score: " + str(score))
  291. def set_final_score(self, score):
  292. self.score_message.setText("final score: " + str(score))
  293. def pause_score(self, score):
  294. self.score_message.setText("un<p>ause / score: " + str(score))
  295.  
  296.  
  297. ############################################################
  298. # Board CLASS
  299. ############################################################
  300.  
  301. class Board():
  302. ''' Board class: it represents the Tetris board
  303.  
  304. Attributes: width - type:int - width of the board in squares
  305. height - type:int - height of the board in squares
  306. canvas - type:CanvasFrame - where the pieces will be drawn
  307. grid - type:Dictionary - keeps track of the current state of
  308. the board; stores the blocks for a given position
  309. '''
  310.  
  311. def __init__(self, win, width, height):
  312. self.width = width
  313. self.height = height
  314.  
  315. # create a canvas to draw the tetris shapes on
  316. self.canvas = CanvasFrame(win, self.width * Block.BLOCK_SIZE,
  317. self.height * Block.BLOCK_SIZE)
  318. self.canvas.setBackground('light gray')
  319.  
  320. # create an empty dictionary
  321. # currently we have no shapes on the board
  322. self.grid = {}
  323.  
  324. def draw_shape(self, shape):
  325. ''' Parameters: shape - type: Shape
  326. Return value: type: bool
  327.  
  328. draws the shape on the board if there is space for it
  329. and returns True, otherwise it returns False
  330. '''
  331. if shape.can_move(self, 0, 0):
  332. shape.draw(self.canvas)
  333. return True
  334. return False
  335.  
  336. def can_move(self, x, y):
  337. ''' Parameters: x - type:int
  338. y - type:int
  339. Return value: type: bool
  340.  
  341. 1. check if it is ok to move to square x,y
  342. if the position is outside of the board boundaries, can't move there
  343. return False
  344.  
  345. 2. if there is already a block at that postion, can't move there
  346. return False
  347.  
  348. 3. otherwise return True
  349.  
  350. '''
  351.  
  352. #YOUR CODE HERE
  353. if not (0 <= x < self.width and 0 <= y < self.height):
  354. return False
  355. if (x, y) in self.grid:
  356. return False
  357. return True
  358.  
  359. def add_shape(self, shape):
  360. ''' Parameter: shape - type:Shape
  361.  
  362. add a shape to the grid, i.e.
  363. add each block to the grid using its
  364. (x, y) coordinates as a dictionary key
  365.  
  366. Hint: use the get_blocks method on Shape to
  367. get the list of blocks
  368.  
  369. '''
  370.  
  371. #YOUR CODE HERE
  372. for block in shape.get_blocks():
  373. self.grid[block.x, block.y] = block
  374.  
  375.  
  376. def delete_row(self, y):
  377. ''' Parameters: y - type:int
  378.  
  379. remove all the blocks in row y
  380. to remove a block you must remove it from the grid
  381. and erase it from the screen.
  382. If you dont remember how to erase a graphics object
  383. from the screen, take a look at the Graphics Library
  384. handout
  385.  
  386. '''
  387.  
  388. #YOUR CODE HERE
  389. for x in range(self.width):
  390. self.grid[x, y].undraw()
  391. del self.grid[x, y]
  392.  
  393. def is_row_complete(self, y):
  394. ''' Parameter: y - type: int
  395. Return value: type: bool
  396.  
  397. for each block in row y
  398. check if there is a block in the grid (use the in operator)
  399. if there is one square that is not occupied, return False
  400. otherwise return True
  401.  
  402. '''
  403.  
  404. #YOUR CODE HERE
  405. for x in range(self.width):
  406. if not (x, y) in self.grid:
  407. return False
  408. return True
  409.  
  410. def move_down_rows(self, y_start):
  411. ''' Parameters: y_start - type:int
  412.  
  413. for each row from y_start to the top
  414. for each column
  415. check if there is a block in the grid
  416. if there is, remove it from the grid
  417. and move the block object down on the screen
  418. and then place it back in the grid in the new position
  419.  
  420. '''
  421.  
  422. #YOUR CODE HERE
  423. for y in range(y_start, -1, -1):
  424. for x in range(self.width):
  425. if (x, y) in self.grid:
  426. block = self.grid[x, y]
  427. block.undraw()
  428. del self.grid[x, y]
  429. block.move(0, 1)
  430. self.grid[block.x, block.y] = block
  431. block.draw(self.canvas)
  432. # TO BE COMPLETED - Remember that if an I-shape in vertical
  433. # position can no longer move, non-contiguous rows
  434. # may have become completed
  435.  
  436. def remove_complete_rows(self):
  437. # This one controls the process; calls the other methods as helpers
  438. ''' removes all the complete rows
  439. 1. for each row, y,
  440. 2. check if the row is complete
  441. if it is,
  442. delete the row
  443. move all rows down starting at row y - 1
  444.  
  445. '''
  446.  
  447. #YOUR CODE HERE
  448. num_removed = 0
  449. for y in range(self.height):
  450. if self.is_row_complete(y):
  451. self.delete_row(y)
  452. self.move_down_rows(y - 1)
  453. num_removed += 1
  454. return num_removed
  455.  
  456. def game_over(self):
  457. ''' display "Game Over !!!" message in the center of the board
  458. HINT: use the Text class from the graphics library
  459. '''
  460.  
  461. #YOUR CODE HERE
  462. game_over_message = Text(Point(150, 100), "Game over")
  463. game_over_message.setSize(32)
  464. game_over_message.setTextColor("black")
  465. game_over_message.draw(self.canvas)
  466.  
  467.  
  468. ############################################################
  469. # TETRIS CLASS
  470. ############################################################
  471.  
  472. class Tetris():
  473. ''' Tetris class: Controls the game play
  474. Attributes:
  475. SHAPES - type: list (list of Shape classes)
  476. DIRECTION - type: dictionary - converts string direction to (dx, dy)
  477. BOARD_WIDTH - type:int - the width of the board
  478. BOARD_HEIGHT - type:int - the height of the board
  479. board - type:Board - the tetris board
  480. win - type:Window - the window for the tetris game
  481. delay - type:int - the speed in milliseconds for moving the shapes
  482. current_shape - type: Shape - the current moving shape on the board
  483. '''
  484.  
  485. SHAPES = [I_shape, J_shape, L_shape, O_shape, S_shape, T_shape, Z_shape]
  486. DIRECTION = {'Left':(-1, 0), 'Right':(1, 0), 'Down':(0, 1)}
  487. BOARD_WIDTH = 10
  488. BOARD_HEIGHT = 20
  489.  
  490. def __init__(self, win):
  491. self.score = 0
  492. self.board = Board(win, self.BOARD_WIDTH, self.BOARD_HEIGHT)
  493. self.scoreBoard = ScoreBoard(win, self.BOARD_WIDTH * Block.BLOCK_SIZE, 50, 0)
  494.  
  495.  
  496. self.win = win
  497. self.delay = 1000 #ms
  498.  
  499. # sets up the keyboard events
  500. # when a key is called the method key_pressed will be called
  501. self.win.bind_all('<Key>', self.key_pressed)
  502.  
  503. # set the current shape to a random new shape
  504. self.current_shape = self.create_new_shape()
  505.  
  506. # Draw the current_shape on the board (take a look at the
  507. # draw_shape method in the Board class)
  508. #### YOUR CODE HERE ####
  509. self.board.draw_shape(self.current_shape)
  510. self.increment_score(1)
  511. # For Step 9: animate the shape!
  512. #### YOUR CODE HERE ####
  513. self.paused = False
  514. self.animate_shape()
  515. def create_new_shape(self):
  516. ''' Return value: type: Shape
  517.  
  518. Create a random new shape that is centered
  519. at y = 0 and x = int(self.BOARD_WIDTH/2)
  520. return the shape
  521. '''
  522.  
  523. #YOUR CODE HERE
  524. return Tetris.SHAPES[random.randint(0, len(Tetris.SHAPES) - 1)] (Point(int(self.BOARD_WIDTH/2), 0))
  525.  
  526. def animate_shape(self):
  527. ''' animate the shape - move down at equal intervals
  528. specified by the delay attribute
  529. '''
  530. if not self.paused:
  531. self.do_move('Down')
  532. self.win.after(self.delay, self.animate_shape)
  533.  
  534. def do_move(self, direction):
  535. ''' Parameters: direction - type: string
  536. Return value: type: bool
  537.  
  538. Move the current shape in the direction specified by the parameter:
  539. First check if the shape can move. If it can, move it and return True
  540. Otherwise if the direction we tried to move was 'Down',
  541. 1. add the current shape to the board
  542. 2. remove the completed rows if any
  543. 3. create a new random shape and set current_shape attribute
  544. 4. If the shape cannot be drawn on the board, display a
  545. game over message
  546.  
  547. return False
  548.  
  549. '''
  550. # print direction
  551.  
  552. #YOUR CODE HERE
  553. dx, dy = self.DIRECTION[direction]
  554. if self.current_shape.can_move(self.board, dx, dy):
  555. self.current_shape.move(dx, dy)
  556. return True
  557. else:
  558. if direction == "Down":
  559. self.board.add_shape(self.current_shape)
  560. removed = self.board.remove_complete_rows()
  561. self.increment_score(removed * removed)
  562. self.current_shape = self.create_new_shape()
  563. if not self.board.draw_shape(self.current_shape):
  564. # self.board.game_over()
  565. self.scoreBoard.set_final_score(self.score)
  566. else:
  567. self.increment_score(1)
  568. return False
  569.  
  570. def do_rotate(self):
  571. ''' Checks if the current_shape can be rotated and
  572. rotates if it can
  573. '''
  574.  
  575. #YOUR CODE HERE
  576. if self.current_shape.can_rotate(self.board):
  577. self.current_shape.rotate(self.board)
  578. return True
  579. return False
  580.  
  581. def key_pressed(self, event):
  582. ''' this function is called when a key is pressed on the keyboard
  583. it currenly just prints the value of the key
  584.  
  585. Modify the function so that if the user presses the arrow keys
  586. 'Left', 'Right' or 'Down', the current_shape will move in
  587. the appropriate direction
  588.  
  589. if the user presses the space bar 'space', the shape will move
  590. down until it can no longer move and is added to the board
  591.  
  592. if the user presses the 'Up' arrow key ,
  593. the shape should rotate.
  594.  
  595. '''
  596.  
  597. #YOUR CODE HERE
  598. key = event.keysym
  599. if not self.paused:
  600. if key in self.DIRECTION:
  601. self.do_move(key)
  602. elif key == "Up":
  603. self.do_rotate()
  604. elif key == "space":
  605. while self.do_move("Down") == True:
  606. pass
  607. if key in ['p', 'P']:
  608. if self.paused:
  609. self.paused = False
  610. self.scoreBoard.set_score(self.score)
  611. else:
  612. self.paused = True
  613. self.scoreBoard.pause_score(self.score)
  614. # print key
  615. def increment_score(self, inc):
  616. self.score += inc
  617. self.scoreBoard.set_score(self.score)
  618. return self.score
  619.  
  620. ################################################################
  621. # Start the game
  622. ################################################################
  623.  
  624. win = Window("Tetris")
  625. game = Tetris(win)
  626. win.mainloop()
  627.  
Not running #stdin #stdout 0s 0KB
stdin
Standard input is empty
stdout
Standard output is empty