Python RGB Matrix games and animations https://www.xythobuz.de/ledmatrix_v2.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tetris.py 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/usr/bin/env python3
  2. # ----------------------------------------------------------------------------
  3. # "THE BEER-WARE LICENSE" (Revision 42):
  4. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  5. # you can do whatever you want with this stuff. If we meet some day, and you
  6. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  7. # ----------------------------------------------------------------------------
  8. from scroll import ScrollText
  9. import time
  10. import random
  11. class Tetris:
  12. def __init__(self, g, i, ts = 0.5, to = 60.0, w = 10, h = 22):
  13. self.gui = g
  14. self.input = i
  15. self.timestep = ts
  16. self.timeout = to
  17. self.width = min(w, self.gui.width)
  18. self.height = min(h, self.gui.height)
  19. self.x_off = int((self.gui.panelW - self.width - 2) / 2)
  20. self.y_off = int((self.gui.panelH - self.height - 2) / 2)
  21. self.endText = ScrollText(self.gui, "Game Over!", "uushi",
  22. 2, 75, (251, 72, 196))
  23. self.scoreText = ScrollText(self.gui, "Score:", "uushi",
  24. 2, 75, (63, 255, 33))
  25. self.bg = (0, 0, 0)
  26. self.colors = [
  27. (251, 72, 196), # camp23 pink
  28. (63, 255, 33), # camp23 green
  29. (255, 0, 0),
  30. #(0, 255, 0),
  31. (0, 0, 255),
  32. (255, 255, 0),
  33. (0, 255, 255),
  34. (255, 0, 255),
  35. #(255, 255, 255),
  36. ]
  37. # all [y][x] sub-lists must be the same length
  38. self.pieces = [
  39. # "I"
  40. [
  41. [1],
  42. [1],
  43. [1],
  44. [1],
  45. ],
  46. # "L"
  47. [
  48. [1, 0],
  49. [1, 0],
  50. [1, 1],
  51. ],
  52. # "J"
  53. [
  54. [0, 1],
  55. [0, 1],
  56. [1, 1],
  57. ],
  58. # "T"
  59. [
  60. [1, 1, 1],
  61. [0, 1, 0],
  62. ],
  63. # "O"
  64. [
  65. [1, 1],
  66. [1, 1],
  67. ],
  68. # "S"
  69. [
  70. [0, 1, 1],
  71. [1, 1, 0],
  72. ],
  73. # "Z"
  74. [
  75. [1, 1, 0],
  76. [0, 1, 1],
  77. ],
  78. ]
  79. random.seed()
  80. self.restart()
  81. def restart(self):
  82. self.start = time.time()
  83. self.last = time.time()
  84. self.button = None
  85. self.score = 0
  86. self.done = False
  87. self.data = [[self.bg for y in range(self.height)] for x in range(self.width)]
  88. self.piece = None
  89. self.old_keys = {
  90. "left": False,
  91. "right": False,
  92. "up": False,
  93. "down": False,
  94. }
  95. def finished(self):
  96. if self.input == None:
  97. # backup timeout for "AI"
  98. if (time.time() - self.start) >= self.timeout:
  99. return True
  100. if self.done:
  101. # game over screen
  102. return self.scoreText.finished()
  103. return False
  104. def collision(self):
  105. # check for collision of piece with data
  106. pos = (self.piece[2], self.piece[3])
  107. for y in range(0, len(self.piece[0])):
  108. for x in range(0, len(self.piece[0][y])):
  109. # only check where piece actually is
  110. if self.piece[0][y][x] == 0:
  111. continue
  112. # check for collision with bottom wall
  113. if (y + pos[1]) >= self.height:
  114. return True
  115. # check for collision with right wall
  116. if (x + pos[0]) >= self.width:
  117. return True
  118. # check for collision with previous pieces
  119. if self.data[x + pos[0]][y + pos[1]] != self.bg:
  120. return True
  121. return False
  122. # copy piece into data buffer
  123. def put(self, clear, pos = None, pie = None):
  124. position = pos
  125. if position == None:
  126. position = (self.piece[2], self.piece[3])
  127. piece = pie
  128. if piece == None:
  129. piece = self.piece[0]
  130. for y in range(0, len(piece)):
  131. for x in range(0, len(piece[y])):
  132. # only set or clear where piece actually is
  133. if piece[y][x] == 0:
  134. continue
  135. if clear:
  136. self.data[x + position[0]][y + position[1]] = self.bg
  137. else:
  138. self.data[x + position[0]][y + position[1]] = self.piece[1]
  139. def checkWin(self):
  140. had_data = False
  141. for y in range(0, self.height):
  142. line_full = True
  143. for x in range(0, self.width):
  144. if self.data[x][y] == self.bg:
  145. line_full = False
  146. else:
  147. had_data = True
  148. if had_data and line_full:
  149. self.score += 1
  150. # move stuff above into this line
  151. for y2 in reversed(range(1, y + 1)):
  152. for x in range(0, self.width):
  153. self.data[x][y2] = self.data[x][y2 - 1]
  154. # clear out top line
  155. for x in range(0, self.width):
  156. self.data[x][0] = self.bg
  157. # check for complete win
  158. board_clear = (self.piece == None)
  159. for y in range(0, self.height):
  160. for x in range(0, self.width):
  161. if self.data[x][y] != self.bg:
  162. board_clear = False
  163. if board_clear == False:
  164. break
  165. if board_clear == False:
  166. break
  167. return board_clear
  168. def step(self):
  169. if self.piece == None:
  170. justPlaced = True
  171. # select a new piece type and color
  172. self.piece = [
  173. self.pieces[random.randrange(0, len(self.pieces))], # piece
  174. self.colors[random.randrange(0, len(self.colors))], # color
  175. 0, # x
  176. 0, # y
  177. ]
  178. # center the piece on top of the playing board
  179. self.piece[2] = int((self.width - len(self.piece[0][0])) / 2)
  180. if self.collision():
  181. # new piece immediately collided. game over!
  182. return False
  183. # copy piece into data buffer
  184. self.put(False)
  185. # don't move in the placement-step
  186. return True
  187. oldPosition = (self.piece[2], self.piece[3])
  188. oldPiece = [x[:] for x in self.piece[0]]
  189. # button input
  190. if self.button == "u":
  191. # rotate piece
  192. # https://stackoverflow.com/a/48444999
  193. self.piece[0] = [list(x) for x in zip(*self.piece[0][::-1])]
  194. elif self.button == "l":
  195. if self.piece[2] > 0:
  196. self.piece[2] -= 1
  197. elif self.button == "r":
  198. if self.piece[2] < (self.width - 1):
  199. self.piece[2] += 1
  200. else:
  201. # one pixel down
  202. self.piece[3] += 1
  203. # clear out piece from its old location
  204. self.put(True, oldPosition, oldPiece)
  205. collision = self.collision()
  206. if collision:
  207. # piece collided, put it back
  208. self.put(False, oldPosition, oldPiece)
  209. self.piece[0] = oldPiece
  210. self.piece[2] = oldPosition[0]
  211. self.piece[3] = oldPosition[1]
  212. if (self.button != "l") and (self.button != "r") and (self.button != "u"):
  213. # but only stop playing it if it was moving down
  214. self.piece = None
  215. # check for cleared line
  216. if self.checkWin():
  217. return False
  218. else:
  219. # copy piece at new location into buffer
  220. self.put(False)
  221. # clear previous input
  222. self.button = None
  223. return True
  224. def buttons(self):
  225. keys = self.input.get()
  226. if keys["left"] and (not self.old_keys["left"]):
  227. self.button = "l"
  228. elif keys["right"] and (not self.old_keys["right"]):
  229. self.button = "r"
  230. elif keys["up"] and (not self.old_keys["up"]):
  231. self.button = "u"
  232. elif keys["down"]:
  233. self.button = "d"
  234. elif (keys["select"] and keys["start"] and (not self.old_keys["start"])) or (keys["start"] and keys["select"] and (not self.old_keys["select"])):
  235. self.restart()
  236. self.old_keys = keys.copy()
  237. def draw(self):
  238. if self.done:
  239. if self.endText.finished():
  240. self.scoreText.draw()
  241. else:
  242. self.endText.draw()
  243. self.scoreText.restart()
  244. return
  245. if self.input != None:
  246. self.buttons()
  247. else:
  248. # TODO "AI"
  249. self.button = None
  250. now = time.time()
  251. if (self.button != None) or ((now - self.last) >= self.timestep) or (now < self.last):
  252. # don't let user stop falling pieces by moving/rotating endlessly
  253. if (self.button != "l") and (self.button != "r") and (self.button != "u"):
  254. self.last = now
  255. cont = self.step()
  256. if cont == False:
  257. self.done = True
  258. self.scoreText.setText("Score: " + str(self.score), "uushi")
  259. self.endText.restart()
  260. # TODO placement of play area
  261. for x in range(-1, self.width + 1):
  262. for y in range(-1, self.height + 1):
  263. c = (255, 255, 255)
  264. if (x >= 0) and (y >= 0) and (x < self.width) and (y < self.height):
  265. c = self.data[x][y]
  266. self.gui.set_pixel(x + 1 + self.x_off, y + 1 + self.y_off, c)
  267. if __name__ == "__main__":
  268. # Need to import InputWrapper before initializing RGB Matrix on Pi
  269. from gamepad import InputWrapper
  270. i = InputWrapper()
  271. import util
  272. t = util.getTarget()
  273. d = Tetris(t, i)
  274. t.loop(d.draw)