|
| 1 | +# *** |
| 2 | +# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games |
| 3 | +# *** - modified from dumbdisplay_examples/tetris/tetris_two_block.py |
| 4 | +# *** |
| 5 | + |
| 6 | +import random |
| 7 | +import time |
| 8 | + |
| 9 | +from dumbdisplay.core import * |
| 10 | +from dumbdisplay.layer_graphical import DDRootLayer |
| 11 | +from dumbdisplay.layer_turtle import LayerTurtle |
| 12 | +from dumbdisplay.layer_lcd import LayerLcd |
| 13 | +from dumbdisplay_examples.tetris._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ |
| 14 | + _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ |
| 15 | + _check_block_grid_placement, _randomize_grid |
| 16 | +from dumbdisplay_examples.tetris._shapes import _randomize_block_grid |
| 17 | + |
| 18 | +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd |
| 19 | + |
| 20 | +_RANDOMIZE_ROW_COUNT = 2 |
| 21 | + |
| 22 | + |
| 23 | +_delay = 0.3 # For time/sleep |
| 24 | + |
| 25 | +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): |
| 26 | + grid = shape.grid |
| 27 | + block = shape.block |
| 28 | + |
| 29 | + # Check if each row is full: |
| 30 | + empty_count = 23 |
| 31 | + deleted_count = 0 |
| 32 | + for y in range(0,24): |
| 33 | + is_full = True |
| 34 | + is_empty = True |
| 35 | + y_erase = y |
| 36 | + for x in range(0,12): |
| 37 | + if grid.get_value(y, x) == 0: |
| 38 | + is_full = False |
| 39 | + else: |
| 40 | + is_empty = False |
| 41 | + if not is_empty and not is_full: |
| 42 | + empty_count -= 1 |
| 43 | + break |
| 44 | + # Remove row and shift down |
| 45 | + if is_full: |
| 46 | + shape.score_count += 1 |
| 47 | + score.clear() |
| 48 | + score.write(f'Score: {shape.score_count}', align='C') |
| 49 | + |
| 50 | + for y in range(y_erase-1, -1, -1): |
| 51 | + for x in range(0,12): |
| 52 | + grid.set_value(y + 1, x, grid.get_value(y, x)) |
| 53 | + |
| 54 | + deleted_count += 1 |
| 55 | + |
| 56 | + return (empty_count == 23, deleted_count) |
| 57 | + |
| 58 | + |
| 59 | +class Shape: |
| 60 | + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): |
| 61 | + self.grid = _randomize_grid(_RANDOMIZE_ROW_COUNT) |
| 62 | + self.score_count = 0 |
| 63 | + self.block: Block = None |
| 64 | + self.pen = pen |
| 65 | + self.block_pen = block_pen |
| 66 | + if not self.reset_block(): |
| 67 | + raise Exception("Failed to create initial block") |
| 68 | + |
| 69 | + def check_grid(self, score: LayerTurtle) -> bool: |
| 70 | + (all_empty, delete_count) = _check_grid(self, score) |
| 71 | + if delete_count > 0: |
| 72 | + self.sync_image() |
| 73 | + return all_empty and self.block is None |
| 74 | + |
| 75 | + def reset_block(self) -> bool: |
| 76 | + x = 5 |
| 77 | + y = 0 |
| 78 | + block_grid = _randomize_block_grid() |
| 79 | + x -= int((block_grid.n_cols - 1) / 2) |
| 80 | + y += 1 - block_grid.n_rows |
| 81 | + if _check_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): |
| 82 | + return False |
| 83 | + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) |
| 84 | + self.sync_image() |
| 85 | + return True |
| 86 | + |
| 87 | + def commit_block(self): |
| 88 | + _commit_block_grid(self.block.block_grid, self.block.x, self.block.y, self.grid) |
| 89 | + #self.block.commit(self.grid) |
| 90 | + self.sync_image() |
| 91 | + self.block = None |
| 92 | + |
| 93 | + def move_block_down(self) -> bool: |
| 94 | + return self.block.move_down(self.grid) |
| 95 | + |
| 96 | + def move_block_right(self) -> bool: |
| 97 | + return self.block.move_right(self.grid) |
| 98 | + |
| 99 | + def move_block_left(self) -> bool: |
| 100 | + return self.block.move_left(self.grid) |
| 101 | + |
| 102 | + def sync_image(self): |
| 103 | + #self.block.sync_image() |
| 104 | + _draw_grid(self.grid, self.pen) |
| 105 | + |
| 106 | + |
| 107 | +class TetrisTwoBlockApp(DDAppBase): |
| 108 | + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): |
| 109 | + super().__init__(dd) |
| 110 | + self.score: LayerTurtle = None |
| 111 | + self.block_pen: LayerTurtle = None |
| 112 | + self.pen: LayerTurtle = None |
| 113 | + self.shape: Shape = None |
| 114 | + self.last_update_time = None |
| 115 | + |
| 116 | + def initializeDD(self): |
| 117 | + |
| 118 | + root = DDRootLayer(self.dd, _width, _height) |
| 119 | + root.border(5, "darkred", "round", 1) |
| 120 | + root.backgroundColor("black") |
| 121 | + |
| 122 | + block_pen = LayerTurtle(self.dd, _width, _height) |
| 123 | + block_pen.penFilled() |
| 124 | + #block_pen.setTextSize(32) |
| 125 | + |
| 126 | + pen = LayerTurtle(self.dd, _width, _height) |
| 127 | + pen.penFilled() |
| 128 | + pen.setTextSize(32) |
| 129 | + |
| 130 | + score = LayerTurtle(self.dd, _width, _height) |
| 131 | + score.penColor('red') |
| 132 | + score.penUp() |
| 133 | + score.goTo(60, -300) |
| 134 | + score.setTextFont("Courier", 24) |
| 135 | + #score.write('Score: 0', 'C') |
| 136 | + |
| 137 | + border = LayerTurtle(self.dd, _width, _height) |
| 138 | + if False: |
| 139 | + border.rectangle(260, 490, centered=True) |
| 140 | + border.penSize(10) |
| 141 | + border.penUp() |
| 142 | + border.goTo(-130, 240) |
| 143 | + border.penDown() |
| 144 | + border.penColor('linen') |
| 145 | + border.rightTurn(90) |
| 146 | + border.forward(490) # Down |
| 147 | + border.leftTurn(90) |
| 148 | + border.forward(260) # Right |
| 149 | + border.leftTurn(90) |
| 150 | + border.forward(490) # Up |
| 151 | + border.penUp() |
| 152 | + border.goTo(0,260) |
| 153 | + border.setTextFont("Courier", 36) |
| 154 | + border.write("TETRIS", "C") |
| 155 | + |
| 156 | + left_button = LayerLcd(self.dd, 2, 1, char_height=28) |
| 157 | + left_button.noBackgroundColor() |
| 158 | + left_button.writeLine("⬅️") |
| 159 | + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) |
| 160 | + |
| 161 | + right_button = LayerLcd(self.dd, 2, 1, char_height=28) |
| 162 | + right_button.noBackgroundColor() |
| 163 | + right_button.writeLine("➡️") |
| 164 | + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) |
| 165 | + |
| 166 | + AutoPin('V', |
| 167 | + AutoPin('S'), |
| 168 | + AutoPin('H', left_button, right_button)).pin(self.dd) |
| 169 | + |
| 170 | + self.score = score |
| 171 | + self.block_pen = block_pen |
| 172 | + self.pen = pen |
| 173 | + |
| 174 | + self.startGame() |
| 175 | + #self.shape = Shape() |
| 176 | + #self.resetBlock() |
| 177 | + |
| 178 | + self.last_update_time = time.time() |
| 179 | + |
| 180 | + def updateDD(self): |
| 181 | + now = time.time() |
| 182 | + need_update = (now - self.last_update_time) >= _delay |
| 183 | + if need_update: |
| 184 | + self.last_update_time = now |
| 185 | + self.update() |
| 186 | + |
| 187 | + def update(self): |
| 188 | + if self.shape is None: |
| 189 | + print("... waiting to restart ...") |
| 190 | + return |
| 191 | + |
| 192 | + moved_down = self.moveBlockDown() |
| 193 | + if not moved_down: |
| 194 | + self.shape.commit_block() |
| 195 | + won = self.checkGrid() |
| 196 | + if won: |
| 197 | + self.endGame(won=True) |
| 198 | + elif not moved_down: |
| 199 | + if not self.resetBlock(): |
| 200 | + self.endGame(won=False) |
| 201 | + # if self.shape.block.y > 0: |
| 202 | + # #self.shape.reset_block() |
| 203 | + # self.resetBlock() |
| 204 | + # else: |
| 205 | + # self.endGame(won=False) |
| 206 | + |
| 207 | + def startGame(self): |
| 208 | + self.score.clear() |
| 209 | + self.score.write('Score: 0', 'C') |
| 210 | + self.pen.clear() |
| 211 | + self.block_pen.clear() |
| 212 | + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) |
| 213 | + print("... started game") |
| 214 | + |
| 215 | + |
| 216 | + def endGame(self, won: bool): |
| 217 | + #self.block_pen.clear() |
| 218 | + self.shape = None |
| 219 | + if won: |
| 220 | + msg = "🥳 YOU WON 🥳" |
| 221 | + color = "purple" |
| 222 | + else: |
| 223 | + msg = "GAME OVER 😔" |
| 224 | + color = "darkgray" |
| 225 | + self.pen.home(with_pen=False) |
| 226 | + self.pen.penColor("white") |
| 227 | + self.pen.oval(300, 100, centered=True) |
| 228 | + self.pen.penColor(color) |
| 229 | + self.pen.write(msg, align='C') |
| 230 | + print("... ended game") |
| 231 | + |
| 232 | + |
| 233 | + def checkGrid(self) -> bool: |
| 234 | + return self.shape.check_grid(score=self.score) |
| 235 | + # check_result = check_grid(shape=self.shape, score=self.score) |
| 236 | + # self.drawGrid() # should only redraw if any lines were cleared |
| 237 | + # return check_result |
| 238 | + |
| 239 | + def resetBlock(self) -> bool: |
| 240 | + return self.shape.reset_block() |
| 241 | + #self.drawGrid() |
| 242 | + #self.drawBlock() |
| 243 | + |
| 244 | + def moveBlockDown(self) -> bool: |
| 245 | + return self.shape.move_block_down() |
| 246 | + # if self.shape.move_block_down(): |
| 247 | + # self.drawBlock() |
| 248 | + # return True |
| 249 | + # return False |
| 250 | + |
| 251 | + def moveBlockLeft(self) -> bool: |
| 252 | + #print("$ move left") |
| 253 | + if self.shape is None: |
| 254 | + self.startGame() |
| 255 | + return False |
| 256 | + return self.shape.move_block_left() |
| 257 | + # if self.shape.move_block_left(): |
| 258 | + # self.drawBlock() |
| 259 | + # return True |
| 260 | + # return False |
| 261 | + |
| 262 | + def moveBlockRight(self) -> bool: |
| 263 | + if self.shape is None: |
| 264 | + self.startGame() |
| 265 | + return False |
| 266 | + return self.shape.move_block_right() |
| 267 | + # if self.shape.move_block_right(): |
| 268 | + # self.drawBlock() |
| 269 | + # return True |
| 270 | + # return False |
| 271 | + |
| 272 | + |
| 273 | +if __name__ == "__main__": |
| 274 | + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase |
| 275 | + app = TetrisTwoBlockApp(create_example_wifi_dd()) |
| 276 | + app.run() |
0 commit comments