123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- #!/usr/bin/env python3
-
- # ----------------------------------------------------------------------------
- # "THE BEER-WARE LICENSE" (Revision 42):
- # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
- # you can do whatever you want with this stuff. If we meet some day, and you
- # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
- # ----------------------------------------------------------------------------
-
- from scroll import ScrollText
- import time
- import random
- import util
-
- class Tetris:
- def __init__(self, g, i, ts = 0.5, to = 60.0, w = 10, h = 22, rc = False):
- self.gui = g
- self.input = i
- self.timestep = ts
- self.timeout = to
- self.width = min(w, self.gui.width)
- self.height = min(h, self.gui.height)
- self.randomizeColors = rc
-
- self.endText = ScrollText(self.gui, "Game Over!", "uushi",
- 1, 50, (251, 72, 196))
- self.scoreText = ScrollText(self.gui, "Score:", "uushi",
- 3, 50, (63, 255, 33))
-
- self.bg = (0, 0, 0)
- self.colors = [
- (251, 72, 196), # camp23 pink
- (63, 255, 33), # camp23 green
- (255, 0, 0), # red
- #(0, 255, 0), # green
- (0, 0, 255), # blue
- (255, 255, 0), # yellow
- (0, 255, 255), # cyan
- #(255, 0, 255),
- #(255, 255, 255),
- (255, 127, 0), # orange
- (127, 127, 127), # grey
- ]
-
- DrawText = util.getTextDrawer()
- self.text = []
- self.text.append(DrawText(self.gui, self.colors[1])) # Green, "Score"
- self.text.append(DrawText(self.gui, self.colors[0])) # Pink, Score Number
- self.text.append(DrawText(self.gui, self.colors[4])) # Yellow, "Paused"
- self.text.append(DrawText(self.gui, self.colors[5])) # Blue, "next"
- self.text.append(DrawText(self.gui, self.colors[0])) # Pink, "Tetris"
- self.text.append(DrawText(self.gui, self.colors[5])) # Blue, "up"
- self.text[0].setText("Score:", "tom-thumb")
- self.text[1].setText("0", "tom-thumb")
- self.text[2].setText("Paused", "tom-thumb")
- self.text[3].setText("next", "tom-thumb")
- if self.gui.height > self.gui.panelH:
- self.text[4].setText("Tetris", "ib8x8u")
- self.text[5].setText("up:", "tom-thumb")
- else:
- self.text[4].setText("Tetris", "tom-thumb")
- self.text[5].setText("up", "tom-thumb")
-
- # all [y][x] sub-lists must be the same length
- self.pieces = [
- # "I"
- [
- [1],
- [1],
- [1],
- [1],
- ],
-
- # "L"
- [
- [1, 0],
- [1, 0],
- [1, 1],
- ],
-
- # "J"
- [
- [0, 1],
- [0, 1],
- [1, 1],
- ],
-
- # "T"
- [
- [1, 1, 1],
- [0, 1, 0],
- ],
-
- # "O"
- [
- [1, 1],
- [1, 1],
- ],
-
- # "S"
- [
- [0, 1, 1],
- [1, 1, 0],
- ],
-
- # "Z"
- [
- [1, 1, 0],
- [0, 1, 1],
- ],
- ]
-
- self.max_width = 0
- self.max_height = 0
- for piece in self.pieces:
- if len(piece) > self.max_height:
- self.max_height = len(piece)
- if len(piece[0]) > self.max_width:
- self.max_width = len(piece[0])
- self.max_width += 2
- self.max_height += 2
-
-
- self.fact = 1
- self.x_off = 1
- self.y_off = self.gui.height - self.height - 3
- self.next_x_off = self.gui.panelW - self.max_width + 24
- self.next_y_off = self.gui.panelH - self.max_height - 3
-
- if self.gui.height > self.gui.panelH:
- self.fact = 2
- self.y_off = self.gui.height - self.height * self.fact - 3
- self.next_y_off = self.gui.panelH - self.max_height * self.fact + 29
-
- random.seed()
- self.restart()
-
- def restart(self):
- self.start = time.time()
- self.last = time.time()
- self.button = None
- self.score = 0
- self.text[1].setText(str(self.score), "tom-thumb")
- self.done = False
- self.data = [[self.bg for y in range(self.height)] for x in range(self.width)]
- self.piece = None
- self.next_piece = None
- self.old_keys = {
- "left": False,
- "right": False,
- "up": False,
- "down": False,
- "a": False,
- "b": False,
- "x": False,
- "y": False,
- "l": False,
- "r": False,
- "start": False,
- "select": False,
- }
- self.pause = False
-
- def finished(self):
- if self.input == None:
- # backup timeout for "AI"
- if (time.time() - self.start) >= self.timeout:
- return True
-
- if self.done:
- # game over screen
- return self.scoreText.finished()
-
- return False
-
- def collision(self):
- # check for collision of piece with data
- pos = (self.piece[2], self.piece[3])
- for y in range(0, len(self.piece[0])):
- for x in range(0, len(self.piece[0][y])):
- # only check where piece actually is
- if self.piece[0][y][x] == 0:
- continue
-
- # check for collision with bottom wall
- if (y + pos[1]) >= self.height:
- return True
-
- # check for collision with right wall
- if (x + pos[0]) >= self.width:
- return True
-
- # check for collision with previous pieces
- if self.data[x + pos[0]][y + pos[1]] != self.bg:
- return True
- return False
-
- # copy piece into data buffer
- def put(self, clear, pos = None, pie = None):
- position = pos
- if position == None:
- position = (self.piece[2], self.piece[3])
-
- piece = pie
- if piece == None:
- piece = self.piece[0]
-
- for y in range(0, len(piece)):
- for x in range(0, len(piece[y])):
- # only set or clear where piece actually is
- if piece[y][x] == 0:
- continue
-
- if clear:
- self.data[x + position[0]][y + position[1]] = self.bg
- else:
- self.data[x + position[0]][y + position[1]] = self.piece[1]
-
- def checkWin(self):
- had_data = False
- for y in range(0, self.height):
- line_full = True
- for x in range(0, self.width):
- if self.data[x][y] == self.bg:
- line_full = False
- else:
- had_data = True
-
- if had_data and line_full:
- self.score += 1
- self.text[1].setText(str(self.score), "tom-thumb")
-
- # move stuff above into this line
- for y2 in reversed(range(1, y + 1)):
- for x in range(0, self.width):
- self.data[x][y2] = self.data[x][y2 - 1]
-
- # clear out top line
- for x in range(0, self.width):
- self.data[x][0] = self.bg
-
- # check for complete win
- board_clear = (self.piece == None)
- for y in range(0, self.height):
- for x in range(0, self.width):
- if self.data[x][y] != self.bg:
- board_clear = False
- if board_clear == False:
- break
- if board_clear == False:
- break
- return board_clear
-
- def step(self):
- if self.next_piece == None:
- # select a new piece type and color
- p_i = random.randrange(0, len(self.pieces))
- if self.randomizeColors:
- c_i = random.randrange(0, len(self.colors))
- else:
- c_i = p_i % len(self.colors)
- self.next_piece = [
- self.pieces[p_i], # piece
- self.colors[c_i], # color
- 0, # x
- 0, # y
- ]
-
- # center the piece on top of the playing board
- self.next_piece[2] = int((self.width - len(self.next_piece[0][0])) / 2)
-
- # offsets for drawing the next piece
- self.piece_x_off = int((self.max_width - len(self.next_piece[0][0])) / 2)
- self.piece_y_off = int((self.max_height - len(self.next_piece[0])) / 2)
-
- if self.piece == None:
- justPlaced = True
- self.piece = self.next_piece
- self.next_piece = None
-
- if self.collision():
- # new piece immediately collided. game over!
- return False
-
- # copy piece into data buffer
- self.put(False)
-
- # don't move in the placement-step
- return True
-
- oldPosition = (self.piece[2], self.piece[3])
- oldPiece = [x[:] for x in self.piece[0]]
-
- # button input
- if self.button == "u":
- # rotate piece
- # https://stackoverflow.com/a/48444999
- self.piece[0] = [list(x) for x in zip(*self.piece[0][::-1])]
- elif self.button == "l":
- if self.piece[2] > 0:
- self.piece[2] -= 1
- elif self.button == "r":
- if self.piece[2] < (self.width - 1):
- self.piece[2] += 1
- else:
- # one pixel down
- self.piece[3] += 1
-
- # clear out piece from its old location
- self.put(True, oldPosition, oldPiece)
-
- collision = self.collision()
- if collision:
- # piece collided, put it back
- self.put(False, oldPosition, oldPiece)
- self.piece[0] = oldPiece
- self.piece[2] = oldPosition[0]
- self.piece[3] = oldPosition[1]
-
- if (self.button != "l") and (self.button != "r") and (self.button != "u"):
- # but only stop playing it if it was moving down
- self.piece = None
-
- # check for cleared line
- if self.checkWin():
- return False
- else:
- # copy piece at new location into buffer
- self.put(False)
-
- # clear previous input
- self.button = None
-
- return True
-
- def buttons(self):
- keys = self.input.get()
-
- if keys["left"] and (not self.old_keys["left"]) and (not self.old_keys["select"]):
- self.button = "l"
- elif keys["right"] and (not self.old_keys["right"]) and (not self.old_keys["select"]):
- self.button = "r"
- elif keys["up"] and (not self.old_keys["up"]) and (not self.old_keys["select"]):
- self.button = "u"
- elif keys["down"] and (not self.old_keys["select"]):
- self.button = "d"
- elif (keys["select"] and keys["start"] and (not self.old_keys["start"])) or (keys["start"] and keys["select"] and (not self.old_keys["select"])):
- self.restart()
- elif keys["start"] and (not self.old_keys["start"]) and (not self.old_keys["select"]):
- self.pause = not self.pause
- elif self.done and keys["start"] and (not self.old_keys["start"]):
- self.restart()
- elif self.done and keys["a"] and (not self.old_keys["a"]):
- self.restart()
- elif self.done and keys["b"] and (not self.old_keys["b"]):
- self.restart()
- elif self.done and keys["x"] and (not self.old_keys["x"]):
- self.restart()
- elif self.done and keys["y"] and (not self.old_keys["y"]):
- self.restart()
-
- self.old_keys = keys.copy()
-
- def draw_stats(self, off):
- x_off, y_off = off
-
- if self.fact > 1:
- self.text[0].draw(-x_off - 2, y_off - 6)
- self.text[1].draw(-x_off - 2, y_off)
- else:
- self.text[0].draw(-x_off - 2, y_off - 11)
- self.text[1].draw(-x_off - 2, y_off - 5)
-
- if self.pause:
- if self.fact > 1:
- self.text[2].draw(-x_off - 2 + 16, -y_off + 11 - 5)
- else:
- self.text[2].draw(-x_off - 2, -y_off + 11)
-
- def draw(self):
- if self.input != None:
- self.buttons()
- else:
- # TODO "AI"
- self.button = None
-
- if self.done:
- if self.endText.finished():
- self.scoreText.draw()
- else:
- self.endText.draw()
- self.scoreText.restart()
- return
-
- now = time.time()
- if (not self.pause) and ((self.button != None) or ((now - self.last) >= self.timestep) or (now < self.last)):
- # don't let user stop falling pieces by moving/rotating endlessly
- if (self.button != "l") and (self.button != "r") and (self.button != "u"):
- self.last = now
-
- cont = self.step()
- if cont == False:
- self.done = True
- self.scoreText.setText("Score: " + str(self.score), "uushi")
- self.endText.restart()
-
- # static text
- if self.fact > 1:
- self.text[4].draw(-2, -22)
- self.text[3].draw(-34, 13)
- self.text[5].draw(-34, 20)
- else:
- self.text[4].draw(-2, -11)
- self.text[3].draw(-14, 5)
- self.text[5].draw(-14, 12)
-
- # draw play area and border
- for x in range(-1, self.width + 1):
- for y in range(-1, self.height + 1):
- c = self.colors[7] # border color
- if (x >= 0) and (y >= 0) and (x < self.width) and (y < self.height):
- c = self.data[x][y]
-
- for x1 in range(0, self.fact):
- for y1 in range(0, self.fact):
- self.gui.set_pixel(
- self.fact * x + 1 + self.x_off + x1,
- self.fact * y + 1 + self.y_off + y1,
- c
- )
-
- # draw next piece and border
- for x in range(-1, self.max_width + 1):
- for y in range(-1, self.max_height + 1):
- c = self.colors[7] # border color
- if (x >= 0) and (y >= 0) and (x < self.max_width) and (y < self.max_height):
- if self.next_piece == None:
- c = (0, 0, 0)
- else:
- if (y >= self.piece_y_off) and (y < (len(self.next_piece[0]) + self.piece_y_off)) and (x >= self.piece_x_off) and (x < (len(self.next_piece[0][0]) + self.piece_x_off)):
- if self.next_piece[0][y - self.piece_y_off][x - self.piece_x_off] != 0:
- c = self.next_piece[1]
- else:
- c = (0, 0, 0)
- else:
- c = (0, 0, 0)
-
- for x1 in range(0, self.fact):
- for y1 in range(0, self.fact):
- self.gui.set_pixel(
- self.fact * x + 1 + self.next_x_off + x1,
- self.fact * y + 1 + self.next_y_off + y1,
- c
- )
-
- # find position for stats
- stats_off = None
- if self.gui.width > self.gui.panelW:
- stats_off = (self.gui.panelW, 0)
- elif self.gui.height > self.gui.panelH:
- stats_off = (0, self.gui.panelH)
-
- # second screen with stats
- if stats_off != None:
- self.draw_stats(stats_off)
-
- if __name__ == "__main__":
- # Need to import InputWrapper before initializing RGB Matrix on Pi
- i = util.getInput()
- t = util.getTarget(i)
-
- # show splash screen while initializing
- from splash import SplashScreen
- splash = SplashScreen(t)
- t.loop_start()
- splash.draw()
- t.loop_end()
-
- d = Tetris(t, i)
- util.loop(t, d.draw)
|