From 946d30d4f11772952e6797d51d0af5d796973876 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 9 Aug 2025 21:41:03 +0800 Subject: [PATCH 01/76] changed setup.py --- .gitignore | 1 + dd_demo.py | 2 +- setup.py | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ac605c4..1c63cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ uDumbDisplay.code-workspace /OLD.md +/setup_OLD.py diff --git a/dd_demo.py b/dd_demo.py index a2422c9..fa052ee 100644 --- a/dd_demo.py +++ b/dd_demo.py @@ -291,7 +291,7 @@ def run_mnist_app(): if __name__ == "__main__": - demo_Feedback() + demo_AutoPin() if True: demo_LayerLedGrid(2, 2) diff --git a/setup.py b/setup.py index aad2fb4..8c2352d 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ import setuptools -from setuptools.config.expand import find_packages # read the contents of your README file @@ -19,6 +18,6 @@ project_urls = { }, license='MIT', - packages=find_packages(include=["dumbdisplay*"]), + packages=["dumbdisplay", "dumbdisplay_examples"], install_requires=[], ) From c33bb9c52d9d1232defceb37641488147d01e341 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 9 Aug 2025 21:48:06 +0800 Subject: [PATCH 02/76] changed setup.py --- setup.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/setup.py b/setup.py index 8c2352d..e35110e 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,14 @@ import setuptools -# read the contents of your README file -from pathlib import Path -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - setuptools.setup( name='uDumbDisplayLib', version='0.5.0', author='Trevor Lee', author_email='trevorwslee@gmail.com', description='MicroPython DumbDisplay Library', - long_description=long_description, long_description_content_type="text/markdown", url='https://github.com/trevorwslee/MicroPython-DumbDisplay', - project_urls = { - }, license='MIT', packages=["dumbdisplay", "dumbdisplay_examples"], - install_requires=[], ) From a346880dcc05d8907a2ffae1877074afefd6f3fb Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 9 Aug 2025 21:50:33 +0800 Subject: [PATCH 03/76] working on v0.5.1 --- README.md | 5 ++++- setup.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 80274eb..09a6958 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# DumbDisplay MicroPython Library (v0.5.0) +# DumbDisplay MicroPython Library (v0.5.1) DumbDisplay MicroPython Library -- workable with Python 3 -- is a port of the [DumbDisplay Arduino Library](https://github.com/trevorwslee/Arduino-DumbDisplay) to MicroPython / Python 3 for the [DumbDisplay Android app](https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay) @@ -334,6 +334,9 @@ MIT # Change History +v0.5.1 +- bug fixes + v0.5.0 - ported "level options" for LayerGraphical - ported LayerSelection diff --git a/setup.py b/setup.py index e35110e..74a22b5 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setuptools.setup( name='uDumbDisplayLib', - version='0.5.0', + version='0.5.1', author='Trevor Lee', author_email='trevorwslee@gmail.com', description='MicroPython DumbDisplay Library', From 95e130199099381798241bc3e59bb0a36baf71f5 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 9 Aug 2025 22:55:19 +0800 Subject: [PATCH 04/76] working on v0.5.1 --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 74a22b5..fd2f191 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import setuptools +from setuptools.config.expand import find_packages setuptools.setup( @@ -10,5 +11,5 @@ long_description_content_type="text/markdown", url='https://github.com/trevorwslee/MicroPython-DumbDisplay', license='MIT', - packages=["dumbdisplay", "dumbdisplay_examples"], +packages=find_packages(include=["dumbdisplay*"]), ) From 867788ae9b3c8bed658af8fade4a024a466e12ea Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 12 Aug 2025 19:53:52 +0800 Subject: [PATCH 05/76] working on v0.5.1 --- _dev_test.py | 67 ++++++-------- dd_demo.py | 36 +++++++- dumbdisplay/ddimpl.py | 5 +- dumbdisplay/ddlayer.py | 3 + dumbdisplay/ddlayer_turtle.py | 165 ++++++++++++++++++++++++++++++++++ dumbdisplay/full.py | 1 + dumbdisplay/layer_turtle.py | 2 + 7 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 dumbdisplay/ddlayer_turtle.py create mode 100644 dumbdisplay/layer_turtle.py diff --git a/_dev_test.py b/_dev_test.py index fbd9afa..8b90f0b 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -25,6 +25,21 @@ def run_melody(): import samples.melody.main +def test_turtleTracked(): + from dumbdisplay.layer_turtle import LayerTurtleTracked + dd = create_example_wifi_dd() + l = LayerTurtleTracked(dd, 100, 100) + l.backgroundColor("ivory") + l.border(3, "blue") + l.forward(100) + coor = l.pos() + print(f"* INIT turtle pos: {coor}") + while True: + coor = l.pos() + print(f"* turtle pos: {coor}") + dd.sleep(2) + + def test_margin(): from dumbdisplay.layer_ledgrid import LayerLedGrid dd = create_example_wifi_dd() @@ -51,22 +66,6 @@ def run_passive_blink_app(): app.run() -def run_sliding_puzzle_app(): - from dumbdisplay_examples.sliding_puzzle.sliding_puzzle_app import SlidingPuzzleApp - print(f"*** SlidingPuzzleApp ***") - suggest_move_from_dir_func = lambda board_manager: random.randint(0, 3) - app = SlidingPuzzleApp(dd=create_example_wifi_dd(), suggest_move_from_dir_func=suggest_move_from_dir_func) - app.run() - -def run_mnist_app(): - from dumbdisplay_examples.mnist.mnist_app import MnistApp - print(f"*** MnistApp ***") - inference_func = lambda board_manager: random.randint(0, 9) - app = MnistApp(dd=create_example_wifi_dd(), inference_func=inference_func) - app.run() - - - def test_read_readme(): from pathlib import Path this_directory = Path(__file__).parent @@ -81,27 +80,17 @@ def test_find_packages(): if __name__ == "__main__": - #test_LayerLedGrid(2, 2) - #test_LayerLcd() - #test_LayerGraphical() - #test_Layer7SegmentRow() - #test_LayerSelection() - #test_LayerPlotter() - - test_AutoPin() - - #run_passive_blink_app() - #run_sliding_puzzle_app() - #run_mnist_app() - - #run_debug() - #run_doodle() - #run_graphical() - #run_melody() - - #test_margin() - - #run_debugBlepriority("MyBLEDevice") - #test_read_readme() - #test_find_packages() + test_turtleTracked() + + # run_debug() + # run_doodle() + # run_graphical() + # run_melody() + # + # test_margin() + # + # run_debugBlepriority("MyBLEDevice") + # + # test_read_readme() + # test_find_packages() diff --git a/dd_demo.py b/dd_demo.py index fa052ee..92290c5 100644 --- a/dd_demo.py +++ b/dd_demo.py @@ -171,6 +171,39 @@ def demo_LayerJoystick(maxStickValue: int = 1023, directions: str = ""): if fb: print(f"* Feedback: {fb.type} at ({fb.x}, {fb.y})") +def demo_LayerTurtle(): + from dumbdisplay.layer_turtle import LayerTurtle + + # Create a DumbDisplay instance + dd = _create_demo_dd() + + # Create a LayerGraphical layer and set it up, like background color and border + # The LayerGraphical layer is 150 pixels wide and 100 pixels high + # Note that this size is not the size on the DumbDisplay app's canvas + # All layers will be scaled to fit the DumbDisplay app's canvas, keeping the aspect ratio. + l = LayerTurtle(dd, 400, 250) + l.backgroundColor("ivory") + l.border(3, "blue") + + distance = 5 + angle = 30 + for i in range(0, 50): + l.forward(distance) + l.rightTurn(angle) + distance += 1 + + # # Draw on the canvas of the LayerGraphical instance + # for i in range(0, 15): + # delta = 3 * i + # x = delta + # y = delta + # w = 150 - 2 * x + # h = 100 - 2 * y + # l.drawRect(x, y, w, h, "plum") + + while True: + dd.timeslice() + def demo_AutoPin(): from dumbdisplay.full import LayerLedGrid, LayerLcd, LayerGraphical, Layer7SegmentRow, LayerSelection, AutoPin @@ -291,7 +324,7 @@ def run_mnist_app(): if __name__ == "__main__": - demo_AutoPin() + demo_LayerTurtle() if True: demo_LayerLedGrid(2, 2) @@ -301,6 +334,7 @@ def run_mnist_app(): demo_LayerSelection() demo_LayerPlotter() demo_LayerJoystick(directions="") # directions can be "", "lr" or "tb" + demo_LayerTurtle() demo_AutoPin() demo_Feedback() diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 4e1adf9..f3576a8 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -465,7 +465,10 @@ def _checkForFeedback(self): y = 0 layer = self._layers.get(lid) if layer is not None: - layer._handleFeedback(type, x, y) + if type == "_": + layer._handleStateChange(x, y) + else: + layer._handleFeedback(type, x, y) except: pass def _readFeedback(self) -> str: diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index a2f3bb8..1ac46f5 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -194,5 +194,8 @@ def _handleFeedback(self, type, x, y): self._feedbacks.append((type, x, y)) # self._shipFeedbacks() + def _handleStateChange(self, x, y): + pass + diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py new file mode 100644 index 0000000..aacb9e8 --- /dev/null +++ b/dumbdisplay/ddlayer_turtle.py @@ -0,0 +1,165 @@ +#from ._ddlayer import DDLayer +from .ddlayer_multilevel import DDLayerMultiLevel +from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG + +class DDLayerTurtle(DDLayerMultiLevel): + '''Graphical LCD''' + def __init__(self, dd, width, height): + ''' + :param dd: DumbDisplay object + :param width: width + :param height: height + ''' + self._init(dd, width, height, track_move=False) + def _init(self, dd, width, height, track_move: bool): + if track_move: + layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_BOOL_ARG(True)) + else: + layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height)) + super().__init__(dd, layer_id) + def forward(self, distance: int, with_pen: bool = True): + ''' + forward; with pen or not + ''' + self.dd._sendCommand(self.layer_id, "fd" if with_pen else "jfd", _DD_INT_ARG(distance)) + def backward(self, distance: int, with_pen: bool = True): + ''' + backward; with pen or not + ''' + self.dd._sendCommand(self.layer_id, "bk" if with_pen else "jbk", _DD_INT_ARG(distance)) + def leftTurn(self, angle: int): + ''' + left turn + ''' + self.dd._sendCommand(self.layer_id, "lt", _DD_INT_ARG(angle)) + def rightTurn(self, angle: int): + ''' + right turn + ''' + self.dd._sendCommand(self.layer_id, "rt", _DD_INT_ARG(angle)) + def home(self, with_pen: bool = True): + ''' + go home (0, 0); with pen or not + ''' + self.dd._sendCommand(self.layer_id, "home" if with_pen else "jhome") + def goTo(self, x: int, y: int, with_pen: bool = True): + ''' + go to (x, y); with pen or not + ''' + self.dd._sendCommand(self.layer_id, "goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) + def goBy(self, by_x: int, by_y: int, with_pen: bool = True): + ''' + go by (by_x, by_y); with pen or not + ''' + self.dd._sendCommand(self.layer_id, "goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) + def setHeading(self, angle: int): + ''' + set heading angle (degree) + ''' + self.dd._sendCommand(self.layer_id, "seth", _DD_INT_ARG(angle)) + def penSize(self, size: int): + ''' + set pen size + ''' + self.dd._sendCommand(self.layer_id, "pensize", _DD_INT_ARG(size)) + def penColor(self, color: str): + ''' + set pen color + ''' + self.dd._sendCommand(self.layer_id, "pencolor", _DD_COLOR_ARG(color)) + def fillColor(self, color: str): + ''' + set fill color + ''' + self.dd._sendCommand(self.layer_id, "fillcolor", _DD_COLOR_ARG(color)) + def noColor(self): + ''' + set no fill color + ''' + self.dd._sendCommand(self.layer_id, "nofillcolor") + def penFilled(self, filled: bool = True): + ''' + set pen filled or not + ''' + self.dd._sendCommand(self.layer_id, "pfilled", _DD_BOOL_ARG(filled)) + def setTextSize(self, size: int): + ''' + set text size + ''' + self.dd._sendCommand(self.layer_id, "ptextsize", _DD_INT_ARG(size)) + def setTextFont(self, font_name = "", text_size = 0): + ''' + set font + @param font_name: empty means default + @param text_size: 0 means default + ''' + self.dd._sendCommand(self.layer_id, "ptextfont", font_name, _DD_INT_ARG(text_size)) + def penUp(self): + '''pen up''' + self.dd._sendCommand(self.layer_id, "pu") + def penDown(self): + '''pen down''' + self.dd._sendCommand(self.layer_id, "pd") + def beginFill(self): + '''begin fill''' + self.dd._sendCommand(self.layer_id, "begin_fill") + def endFill(self): + '''end fill''' + self.dd._sendCommand(self.layer_id, "end_fill") + def dot(self, size: int, color: str): + '''draw a dot''' + self.dd._sendCommand(self.layer_id, "dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) + def circle(self, radius: int, centered: bool = False): + """draw circle; centered or not""" + self.dd._sendCommand(self.layer_id, "ccircle" if centered else "circle", _DD_INT_ARG(radius)) + def oval(self, width: int, height: int, centered: bool = False): + """draw oval; centered or not""" + self.dd._sendCommand(self.layer_id, "coval" if centered else "oval" , _DD_INT_ARG(width), _DD_INT_ARG(height)) + def arc(self, width: int, height: int, start_angle: int, sweep_angle: int, centered: bool = False): + """draw arc; centered or not""" + self.dd._sendCommand(self.layer_id, "carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) + def triangle(self, side1: int, angle: int, side2: int): + """draw triangle (SAS)""" + self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side1), _DD_INT_ARG(angle), _DD_INT_ARG(side2)) + def isoscelesTriangle(self, side: int, angle: int): + """draw isosceles triangle; given size and angle""" + self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side), _DD_INT_ARG(angle)) + def rectangle(self, width: int, height: int, centered: bool = False): + """draw rectangle; centered or not""" + self.dd._sendCommand(self.layer_id, "crect" if centered else "rect", _DD_INT_ARG(width), _DD_INT_ARG(height)) + def polygon(self, side: int, vertex_count: int): + """draw polygon given side and vertex count""" + self.dd._sendCommand(self.layer_id, "poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) + def centeredPolygon(self, radius: int, vertex_count: int, inside: bool = False): + """draw polygon enclosed in an imaginary centered circle + - given circle radius and vertex count + - whether inside the imaginary circle or outside of it""" + self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) + def write(self, text: str, draw: bool = False): + """write text; draw means draw the text (honor heading)""" + self.dd._sendCommand(self.layer_id, "drawtext" if draw else "write", text) + + +class DDLayerTurtleTracked(DDLayerTurtle): + '''Graphical LCD''' + def __init__(self, dd, width, height): + ''' + :param dd: DumbDisplay object + :param width: width + :param height: height + ''' + self._init(dd, width, height, track_move=True) + self._x: int = 0 + self._y: int = 0 + def pos(self) -> (int, int): + self.dd.timeslice() + return (self._x, self._y) + def xcor(self) -> int: + self.dd.timeslice() + return self._x + def ycor(self) -> int: + self.dd.timeslice() + return self._y + def _handleStateChange(self, x, y): + self._x = x + self._y = y diff --git a/dumbdisplay/full.py b/dumbdisplay/full.py index 92a8ff6..2d8470c 100644 --- a/dumbdisplay/full.py +++ b/dumbdisplay/full.py @@ -10,5 +10,6 @@ from .layer_ledgrid import * from .layer_plotter import * from .layer_selection import * +from .layer_turtle import * from ._exp_tunnel_basic import * \ No newline at end of file diff --git a/dumbdisplay/layer_turtle.py b/dumbdisplay/layer_turtle.py new file mode 100644 index 0000000..c7dd6f2 --- /dev/null +++ b/dumbdisplay/layer_turtle.py @@ -0,0 +1,2 @@ +from .ddlayer_turtle import DDLayerTurtle as LayerTurtle +from .ddlayer_turtle import DDLayerTurtleTracked as LayerTurtleTracked From c5e67590ae6a1d18b28e8eb5e6701525152d087f Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 12 Aug 2025 21:07:19 +0800 Subject: [PATCH 06/76] working on v0.5.1 --- dumbdisplay/ddimpl.py | 10 ++++++++-- dumbdisplay/ddlayer.py | 4 +--- dumbdisplay/ddlayer_multilevel.py | 1 - dumbdisplay/ddlayer_turtle.py | 10 +++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index f3576a8..e662b92 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -439,6 +439,7 @@ def _checkForFeedback(self): if len(type) == 1 and type >= "0" and type <= "9": x = int(type) y = 0 + text = None type = "click" else: if type == "C": @@ -457,8 +458,13 @@ def _checkForFeedback(self): type = "custom" feedback = feedback[idx + 1:] idx = feedback.index(',') + idx2 = feedback.find(',', idx + 1) x = int(feedback[0:idx]) - y = int(feedback[idx + 1:]) + if idx2 == -1: + y = int(feedback[idx + 1:]) + else: + y = int(feedback[idx + 1:idx2]) + text = feedback[idx2 + 1:] # TODO: set text as feedback text else: type = "click" x = 0 @@ -466,7 +472,7 @@ def _checkForFeedback(self): layer = self._layers.get(lid) if layer is not None: if type == "_": - layer._handleStateChange(x, y) + layer._handleAck(x, y) # TODO: working on "ack" (say, for returning turtle position) else: layer._handleFeedback(type, x, y) except: diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 1ac46f5..1842039 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -187,14 +187,12 @@ def reorderLayer(self, how: str): def _handleFeedback(self, type, x, y): #print("RAW FB: " + self.layer_id + '.' + type + ':' + str(x) + ',' + str(y)) if self._feedback_handler is not None: - # if False: # TODO: text parameters - # print(self._feedback_handler.__code__.co_argcount) self._feedback_handler(self, type, x, y) else: self._feedbacks.append((type, x, y)) # self._shipFeedbacks() - def _handleStateChange(self, x, y): + def _handleAck(self, x, y): pass diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 7d4fbb3..515882c 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -150,4 +150,3 @@ def deleteLevel(self, level_id: str): ''' self.dd._sendCommand(self.layer_id, "dellevel", level_id) -# TODO: add more diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index aacb9e8..29c5f28 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -1,9 +1,9 @@ #from ._ddlayer import DDLayer -from .ddlayer_multilevel import DDLayerMultiLevel +from .ddlayer_multilevel import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG -class DDLayerTurtle(DDLayerMultiLevel): - '''Graphical LCD''' +class DDLayerTurtle(DDLayer): + '''Turtle-like Layer''' def __init__(self, dd, width, height): ''' :param dd: DumbDisplay object @@ -140,7 +140,7 @@ def write(self, text: str, draw: bool = False): self.dd._sendCommand(self.layer_id, "drawtext" if draw else "write", text) -class DDLayerTurtleTracked(DDLayerTurtle): +class DDLayerTurtleTracked(DDLayerTurtle): # TODO: working on DDLayerTurtleTracked '''Graphical LCD''' def __init__(self, dd, width, height): ''' @@ -160,6 +160,6 @@ def xcor(self) -> int: def ycor(self) -> int: self.dd.timeslice() return self._y - def _handleStateChange(self, x, y): + def _handleAck(self, x, y): self._x = x self._y = y From cdcd73f37db2cee7a5d9d2578a5f6e946f19290b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 12 Aug 2025 21:23:29 +0800 Subject: [PATCH 07/76] working on ack_seq --- dumbdisplay/ddimpl.py | 8 ++++++-- dumbdisplay/ddlayer_turtle.py | 36 +++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index e662b92..0756758 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -364,12 +364,15 @@ def _sendSpecial(self, special_type: str, special_id: str, special_command: str, self._io.print(special_data) self._io.print('\n') #self.switchDebugLed(False) - def _sendCommand(self, layer_id: str, command: str, *params): + def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: str = None): self._checkForFeedback() #self.switchDebugLed(True) try: if layer_id is not None: self._io.print(layer_id) + if ack_seq is not None: + self._io.print('@') + self._io.print(ack_seq) self._io.print('.') self._io.print(command) for i in range(0, len(params)): @@ -462,6 +465,7 @@ def _checkForFeedback(self): x = int(feedback[0:idx]) if idx2 == -1: y = int(feedback[idx + 1:]) + text = None else: y = int(feedback[idx + 1:idx2]) text = feedback[idx2 + 1:] # TODO: set text as feedback text @@ -472,7 +476,7 @@ def _checkForFeedback(self): layer = self._layers.get(lid) if layer is not None: if type == "_": - layer._handleAck(x, y) # TODO: working on "ack" (say, for returning turtle position) + layer._handleAck(x, y, text) # use text as ack_seq else: layer._handleFeedback(type, x, y) except: diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 29c5f28..18480fc 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -21,12 +21,12 @@ def forward(self, distance: int, with_pen: bool = True): ''' forward; with pen or not ''' - self.dd._sendCommand(self.layer_id, "fd" if with_pen else "jfd", _DD_INT_ARG(distance)) + self._sendCommandToDD("fd" if with_pen else "jfd", _DD_INT_ARG(distance)) def backward(self, distance: int, with_pen: bool = True): ''' backward; with pen or not ''' - self.dd._sendCommand(self.layer_id, "bk" if with_pen else "jbk", _DD_INT_ARG(distance)) + self._sendCommandToDD("bk" if with_pen else "jbk", _DD_INT_ARG(distance)) def leftTurn(self, angle: int): ''' left turn @@ -41,17 +41,17 @@ def home(self, with_pen: bool = True): ''' go home (0, 0); with pen or not ''' - self.dd._sendCommand(self.layer_id, "home" if with_pen else "jhome") + self._sendCommandToDD("home" if with_pen else "jhome") def goTo(self, x: int, y: int, with_pen: bool = True): ''' go to (x, y); with pen or not ''' - self.dd._sendCommand(self.layer_id, "goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) + self._sendCommandToDD("goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) def goBy(self, by_x: int, by_y: int, with_pen: bool = True): ''' go by (by_x, by_y); with pen or not ''' - self.dd._sendCommand(self.layer_id, "goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) + self._sendCommandToDD("goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) def setHeading(self, angle: int): ''' set heading angle (degree) @@ -108,36 +108,38 @@ def endFill(self): self.dd._sendCommand(self.layer_id, "end_fill") def dot(self, size: int, color: str): '''draw a dot''' - self.dd._sendCommand(self.layer_id, "dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) + self._sendCommandToDD("dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) def circle(self, radius: int, centered: bool = False): """draw circle; centered or not""" - self.dd._sendCommand(self.layer_id, "ccircle" if centered else "circle", _DD_INT_ARG(radius)) + self._sendCommandToDD("ccircle" if centered else "circle", _DD_INT_ARG(radius)) def oval(self, width: int, height: int, centered: bool = False): """draw oval; centered or not""" - self.dd._sendCommand(self.layer_id, "coval" if centered else "oval" , _DD_INT_ARG(width), _DD_INT_ARG(height)) + self._sendCommandToDD("coval" if centered else "oval", _DD_INT_ARG(width), _DD_INT_ARG(height)) def arc(self, width: int, height: int, start_angle: int, sweep_angle: int, centered: bool = False): """draw arc; centered or not""" - self.dd._sendCommand(self.layer_id, "carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) + self._sendCommandToDD("carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) def triangle(self, side1: int, angle: int, side2: int): """draw triangle (SAS)""" - self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side1), _DD_INT_ARG(angle), _DD_INT_ARG(side2)) + self._sendCommandToDD("trisas", _DD_INT_ARG(side1), _DD_INT_ARG(angle), _DD_INT_ARG(side2)) def isoscelesTriangle(self, side: int, angle: int): """draw isosceles triangle; given size and angle""" - self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side), _DD_INT_ARG(angle)) + self._sendCommandToDD("trisas", _DD_INT_ARG(side), _DD_INT_ARG(angle)) def rectangle(self, width: int, height: int, centered: bool = False): """draw rectangle; centered or not""" - self.dd._sendCommand(self.layer_id, "crect" if centered else "rect", _DD_INT_ARG(width), _DD_INT_ARG(height)) + self._sendCommandToDD("crect" if centered else "rect", _DD_INT_ARG(width), _DD_INT_ARG(height)) def polygon(self, side: int, vertex_count: int): """draw polygon given side and vertex count""" - self.dd._sendCommand(self.layer_id, "poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) + self._sendCommandToDD("poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) def centeredPolygon(self, radius: int, vertex_count: int, inside: bool = False): """draw polygon enclosed in an imaginary centered circle - given circle radius and vertex count - whether inside the imaginary circle or outside of it""" - self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) + self._sendCommandToDD("cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) def write(self, text: str, draw: bool = False): """write text; draw means draw the text (honor heading)""" - self.dd._sendCommand(self.layer_id, "drawtext" if draw else "write", text) + self._sendCommandToDD("drawtext" if draw else "write", text) + def _sendCommandToDD(self, command: str, *params): + self.dd._sendCommand(self.layer_id, command, *params) class DDLayerTurtleTracked(DDLayerTurtle): # TODO: working on DDLayerTurtleTracked @@ -160,6 +162,8 @@ def xcor(self) -> int: def ycor(self) -> int: self.dd.timeslice() return self._y - def _handleAck(self, x, y): + def _sendCommandToDD(self, command: str, *params): + self.dd._sendCommand(self.layer_id, command, *params, ack_seq="0") # TODO: make use of ack_seq + def _handleAck(self, x, y, ack_seq: str): self._x = x self._y = y From c0299bd04145e7b5067b319026c60adf83498ef2 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 12 Aug 2025 21:31:09 +0800 Subject: [PATCH 08/76] working on ack_seq (2) --- dumbdisplay/ddimpl.py | 7 ++++--- dumbdisplay/ddlayer.py | 2 +- dumbdisplay/ddlayer_turtle.py | 25 ++++++++++++++++--------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 0756758..f007a5e 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -364,7 +364,7 @@ def _sendSpecial(self, special_type: str, special_id: str, special_command: str, self._io.print(special_data) self._io.print('\n') #self.switchDebugLed(False) - def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: str = None): + def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: int = None): self._checkForFeedback() #self.switchDebugLed(True) try: @@ -372,7 +372,7 @@ def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: str = None self._io.print(layer_id) if ack_seq is not None: self._io.print('@') - self._io.print(ack_seq) + self._io.print(str(ack_seq)) self._io.print('.') self._io.print(command) for i in range(0, len(params)): @@ -476,7 +476,8 @@ def _checkForFeedback(self): layer = self._layers.get(lid) if layer is not None: if type == "_": - layer._handleAck(x, y, text) # use text as ack_seq + ack_seq = int(text) + layer._handleAck(x, y, ack_seq) # use text as ack_seq else: layer._handleFeedback(type, x, y) except: diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 1842039..bb43cf1 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -192,7 +192,7 @@ def _handleFeedback(self, type, x, y): self._feedbacks.append((type, x, y)) # self._shipFeedbacks() - def _handleAck(self, x, y): + def _handleAck(self, x, y, ack_seq: int): pass diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 18480fc..7dc37fa 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -153,17 +153,24 @@ def __init__(self, dd, width, height): self._init(dd, width, height, track_move=True) self._x: int = 0 self._y: int = 0 + self._next_ack_seq: int = 0 def pos(self) -> (int, int): - self.dd.timeslice() + while self._pending_ack_seq is not None: + self.dd.timeslice() return (self._x, self._y) - def xcor(self) -> int: - self.dd.timeslice() - return self._x - def ycor(self) -> int: - self.dd.timeslice() - return self._y + # def xcor(self) -> int: + # self.dd.timeslice() + # return self._x + # def ycor(self) -> int: + # self.dd.timeslice() + # return self._y def _sendCommandToDD(self, command: str, *params): - self.dd._sendCommand(self.layer_id, command, *params, ack_seq="0") # TODO: make use of ack_seq - def _handleAck(self, x, y, ack_seq: str): + ack_seq = self._next_ack_seq + self._next_ack_seq = (self._next_ack_seq + 1) % 10 + self._pending_ack_seq = ack_seq + self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq) + def _handleAck(self, x, y, ack_seq: int): + if ack_seq == self._pending_ack_seq: + self._pending_ack_seq = None self._x = x self._y = y From cd25f33e055e4a9b3a2aabd9550684a4ccb60aae Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 12 Aug 2025 22:52:50 +0800 Subject: [PATCH 09/76] working on ack_seq (3) --- _dev_test.py | 22 ++++++++++++++++++---- dumbdisplay/ddimpl.py | 13 ++++++++++++- dumbdisplay/ddlayer_turtle.py | 5 +++-- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 8b90f0b..cb47c81 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -28,12 +28,26 @@ def run_melody(): def test_turtleTracked(): from dumbdisplay.layer_turtle import LayerTurtleTracked dd = create_example_wifi_dd() - l = LayerTurtleTracked(dd, 100, 100) + l = LayerTurtleTracked(dd, 200, 200) l.backgroundColor("ivory") l.border(3, "blue") - l.forward(100) - coor = l.pos() - print(f"* INIT turtle pos: {coor}") + if True: + distance = 5 + angle = 30 + for i in range(0, 50): + l.forward(distance) + l.rightTurn(angle) + distance += 1 + coor = l.pos() + print(f"* LOOP[{i}] turtle pos: {coor}") + dd.sleep(0.2) + else: + l.forward(100) + coor = l.pos() + print(f"* INIT turtle pos: {coor}") + l.forward(100) + coor = l.pos() + print(f"* INIT 2 turtle pos: {coor}") while True: coor = l.pos() print(f"* turtle pos: {coor}") diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index f007a5e..d761c10 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -26,6 +26,17 @@ _VALIDATE_GAP: int = 1000 _RECONNECTING_VALIDATE_GAP: int = 500 +_INIT_ACK_SEQ = 0 + +def _NEXT_ACK_SEQ(ack_seq: int) -> int: + ack_seq = (ack_seq + 1) % 10 + return ack_seq + +def _ACK_SEQ_TO_STR(ack_seq: int) -> str: + return str(ack_seq) + + + class IOProxy: def __init__(self, io): @@ -372,7 +383,7 @@ def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: int = None self._io.print(layer_id) if ack_seq is not None: self._io.print('@') - self._io.print(str(ack_seq)) + self._io.print(_ACK_SEQ_TO_STR(ack_seq)) self._io.print('.') self._io.print(command) for i in range(0, len(params)): diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 7dc37fa..cc96a69 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -1,6 +1,7 @@ #from ._ddlayer import DDLayer from .ddlayer_multilevel import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG +from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ class DDLayerTurtle(DDLayer): '''Turtle-like Layer''' @@ -153,7 +154,7 @@ def __init__(self, dd, width, height): self._init(dd, width, height, track_move=True) self._x: int = 0 self._y: int = 0 - self._next_ack_seq: int = 0 + self._next_ack_seq: int = _INIT_ACK_SEQ def pos(self) -> (int, int): while self._pending_ack_seq is not None: self.dd.timeslice() @@ -166,7 +167,7 @@ def pos(self) -> (int, int): # return self._y def _sendCommandToDD(self, command: str, *params): ack_seq = self._next_ack_seq - self._next_ack_seq = (self._next_ack_seq + 1) % 10 + self._next_ack_seq = _NEXT_ACK_SEQ(self._next_ack_seq) self._pending_ack_seq = ack_seq self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq) def _handleAck(self, x, y, ack_seq: int): From f67d88ef87764cbda816d99fdc92754408d9f114 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 13 Aug 2025 19:08:15 +0800 Subject: [PATCH 10/76] working on ack_seq (4) --- _dev_test.py | 19 ++++++++++++++----- dumbdisplay/ddlayer_turtle.py | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index cb47c81..6b2db32 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -28,17 +28,26 @@ def run_melody(): def test_turtleTracked(): from dumbdisplay.layer_turtle import LayerTurtleTracked dd = create_example_wifi_dd() - l = LayerTurtleTracked(dd, 200, 200) + l = LayerTurtleTracked(dd, 2000, 2000) l.backgroundColor("ivory") l.border(3, "blue") if True: - distance = 5 - angle = 30 - for i in range(0, 50): + sync = True + distance = 1 + angle = 10 + for i in range(0, 180): + l.penColor("red") + l.penSize(20) l.forward(distance) l.rightTurn(angle) distance += 1 - coor = l.pos() + coor = l.pos(sync=sync) + if True: + l.goTo(0, 0, with_pen=False) + l.penColor("blue") + l.penSize(10) + l.circle(15 * i, centered=True) + l.goTo(coor[0], coor[1], with_pen=False) print(f"* LOOP[{i}] turtle pos: {coor}") dd.sleep(0.2) else: diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index cc96a69..fa8e4dc 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -155,9 +155,11 @@ def __init__(self, dd, width, height): self._x: int = 0 self._y: int = 0 self._next_ack_seq: int = _INIT_ACK_SEQ - def pos(self) -> (int, int): + def pos(self, sync: bool = True) -> (int, int): while self._pending_ack_seq is not None: self.dd.timeslice() + if not sync or not self.dd._connected: + break return (self._x, self._y) # def xcor(self) -> int: # self.dd.timeslice() From 68f58659e8cd1dc30238cb5d502a51da49e2b3e0 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 13 Aug 2025 19:29:19 +0800 Subject: [PATCH 11/76] working on ack_seq (5) --- dumbdisplay/ddimpl.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index d761c10..7140898 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -29,11 +29,35 @@ _INIT_ACK_SEQ = 0 def _NEXT_ACK_SEQ(ack_seq: int) -> int: - ack_seq = (ack_seq + 1) % 10 + if True: + ack_seq = (ack_seq + 1) % 62 + else: + ack_seq = (ack_seq + 1) % 10 return ack_seq -def _ACK_SEQ_TO_STR(ack_seq: int) -> str: - return str(ack_seq) +def _ACK_SEQ_TO_ACK_STR(ack_seq: int) -> str: + if True: + if ack_seq < 10: + return str(ack_seq) + elif ack_seq < 36: + return chr(ord('A') + ack_seq - 10) + else: + return chr(ord('a') + ack_seq - 36) + else: + return str(ack_seq) + +def _ACK_STR_TO_ACK_SEQ(ack_str: str) -> int: + if True: + if ack_str >= '0' and ack_str <= '9': + return int(ack_str) + elif ack_str >= 'A' and ack_str <= 'Z': + return ord(ack_str) - ord('A') + 10 + elif ack_str >= 'a' and ack_str <= 'z': + return ord(ack_str) - ord('a') + 36 + else: + raise ValueError("Invalid ACK string: " + ack_str) + else: + return int(ack_str) @@ -383,7 +407,7 @@ def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: int = None self._io.print(layer_id) if ack_seq is not None: self._io.print('@') - self._io.print(_ACK_SEQ_TO_STR(ack_seq)) + self._io.print(_ACK_SEQ_TO_ACK_STR(ack_seq)) self._io.print('.') self._io.print(command) for i in range(0, len(params)): @@ -487,7 +511,7 @@ def _checkForFeedback(self): layer = self._layers.get(lid) if layer is not None: if type == "_": - ack_seq = int(text) + ack_seq = _ACK_STR_TO_ACK_SEQ(text) layer._handleAck(x, y, ack_seq) # use text as ack_seq else: layer._handleFeedback(type, x, y) From b3a121e301391214b6b92a97eaf5664c988a9754 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 13 Aug 2025 20:25:19 +0800 Subject: [PATCH 12/76] working on ack_seq (6) --- _dev_test.py | 51 ++++++++++++++++++++++++++++++++--- dumbdisplay/ddimpl.py | 5 ++++ dumbdisplay/ddlayer_turtle.py | 2 +- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 6b2db32..f858ac5 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -2,7 +2,7 @@ import time import math - +from dumbdisplay.dumbdisplay import DumbDisplay from dumbdisplay_examples.utils import create_example_wifi_dd @@ -49,7 +49,8 @@ def test_turtleTracked(): l.circle(15 * i, centered=True) l.goTo(coor[0], coor[1], with_pen=False) print(f"* LOOP[{i}] turtle pos: {coor}") - dd.sleep(0.2) + if not sync: + dd.sleep(0.1) else: l.forward(100) coor = l.pos() @@ -62,6 +63,50 @@ def test_turtleTracked(): print(f"* turtle pos: {coor}") dd.sleep(2) +def test_passive_turtleTracked(sync: bool = True): + from dumbdisplay.layer_turtle import LayerTurtleTracked + def _setup(dd: DumbDisplay) -> LayerTurtleTracked: + l = LayerTurtleTracked(dd, 2000, 2000) + l.backgroundColor("ivory") + l.border(3, "blue") + return l + def _loop(l: LayerTurtleTracked, i: int, distance: int): + l.penColor("red") + l.penSize(20) + l.forward(distance) + l.rightTurn(10) + coor = l.pos(sync=sync) + if True: + l.goTo(0, 0, with_pen=False) + l.penColor("blue") + l.penSize(10) + l.circle(15 * i, centered=True) + l.goTo(coor[0], coor[1], with_pen=False) + print(f"* LOOP[{i}] turtle pos: {coor}") + if not sync: + l.dd.sleep(0.2) + dd = create_example_wifi_dd() + distance = 1 + i = 0 + l: LayerTurtleTracked = None + while True: + (connected, reconnecting) = dd.connectPassive() + if connected: + if l is None: + l = _setup(dd) + distance = 1 + i = 0 + else: + if reconnecting: + dd.masterReset() + l = None + else: + _loop(l, i=i, distance=distance) + distance = distance + 1 + i = i + 1 + + + def test_margin(): from dumbdisplay.layer_ledgrid import LayerLedGrid @@ -104,7 +149,7 @@ def test_find_packages(): if __name__ == "__main__": - test_turtleTracked() + test_passive_turtleTracked() # run_debug() # run_doodle() diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 7140898..6ef6566 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -424,6 +424,11 @@ def _sendCommand(self, layer_id: str, command: str, *params, ack_seq: int = None #self.switchDebugLed(False) + def _is_reconnecting(self) -> bool: + if self._connected_iop is not None: + return self._connected_iop.reconnecting + else: + return False def _checkForFeedback(self): feedback = self._readFeedback() diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index fa8e4dc..ff65d31 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -158,7 +158,7 @@ def __init__(self, dd, width, height): def pos(self, sync: bool = True) -> (int, int): while self._pending_ack_seq is not None: self.dd.timeslice() - if not sync or not self.dd._connected: + if not sync or self.dd._is_reconnecting(): break return (self._x, self._y) # def xcor(self) -> int: From 7d76c3c45a6bb3988ecc2af9f14518747e88b87b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 13 Aug 2025 23:33:55 +0800 Subject: [PATCH 13/76] updated --- _dev_test.py | 36 ++++++++++++++++++++++++++++------- dumbdisplay/ddlayer_turtle.py | 34 ++++++++++++++++----------------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index f858ac5..3abd281 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -71,20 +71,42 @@ def _setup(dd: DumbDisplay) -> LayerTurtleTracked: l.border(3, "blue") return l def _loop(l: LayerTurtleTracked, i: int, distance: int): + if i > 300: + coor = l.pos(sync=sync) + print(f"* ENDED turtle pos: {coor}") + l.dd.sleep(2) + return l.penColor("red") l.penSize(20) l.forward(distance) l.rightTurn(10) - coor = l.pos(sync=sync) if True: + r = 40 + random.random() * 20 + shape = i % 5 + centered = random.random() < 0.5 + l.penColor("green") + l.penSize(10) + if shape == 0: + l.rectangle(r, r, centered=centered) + elif shape == 1: + l.centeredPolygon(r, 6, inside=centered) + elif shape == 2: + l.polygon(r, 5) + elif shape == 3: + l.arc(r, r, r, r, centered=centered) + else: + l.circle(r, centered=centered) + coor = l.pos(sync=sync) + if i % 2 == 0: l.goTo(0, 0, with_pen=False) l.penColor("blue") - l.penSize(10) - l.circle(15 * i, centered=True) - l.goTo(coor[0], coor[1], with_pen=False) + l.penSize(5) + r = 5 * i + l.circle(r, centered=True) + l.goTo(coor[0], coor[1], with_pen=False) print(f"* LOOP[{i}] turtle pos: {coor}") - if not sync: - l.dd.sleep(0.2) + # if not sync: + # l.dd.sleep(0.2) dd = create_example_wifi_dd() distance = 1 i = 0 @@ -149,7 +171,7 @@ def test_find_packages(): if __name__ == "__main__": - test_passive_turtleTracked() + test_passive_turtleTracked(sync=True) # run_debug() # run_doodle() diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index ff65d31..68fedd7 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -22,12 +22,12 @@ def forward(self, distance: int, with_pen: bool = True): ''' forward; with pen or not ''' - self._sendCommandToDD("fd" if with_pen else "jfd", _DD_INT_ARG(distance)) + self._sendCommandTracked("fd" if with_pen else "jfd", _DD_INT_ARG(distance)) def backward(self, distance: int, with_pen: bool = True): ''' backward; with pen or not ''' - self._sendCommandToDD("bk" if with_pen else "jbk", _DD_INT_ARG(distance)) + self._sendCommandTracked("bk" if with_pen else "jbk", _DD_INT_ARG(distance)) def leftTurn(self, angle: int): ''' left turn @@ -42,17 +42,17 @@ def home(self, with_pen: bool = True): ''' go home (0, 0); with pen or not ''' - self._sendCommandToDD("home" if with_pen else "jhome") + self._sendCommandTracked("home" if with_pen else "jhome") def goTo(self, x: int, y: int, with_pen: bool = True): ''' go to (x, y); with pen or not ''' - self._sendCommandToDD("goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) + self._sendCommandTracked("goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) def goBy(self, by_x: int, by_y: int, with_pen: bool = True): ''' go by (by_x, by_y); with pen or not ''' - self._sendCommandToDD("goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) + self._sendCommandTracked("goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) def setHeading(self, angle: int): ''' set heading angle (degree) @@ -109,37 +109,37 @@ def endFill(self): self.dd._sendCommand(self.layer_id, "end_fill") def dot(self, size: int, color: str): '''draw a dot''' - self._sendCommandToDD("dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) + self.dd._sendCommand(self.layer_id, "dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) def circle(self, radius: int, centered: bool = False): """draw circle; centered or not""" - self._sendCommandToDD("ccircle" if centered else "circle", _DD_INT_ARG(radius)) + self.dd._sendCommand(self.layer_id, "ccircle" if centered else "circle", _DD_INT_ARG(radius)) def oval(self, width: int, height: int, centered: bool = False): """draw oval; centered or not""" - self._sendCommandToDD("coval" if centered else "oval", _DD_INT_ARG(width), _DD_INT_ARG(height)) + self.dd._sendCommand(self.layer_id, "coval" if centered else "oval", _DD_INT_ARG(width), _DD_INT_ARG(height)) def arc(self, width: int, height: int, start_angle: int, sweep_angle: int, centered: bool = False): """draw arc; centered or not""" - self._sendCommandToDD("carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) + self.dd._sendCommand(self.layer_id, "carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) def triangle(self, side1: int, angle: int, side2: int): """draw triangle (SAS)""" - self._sendCommandToDD("trisas", _DD_INT_ARG(side1), _DD_INT_ARG(angle), _DD_INT_ARG(side2)) + self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side1), _DD_INT_ARG(angle), _DD_INT_ARG(side2)) def isoscelesTriangle(self, side: int, angle: int): """draw isosceles triangle; given size and angle""" - self._sendCommandToDD("trisas", _DD_INT_ARG(side), _DD_INT_ARG(angle)) + self.dd._sendCommand(self.layer_id, "trisas", _DD_INT_ARG(side), _DD_INT_ARG(angle)) def rectangle(self, width: int, height: int, centered: bool = False): """draw rectangle; centered or not""" - self._sendCommandToDD("crect" if centered else "rect", _DD_INT_ARG(width), _DD_INT_ARG(height)) + self.dd._sendCommand(self.layer_id, "crect" if centered else "rect", _DD_INT_ARG(width), _DD_INT_ARG(height)) def polygon(self, side: int, vertex_count: int): """draw polygon given side and vertex count""" - self._sendCommandToDD("poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) + self.dd._sendCommand(self.layer_id, "poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) def centeredPolygon(self, radius: int, vertex_count: int, inside: bool = False): """draw polygon enclosed in an imaginary centered circle - given circle radius and vertex count - whether inside the imaginary circle or outside of it""" - self._sendCommandToDD("cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) + self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) def write(self, text: str, draw: bool = False): """write text; draw means draw the text (honor heading)""" - self._sendCommandToDD("drawtext" if draw else "write", text) - def _sendCommandToDD(self, command: str, *params): + self._sendCommandTracked("drawtext" if draw else "write", text) + def _sendCommandTracked(self, command: str, *params): self.dd._sendCommand(self.layer_id, command, *params) @@ -167,7 +167,7 @@ def pos(self, sync: bool = True) -> (int, int): # def ycor(self) -> int: # self.dd.timeslice() # return self._y - def _sendCommandToDD(self, command: str, *params): + def _sendCommandTracked(self, command: str, *params): ack_seq = self._next_ack_seq self._next_ack_seq = _NEXT_ACK_SEQ(self._next_ack_seq) self._pending_ack_seq = ack_seq From 2345c666d6b53993f8e83ceccb733ca0e601eeb6 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Thu, 14 Aug 2025 08:02:51 +0800 Subject: [PATCH 14/76] updated --- _dev_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 3abd281..2a71e9c 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -81,8 +81,8 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): l.forward(distance) l.rightTurn(10) if True: - r = 40 + random.random() * 20 - shape = i % 5 + r = 40 + int(random.random() * 20) + shape = i % 6 centered = random.random() < 0.5 l.penColor("green") l.penSize(10) @@ -94,6 +94,8 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): l.polygon(r, 5) elif shape == 3: l.arc(r, r, r, r, centered=centered) + elif shape == 4: + l.dot(r, color="darkblue") else: l.circle(r, centered=centered) coor = l.pos(sync=sync) From f3970a1ce5da75f6856cb06f65e16784659cd6d7 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Thu, 14 Aug 2025 21:04:08 +0800 Subject: [PATCH 15/76] updated --- _dev_test.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 2a71e9c..62bb96b 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -1,7 +1,4 @@ import random -import time -import math - from dumbdisplay.dumbdisplay import DumbDisplay from dumbdisplay_examples.utils import create_example_wifi_dd @@ -81,23 +78,28 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): l.forward(distance) l.rightTurn(10) if True: - r = 40 + int(random.random() * 20) - shape = i % 6 + r = 50 + int(random.random() * 30) + r2 = r + 10 + int(random.random() * 20) + a = int(random.random() * 45) + a2 = a + 10 + int(random.random() * 45) centered = random.random() < 0.5 l.penColor("green") l.penSize(10) + shape = i % 7 if shape == 0: - l.rectangle(r, r, centered=centered) + l.rectangle(r, r2, centered=centered) elif shape == 1: - l.centeredPolygon(r, 6, inside=centered) + l.centeredPolygon(r, 5, inside=centered) elif shape == 2: l.polygon(r, 5) elif shape == 3: - l.arc(r, r, r, r, centered=centered) + l.arc(r, r2, a, a2, centered=centered) elif shape == 4: - l.dot(r, color="darkblue") - else: l.circle(r, centered=centered) + elif shape == 5: + l.isoscelesTriangle(r, a) + else: + l.dot(r, color="darkblue") coor = l.pos(sync=sync) if i % 2 == 0: l.goTo(0, 0, with_pen=False) From 4a60acb0fbde964b58a05823708525dbf3381401 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Thu, 14 Aug 2025 22:00:03 +0800 Subject: [PATCH 16/76] updated --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1c63cf1..89ca057 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /.vscode /venv +__pycache__ + /MicroPythonDumbDisplay.iml /MicroPythonDumbDisplay.code-workspace From b0045e53f7c087623dfc597f6bbc7f73f64a5976 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 10:42:47 +0800 Subject: [PATCH 17/76] updated --- README.md | 39 +++++++++++++++++++++++----------- dd_demo.py | 17 ++++----------- dumbdisplay/ddimpl.py | 6 +++--- dumbdisplay/ddlayer.py | 2 +- dumbdisplay/ddlayer_turtle.py | 6 ++++-- screenshots/layer_turtle.png | Bin 0 -> 21101 bytes 6 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 screenshots/layer_turtle.png diff --git a/README.md b/README.md index 09a6958..8e83a74 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ Other then the `DumbDisplay` object, you will need to create one or more layer o |:--:| || -- `LayerGraphical` -- a graphical LCD that you can draw to with common drawing directives +- `LayerGraphical` -- a graphical LCD that you can draw to with common TFT LCD drawing directives ``` from dumbdisplay.core import * from dumbdisplay.layer_graphical import * @@ -148,31 +148,46 @@ Other then the `DumbDisplay` object, you will need to create one or more layer o |:--:| || -- `LayerSelection` -- a row / column / grid of TEXT based LCDs mostly for showing selection choices +- `Layer7SegmentRow` -- a single 7-segment digit, or a row of **n** 7-segments digits ``` from dumbdisplay.core import * - from dumbdisplay.layer_selection import * + from dumbdisplay.layer_7segrow import * dd = DumbDisplay() - l = LayerSelection(dd) + l = Layer7SegmentRow(dd) + ``` + + example: + |[`demo_Layer7SegmentRow()` in `dd_demo.py`](dd_demo.py)| + |:--:| + || +- +- `LayerTurtle` -- a Python Turtle like canvas that you can draw to using Python Turtle like commands + ``` + from dumbdisplay.core import * + from dumbdisplay.layer_turtle import * + dd = DumbDisplay() + l = LayerTurtle(dd) ``` example: - |[`demo_LayerSelection()` in `dd_demo.py`](dd_demo.py)| + |[`demo_LayerTurtle()` in `dd_demo.py`](dd_demo.py)| |:--:| - || + || -- `Layer7SegmentRow` -- a single 7-segment digit, or a row of **n** 7-segments digits + +- `LayerSelection` -- a row / column / grid of TEXT based LCDs mostly for showing selection choices ``` from dumbdisplay.core import * - from dumbdisplay.layer_7segrow import * + from dumbdisplay.layer_selection import * dd = DumbDisplay() - l = Layer7SegmentRow(dd) + l = LayerSelection(dd) ``` - + example: - |[`demo_Layer7SegmentRow()` in `dd_demo.py`](dd_demo.py)| + |[`demo_LayerSelection()` in `dd_demo.py`](dd_demo.py)| |:--:| - || + || + - `LayerPlotter` -- a "plotter" for plotting real-time data ``` diff --git a/dd_demo.py b/dd_demo.py index 92290c5..40004f2 100644 --- a/dd_demo.py +++ b/dd_demo.py @@ -77,7 +77,7 @@ def demo_LayerGraphical(): # Create a LayerGraphical layer and set it up, like background color and border # The LayerGraphical layer is 150 pixels wide and 100 pixels high # Note that this size is not the size on the DumbDisplay app's canvas - # All layers will be scaled to fit the DumbDisplay app's canvas, keeping the aspect ratio. + # All layers will be scaled to fit the DumbDisplay app's canvas, keeping the aspect ratio l = LayerGraphical(dd, 150, 100) l.backgroundColor("azure") l.border(3, "blue") @@ -177,10 +177,10 @@ def demo_LayerTurtle(): # Create a DumbDisplay instance dd = _create_demo_dd() - # Create a LayerGraphical layer and set it up, like background color and border - # The LayerGraphical layer is 150 pixels wide and 100 pixels high + # Create a LayerTurtle layer and set it up, like background color and border + # The LayerTurtle layer is 400 pixels wide and 250 pixels high, with (0, 0) at the center # Note that this size is not the size on the DumbDisplay app's canvas - # All layers will be scaled to fit the DumbDisplay app's canvas, keeping the aspect ratio. + # All layers will be scaled to fit the DumbDisplay app's canvas, keeping the aspect ratio l = LayerTurtle(dd, 400, 250) l.backgroundColor("ivory") l.border(3, "blue") @@ -192,15 +192,6 @@ def demo_LayerTurtle(): l.rightTurn(angle) distance += 1 - # # Draw on the canvas of the LayerGraphical instance - # for i in range(0, 15): - # delta = 3 * i - # x = delta - # y = delta - # w = 150 - 2 * x - # h = 100 - 2 * y - # l.drawRect(x, y, w, h, "plum") - while True: dd.timeslice() diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 6ef6566..345d2fb 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -12,7 +12,8 @@ #_DD_LIB_COMPATIBILITY = 2 #_DD_LIB_COMPATIBILITY = 7 # for :drag #_DD_LIB_COMPATIBILITY = 8 # for feedback type -_DD_LIB_COMPATIBILITY = 9 # joy stick valuerange (not used) +#_DD_LIB_COMPATIBILITY = 9 # joy stick valuerange (not used) +_DD_LIB_COMPATIBILITY = 14 # bring forward since v0.5.1 #_DD_SID = 'MicroPython-c2' _DD_SID = f"MicroPython-c{_DD_LIB_COMPATIBILITY}" @@ -516,8 +517,7 @@ def _checkForFeedback(self): layer = self._layers.get(lid) if layer is not None: if type == "_": - ack_seq = _ACK_STR_TO_ACK_SEQ(text) - layer._handleAck(x, y, ack_seq) # use text as ack_seq + layer._handleAck(x, y, text) else: layer._handleFeedback(type, x, y) except: diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index bb43cf1..f7bf6d2 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -192,7 +192,7 @@ def _handleFeedback(self, type, x, y): self._feedbacks.append((type, x, y)) # self._shipFeedbacks() - def _handleAck(self, x, y, ack_seq: int): + def _handleAck(self, x, y, text: str): pass diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 68fedd7..5a56ba2 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -1,7 +1,8 @@ #from ._ddlayer import DDLayer from .ddlayer_multilevel import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG -from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ +from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ, _ACK_STR_TO_ACK_SEQ + class DDLayerTurtle(DDLayer): '''Turtle-like Layer''' @@ -172,7 +173,8 @@ def _sendCommandTracked(self, command: str, *params): self._next_ack_seq = _NEXT_ACK_SEQ(self._next_ack_seq) self._pending_ack_seq = ack_seq self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq) - def _handleAck(self, x, y, ack_seq: int): + def _handleAck(self, x, y, text: str): + ack_seq = _ACK_STR_TO_ACK_SEQ(text) if ack_seq == self._pending_ack_seq: self._pending_ack_seq = None self._x = x diff --git a/screenshots/layer_turtle.png b/screenshots/layer_turtle.png new file mode 100644 index 0000000000000000000000000000000000000000..188a553569828829a65561fba4dc6889ff63ebb3 GIT binary patch literal 21101 zcmeF2-Q7L7yX)Yy^RE02d-k0D zFf-@c^u2wntE=kwbfl`X%x4rr6c`wo&vLSo>M$^{3DA!}G9q-31P2ZD8wS=zT}B+H zYLfUEy79q6Oi2s|rZx`s^*aJ|`=g_*o(l{NTHk*k*dd2fGZ+|kI5|l%O;4lqbq`S+LrTpStT zswR&rPbnFSA%0|X)g=Z!_*nhFHmL#R(7ik#F`?(T%0)nrOe={?LVxW4*M&(KnBvI! znV}IF;>hd&{}N~i{@?Af{w~$n=WKO#)dFvOP6AOn2rBf&6~JjBq?Nx~6xZiTE_70{ zYz9sIk&j0P5Yp25%ajy=Q1DPCWzu0hTseP93o8L!CU6r2jkZ4-B>F_RcSMJ%|*gdz%2jSD$>k zO$GL}OYSQ*b&~(vE$bJOQjmMV2sR+p(h&!NSQ$H)DUO?cmeU|< zqy4!7on%oN^jUXAN}q?h{-3m0w`3F$3J7w3FY9N-qvjwR5z#vEO&3?So~7b`i$U_` zZ|joMHz|O%z<=)%fi*MVBNuKlAW1ZhdG+yriCeewF9W4D09W->;1?M=*s#rj@a2PP zxMDO7Edu}`6!;H5WEt*X5g)mA*kATAj!D_?r;EyW&AcHEHU%}S>s`>H>=ysg#|TX- z)1UmM4j0G2DM&ROzJW9}qR*P@^{;}Js~1`C6hF49?uV@_hyuVmIW(e_W;`&VH@C}T z>fFO5I+psWY(z549fjaTY^tIlZ{>W?;V~hZW)O=6SMGMe!NhLvqa)l(iFO1bbeNp* zf}3SV$V6N`m&5--o-?oJlqpVHgCyOB3{M}*R2cW{Rzzoz#%TW~iTCSSVEL_oC0}tv z90eWi+`JkLeKRRo-)pa_o*e`-Z-(_hz}%nSB1f&8;Q(H63|d z&L?ZSEzk=PbU9Tlnd?HTlp7G4%8}Feu)zOt-}SRXMY!9%&IbM}r(2auu>T_!fZQ5= z;5H6U9=WmI7j42n!7wD;@hDCt0?3=&=)l;Y5&t2%bqECqtSjE3`km`clnzk2GQE?8 zZuLB_IlJgPve?rU-*BasWv<*!4oj}g`w>%ZjG$+~VDA#hd)13-*4`M{oP+Ojg31wj zs|#$eR_wbC#2^Svoi z1izfPWR~NFv(*Rj8uLF%H?O~JTf8w4OMWHsI8c6{578g%;~?Eyx+zt;pKD`|nzC+P zJFP0^%)P-O30M=|eju^!vWBtxwb7lER_*3AGNd;z=(QAyttbZg@a_5gEHHB{6|<1` zUq|@lm`K4wawGwxcJ33_XG@r6kJ3W%(|%>ya&A|Db8fp0-R_=C?wZpaAGrXq(ivaN ziW$^GW1)t@m-3T&yc8`g_;T?tB!DNW;n8nJSU5w&$S>upipDDZ+`F|(@J;A*=p{&7 z-wr9NM|AHA-+8f@$I|=7&TPsva=q`OXs?8<#)(eL6&?i>28*2hN)W!k)2EAm#Y#?`R#|EPN+U@&ll?OnAP3KZk@Ec@-}nkg zn$CS_D5%F8u)XnORckE#Kmv((x~(+pyhW_S%A8U&m?gvvq5+Txi_V;+s1VTNX|~B< z$8FZ!Zp(}S|0eGtU6w2;xYRaUBP2hr0bglw|7s)1N1pj&F)dXL@|*A;Pj!fnhM=px z9m>l{dC%Cb~?dRcba}E*Ljb(0%?sbMP;VVy#)A- z45^gSe>|hb!;TDb_;VXa6w!L++Zgj@s4BlnF#9#TC{4muO$VRVdCCpi}3q5Q7* zom`{~s>?$38gmPqbptJgB#a@bbGg$Hgy^anqBO$4{PR9#oh2nza$Rk7O&7{M%CsLK zFiC~s+t%!k<)e>k{@%xD^}ty19kU3%BV-*c5?c86lHhP>_5rUbg&g!1Ko05&9fJ)! z?K0k+;7OW=5Mou4h_1#ES-ww8R`o|7UhJqVBI+w=%HRC?9GdM;-&WP0(Ge#U zTsZHNYP>m{*9`0f-AHkILZc=c?1wW&XS^<{=Y^2Gym;v(>UdwUUueCxhCi3^NZd+c zuT8$2liP{>m<%b6(@`bP`#Vh;x{%@pey-`%Sh?V-p^>5*!beOeVw}n`iqPQ8F4My@ z3&*3ReNMx!pLk>elHy&k7pdX&X)cv1^!1d2BF!p%o0dl;uJ3oISk+{*D@7&$yx#|K z9F_|jPl$3ga7M(KIpqrR)68d1}Mq?X~M1b zMp!4nj)IfOc`E6Xg04vF!xIxE*V(TbVKm7KE~l=G(sc!oRXfJOxHU#61guNIA!mQm#1( zEK)Ny>{Nuw#i%zdhCI1>tF`V8IR96VNrjM2#w%XmewrE z?j`d#zC`74DlVEn8#s(SU65fQd?>_0_Xw)^0$P+;hoy+U>oeLUP z7Jk?%KFnt^_uT1&e7g7)tBQlKWFkbdlKnAg!SWg z|4O{C=6TO&e%hl68H`kjmG|EP9=AW-jlB7uSSE{>YgR3PY+IBxAoam>op~u`M$RMTfb3KEqdQ)h<<;aou3^#YlNxZ=H2uC}cZSsCKu|THuu#KUU&+@AZ)aNhd%|+@4g8 z;*x5o_m}FY(MYRw;*^hB?osY%##4KA1TM~z9T)sN)g6Q`h0WOWBs9TF`s4)$#Yqod z_8O|^R7I0nO0MABqgG?s18uu{5{+E)gJprro$tXaVs7b(Pb39yV=SKVdqN>rzS*W`NKJG&Q?N6R5^iJKtg#dC(Vw3;%!v zg{CIv_IJ01@8-4CrB_10w?nu(m@7pe&v|^6RtGMPqhn`(3@+W>e#X)AxhU2pXt&{YLQVSJ3tn}j0Y}&T-=1)bAESpGO^=DyZS8lgdY?a zR7GxW-kwyF__P=sov2D=UeO0}Lh{FHz}nLwq!GxIi~$vumda~+nM68+t+mSI6G`#m zOIInFQu2fGUiUCk4Ib|7$%c<|H)*lTn8Zx)hdLks(IEJ<&I*>ci+2`zxz7p2O0O&Mf#b^_HRkPbs$ilsw1S} zC%aM>)Z2beErBDT+sG8ZXe8-hckIyq4FaL{6g@J|1)c;aiujvPX(TOk9>|eHU3!{I zDyG|p*Gh#|=G40NbIW2NieaodEdbCD^!{R};~Q1G`o-3F3xGD^8C#GoP#;%wXll^6 zDz3)BJln>y_2M1%tSnyCb8|+YA?srY%hvQ6@Ng2e^1YU8!o{-e^Tzm5u)WGcA&sU(E)R1Wbgx&Yjc5dt zJqNjJ=MSK^=HSULg$Xcq@kt1;WCsOcHcxNF;2jWaPBv3?xzELcrf!9_Fz04ov{j;H zdpwi*#&I}(vTI)N<5Bw(Zzw<3_D|5Q%xIA&Hw9Gh4{H2utDC`7GJ(%v1s)|UT(jzo7u4g(zuj2tbw)sk`%G~au9&l@x7=@Gw%{!PX~Gg!_Q zN&{0==y9~Nc%>C3P<*sno4+gymH#Z<)Y^;iucpyj>6Fcs_q1c6IqKK)%&9uNk++&a z)R+TxOH-TNeH=c(H3`EiUJ=Iul6lv50k{|~XZ@PHQOF$fvoQ%>+ZnRN&lsZf!JIB#P>=y4?0y;xwlP>3Z)EVyK@(&yAx17FBp&$8{JnVe9aqR=p$rqg=BTTZ=Io-9R;BO zv*U~Iz*Z4%g&9$p2ik$uYy>&u()Qux%J!C5$G6`ZMg{yECR;L)IpLUd+~0pfXux=u zi>~Cc zrjx(QCp9(PxMq-5CqQpu4hPhRlL7y-6|=4EGnHY*yHaBOm9l3eCH zPVAYYY7ue7F3n|zw+ez{vRP3=gE^|5QBD{iyS9LztQ$%z#4m=|*?HMdDxg6Ys{s*y0OmK{#xs(Pwy*LsFu>Fu@iu~U?etHpqn_gLDgIy5I!1Dqrw{sR zIg@szIC#Xx182{hO8xikzeCl~EvIV#7!j%A6Q7R$#3H}X2`gaHak&D)a<13pT-8mY zuC-%0p#7$MzU6U8?+lA&&@ooB$80X_1WiQMeT`e2N=p*5~G$mRF>{lz0)~#{Z%6mFpQm{#jaIDf`2qXzq!jn3k6NCjTccp0tNCky(56J@(ka zxa6|T#QV{5iD|;6N#MuR0QM;nP|V;eTn|A{68z5SCGy(+kQwf{H;3-T zXMn4cs#`dvg)ksHC-76f(E=R_2s{ECgqCK6%66C)F`ir~`TjJY7&EpsC?eSoi; z761EV0&w>;NEa*P!}l*TyDdMPK5D{yi8z=IJvtF*+_GQ4RTsPw{fgkdC9XUjg~r-k z`?@X~nlvbA1uU0i$G|E;>uP$DVnAxPic+!V7*PMZs^_%CCpQ<>#sE9fp*g1=oEk_k zV-*=d15jsqbiK6nIX+g29oTWkbeajfzzg0W_nv?GBJ1<|kx?mQ*5vFTp>>$&%bKxWjoZh8k+B zzu*hZBDAi|+4=dK^gZ0i&c&Qu__FB!)t`A;?)2fCzA~pjuy>9qB~SQqE9t$WQWJt> zGPhPgQ^pV&2~VFh84Qg;{OK5rQGRUtq#^vR6m4ghJNi zgo5b!qZX?t3I`zeb5)_b-_V%es|^B4G!z*tFTP0K@^Lt&q$WOhOPDcvrpzV_>Bkc@ zCD*UM@ub+(NAU&!d3({g8q7>S2OH4Mya@naq2}uL-XP`Xymig?+nz*xZ=mDtVfYR5 z*mcvA3ukNsP10nK{65Ycx_O}M`t&iz`;tu|>yGGk{Is)hI~>+IJz{^MN<8e4md)he z-zClA*Eu}N_Gza)sh_jD9sE|G#h>ts@av!Vck~P%@7GA2nVdoOItR6O z(7M6-dbyj^ldmoAX4+TP>M#_7NUuFWaxS;V;{u4jbs^Ld+CF-FWuQ7*rIAE$b1l>| ztz2;Ht@YOnKui)^=X>q!xzZL{%8zm-DIR26;A;jo7>;5(AKuqzK{UnRlClPjl|~t2 zJ24WhL;aS}(1I=tblHfkDOM@hIo0w>LESX*=jW33yRv2jWpy&2tZ|RcZoQqB=H$V) zbNDCnf04rHw&LDE@n=(&t-ooAkJnOu0Dw9f?L}~#!>neniAltXS8dxbIe?=Mlja)s z%L6n)Gt#D>pjJVieGKX8XsV1wS!XHY z85sgz#ibhm({#IKuQ%4s7$MRB#$GJR9z@+zeJ&ij-LJlSxfuOfW|F9({T0v)P0@unGQ;WNF43-;^4}hzFh2Y5jpJ8kALibk(=kiB6rI!2t3*9enpTp z+6b3^tNOI|5wE9&Fn#3h9m&A%TN3LLq1}T=peJfhO2rahy#MKWed{xQYx^zUb<<|p zj5YEV|AJPrZUKcA{|#V;$v^XlMb>1#O%=udqJSeGG42o(oc5_*sGC5_-wN9Y#dPhu zTOG;luK@K!y^(iY1cY*(B;O^HybsSGel;V_=`hpS4Jz`Fr-${}em-Nzfq}33ORnBz zU-%@W5P9iEC;dzBwDrc|q9I5F*RCzi1O>hDx;v=;^#_x@37ije2i&)npg`QGn~6#j zHKVJo)W_WDblDoz zYsb-CkXmfJ#9E|HjX-62IBlIB6h2uakje9;)_bn%LUqJ+e&(Z$BSAGUHuJl+!$mLx zKGXY;MV&3fn^u#a1)P31XiU60EV$g35+Vnvm*|ZEA)l$dp6Y*JnZ>vIGVa-LDyPnf z-Y~cX!by(v2^u2AD)M1`~6u&xyBL6b({T$e%dOZ)_lK-hbB%$ zgH1*#J^45y#t&Z|63@+q9N>r(?*AyZnyy-3H7LrWFi!qxQvwq$>hLy1D6d%ak_q3A8*n$3ecR<0_FrV8L&g|>Vp0ykk8@1x384@*36QTxf_Zr9K z`CTgf^XW5|2mg`%hVwlQ^_-n2XK*lrJaS3YI|yAkdlKV3&7qg@HG+b;NOh~BUKkE)|4dp>M@0r%(v8fFaw0ff)KAkCb<)SM30`Q{q#?Q>ZmWB_wvxowfcI znaJ@BmgZT&4oE?e#YuNm8o})v|}aIj)PDuo4WvJ>k3C{S&P_n2>Eh zAQV>-Kqy=$3?;uPQEv>d@wn^zZBk%)Gu%hOxYGEU06|r@!wPva7;eS)p|Oegi>@Wb zB>=Uhk0*u1bbppIK)nsAK6uaH-TYfsSi~?UQqA_2+5zSzHxXPvqRv9pEGL4EwnJt-De3;5~ z%JxTb^4O@t9TeqIyIRnHw55t+>0}LrEwGD|&m0IXAD8Y?5%~*wXd5wl1~BDj$hOhY zV9a(>M2#F_C#PIggrJ5DD0-$PS#rGbWvm5|ExR22y$nL4dq2h3EGajsy)S+iMra@m zp9B#oYG(*O(}sz>Np92RY7xVL4S$kubpOTnn|b=}|CSQ$v03h^TWiS*?O0-jd1K-M zvjVdrATg;xES`#B5MSp<>+ZNz^=_l_ocm3T_miAR7k<5nm5d=dd`Q@Pl$T7p_WXX=F60m6p+YO-H+JYou4km0q|FW zrnTZNxO9wp$oN`F$v-a*h>I`p$t%b68ke+G6*P_Ej@}%5=H)^+F-kAu7ZD!SSg3ma z!_T%~=o#iAsWtIwcknAo?CIC%Sq4@b8keoe+KVf+S0b5t6D)E#R`vQf2Q)}s$kh<3 zG~2fjR*ODSy+K$sCPVr8ZrBwgPf_|}bTAz+`-ahQCsdkla^{LT9H;b}^m_ZQh45VX<8vqpNJs<9!%@U8}RQKfTjR)#u5 zgg)12Pp4IDDOwG0njZK%W>Xuk@9rdQJfBPsvvmG(-?{(QsYV{hH0TVuMhVRB|HaZD zpD+?Mq5*%K0G=>H=$5`~IZR+R;iYq6e)tH2u~$*ELD(XpI^jmJc)%S0)`aczkiI{_ zotGPcy}aowH@MQk*>(E>gQH+49C_#i6P^6$BF}P!eMJz$So?;QugjF8Qs`WNuUW&N2kKLiWaZj#c#X>^gpXX!2B*d3_{#93FVe!1V`>3T~g+d@l@P?c&@}}4`l?gXT z*b|^ff8B(Z?|H}^TVco){>lxl<-s8!6wXAPpt$1W<(w!^)>}l_3D(h)E>M4}tyDE!f|x+c0(XzT{~x$Ind3 z^%nbC&F-4aQY$aomnU;C?Db=<{x0HBrgo~7K51jU=Ixu@VANv;(K(#=Y?B`Kp2-|t_op+kKi?> zgub4hrp+o7BE;aBeu1ARalgzj&-iGzl38{np@O}bS$9f6=$h4>6=$BvV(R(WHvnw; zHT7Be+%Ta(OAHbuZn&x>gs`;lXGl?RYk6RHjlD$@YXGSLeg`b`R0BWUh@9-N#`<=T z4_{0N)nA8lVUM)$4to>lgSEjilokiXwsbzl{hTm2D8FECE93EIfqvOF98+H}K%HXz zJq3|x4M0;B*{=^(-!JSdM3**U=*hakt~Opd6-ob4Td^Ti_J1v=(8Pm~YA`n;^3W+O zr9Gt_zUvciUUVsH8@5ZIn{9xX5k{}-JU5gW=oXaW*Q0%Q!w&nk3D|yrVbi5}8>EGs z!C^BVo7x*b%uV98Ly4l&Byz3p2pjV`cb;;&J6>eh?Tu_d$8$bpftH;}nqjX{K5?e= z#zt=j6xu>^x+h7lLknl*@9slrnd{`zwBezeGn`7#+akXw#=;MZdb{#bjyZVe0;#ZZ zbdf)_oXNj8{Cr~>Q6)tYem$E&JTme338y);mk{C00;g7nF79kJYgcockV+#CMp zK(=4;VF8$9QkPEvGLTc7f`Ua~)p{)Z&Djx-IhpX}n;d9tPF(pv6M@dJwYwg(b(y0R z*y{bP5MN|Olpjt~^c4W21OM8*h&fwa2%a1|l{R>QIyqcqrTdYHV&ak?v>Znhf=?K? zuRvPAy}{%=Yc6oE^E!50-7B>ui%D(HdmU6MZ6Tnfq>XSVW4ld+U5NeOuto0fU)&Gp zDOmTpP*^0S0y>3d79coGZ>N{2ed^&28~vEq3Q_wcz0&bU4O$mlzDT}-bzHDm6T@FP z?Bre%eEJZZ%bmpa=9NxN3oYtnxyR8H6(OhpF>NF_Y0Np0I+ z>}|hAnnZW>ia{S|Jq9fcxHy!``t=jLj#VGGNmju+l3h~DU#yWXd%{F!}Glhb;L|?M^=FWJV)*CcIhIUI$Q#c^wbJI?`+8HY-SVW!I?K*<+pmT=wXL>h zQ7JXX`u0{bBx>b+MyBy)FmkO#t)q zYu%^EkF%c=k&GlpYue<1odr>mJ`4N3#LE%f=z@~RL=I~0=HIrayr?9(psCD4|xHNx`pBhU!1pOgu?krqmC?##y(c2q2QAnTUd68SQ!In^+uio^wmOEKWl1 zWRsAf;Ljq67QX$fqA1d7>HjGZ$Z90?fk{`~*_IsElzd|%efzYjajBN9d)dsAZk}z; zqn54<`^kU)B(mAW`qoQ^04`ktaN<&IGShcIPwBFG$b1I#zedL(tGz&P4U^Tpf5RR1 zCr6u@Tp5HVtTYXZ#v+HJ(D2-eBq~HaD~LcN=Cq0yW8qVAv{2($uFOJ9 z`JgWuO^q!y42%7U5bbisEBT2?JR;*i!^FTHz!6`Y;Bm5iI>fnLA$Rx=P-cDl5XC>J z?3y;oX+QU~eq?Sh%2zzg_`g44PxL%IPd+$I0M8+Q`H8Xf0V?sjMZ0%N9sF@J@slxS0)A4~f1 z^7@>Fu%BVhB;76)O>8O91aJO*6rPAk2`1ytr=*_tUNP&wfFOy_D?q)ntfpeZb^qO^ z%%ls8YU^$;jFF<80=X7KG-s;^Z@S2IZ_+dT}j~6KR1iAfu}ycUGICoSD5Ljo72o{ zHl_JunDC8jpU@!B47DoJgT^S=kdSUxg6?fmx0crcivI~7a-tB#1XW+~vhgTWx65IL zllgvF(1wdD$hHAxw14?NG|!mOED)YhccpYA{DNU2rnayou+*m801{;{Trn&XzN5|0 z7D#aH!{}Cv341KP!jS$Ou%hVX%n==9(s0_2swM!nPXm9> z8ah0N_&Zrw0=F$eEN)>OCd?h+U%@!ja0Fs2xD|m-y5EjM>Y#_E7df1mS z(`wyxQWu<36B`&wpd!QAsHNDpTW$WvFV95=jDShRu)5O3DNLun1p-i03NAa{b!q~a z%Gh_j@B3fpexP;n)}2)SvDOG;?_G5-8u@+g>gmND+XXRMDt#G&wqfx-VQS`^ezCS7 z_;&pb+_BylaiU4L+@5eQ5uWhSuS+Pxk+O%Lim`Amh@Zcqo(AmM$aLsSNWu=~!>5lj$yb}o*_+7ubw0ceLwa+% zP1o<9hoi7NSg&7~@!(>S;C4x?&T=Al@m%fO+8lksGEgdne|{+66t0@l8a^Kc&*v&f zMhHHZXmeP^c01mul}4Z~f5nN-RJo#sK{POj_Uf~hKKM9v=HWJz5nxCtH1i->3NN>0GRC6gI65XR0Uw6w$q8v8hCob|pY zXSsXe>hHz16r+J~dCQY@ zJBR*ydx16SW%YCFMP^>h+#JOsE1`F>GW*EM3DIUr^83 z--H8FaJ!6b%w&%&NY<2hFMf=UdDO&Rhu!%@5K~VmfM)8>J@CyBjiG9z?_EZjk*!wJ zRUq>!;D!H|n4I4YVF2?Lrl;KZyoIU(6sQUt5PJ878qsoS zpL>mW4)z8jUB1i3wxSZ{$sPAOb0{T(Hh9EKRmvL_D8_Mh{10ED~I_$rUdv4nm-Y zc>Zz8nK+T}o3%+$jZ)1hx}%|ke_(m8SCw|7Gi+Q%xcF%+lZ`;4TvrECe&v%tnHU;d}pPOBpB@DIH|t{`H!+~BIi++CPOEhRVIrMQM~QzSQ^ zIz9qxjmHj-$)r$Pe3cG)4xDsBQaq}F#k{c|2TfAkZFOHsUt`Pv_EOqTEs z1FBNZk6@Q~f`ARBf!V`j%mY|NV?AstkL_9@HB0(+t$~$V4mG9BL>>=P`dH9=sW%8k zsEZ<>{(4Pwpc*tG0@JP{MfH|FkZfj#mMef8H|_o5}K#=ZpU`@T28x}Ijl+cOo?bQcf!t=UheM& zfQ4ih17PJ@5pV4J(c6Dc$E!MTddn~f3d! z<-i%!6e$U07`b9vVV5N1mjpUvZ2aMvU^!QME-EWkKG#1EDu8Kbl^#ssJf}lb$r^un zk+v#73K)HaVFpXySdl=Po+)9Kx{Z#H4s&V(3e{lru}c>NU-+dr<1gq!(!P?EAJYtR zi~3TT_TFkDd+kfG10WQ$F$;mR(B?Y_=H+@L+UU45hxNf)Tqsku|76=;2p%4#&$5MN z7=nYCkGRPv`F{OyVC>fWVXD#vYt|`rTixnc17!4dOwv$z2bu7ha;=lzi>Jx*A9$fk-e_d8j1Q^9{z_K6@3 zfN3ScceRqZ)d_hZ;MMQ0#|lCb^3Fk%T4bib&XY({cVH~w`bcYNu~0@x|68&Dfk<>| z$*9kFG2KiUOpatRo@YCytVtt0cBdDspM4La%ekOJ(Bj%2=2cvLxr~3fvYa1A){pi7 zV3T-R;e_6CTAUnE#{F01CF^n3f$H_HE@WC<6$U>X_$`8gIwL|uh1ucw$nUv88EUKe zw*7`1nKgJ5Bh{>Y7+t+pO~VYLbZYq7GIyVsY8{qkWIlpUv_EUD5;Z~L2J&-mzc@O^ zWD<|Kujbti$;E%HOPs)ZjXNzsyDcx$_o*S6&@K8}m#ZM8$CuG^y5-iGMKb36^R~gGuP;mgmCg!O0`>WGMDG}fBZP$DEz~g2Hv*6bOxz%o0oW1V3gShv zE&x2$9)1LEwIGs;;d}A9-xx@rG?u?OgoTT@x zO1Z=#CQWtVPBpVm2;M_Gg?_o@xBQbTov)81pS6r6KJ&`U4@2xp%65m;Ur!w<*W8ds zl1eZa_bHhK;dGrkR(m7ru8ROL zP}-gUFRG$shZor)QPKD1VXys0KOWK2k1tSt z$LFr-9)!yo@czXaT75EH_fdUZZ(4aGXA08ovA$X6&{;#!?uiCvIFXF}xSVLJ?SeFv zE@G@yz9jzeKtqxbX@6tft2C66ZQ`)x`wm^8b4AeCoRh#ic(- zLgA9Fr+%oIHyu}TJe2@B_t@uHE`JMpsN_Y<7|&GL7%?1PuBVetYq`H-T!M{~*Da~4 ze|nVIv4{GnFC5Fy!-Y8h9e5T_$7-!h)=@ik37<=uX`gm^)c+`Nha-^Eyd(YzFaHKX>J^C(jE$&g<9JTH9kemFGSrZ{C|QeX=qodxE|WO}ifS{=A# z;K2!|$imWB+!9GX*u;Y z3^i32dh*uIlohT6KckwH_D#ubsPc>6Kq3-b5a90Uc`R^oL8@{+t;a#XIVHX4WuftU zxJjJB!Qwg4HX82Q(iS{Utsm`^=-y>p;J9S}F8CtIzUe1Jl*meUJ}I`-Y2>qgkh{E6 z$bF{ z9*f+h*YP>poYdIr|8rxP&8p($5vPkWYu8DRduE9FyP86~99m*}bN&0RJRU>8eeJq>+CV0tTdqq=Mj|sC>!64v z^Q9mG=E|%sgs_H&Y1w;yh<{lw6`mRkq@W2|b4rR7IwT&KV_L%}oXf=3QtvjmfFO;r zK3vG%eL4K#Wh*o9&h<8e&($k6pJ($`F#5IOH299ayx-6V{^uVh-sU~O`EG3xWO#kR zJTi*%;KYXaB?KH{zZ@0t^OO1ITAEYQ`BJOx+JG=Ckopo*m1WB=Y%rMX_pIJ%PGnc2 zOnuymglx5@Y2){ZbCqXDsuBpJI}G_wVeLt)29NaNxSj&fS*N1N^MF`?%|%63U$?;i zKehFnbX1d_Ik=n1lK(|&hxy~YAT035O~A@<=h*Y?%`SSx*|7^%r}54DQ&l$%Juf?2 z-DS#CjlGbyui(#VjLHa~jj2&5|I0DIHV~+J7R|(RJu1FaXSygrBzd}~5a*&$cpHY@ zSV3ew)d|~5;6}JWS(pnM!AMMNJ1&R^W3vo?qC}{;@|)JG+&s-t>tHK%l>YcJq!jYe z4oBh_9}7}`H<9Ct{D|ikrtS82y|2A={3P0`LYqHW&E{`GL;LvN+YLpSLfUt>53N*x zc3gQ?Ck4`qY^s-1t;!7){cdTG#7qEokL$@KyrP-7xk%d$h^z^WF?XAuxmCgC6%l?MM*VBMCgf9>Mo#PHwa98u5aK)b<%pj7f@T} z3rDVbIS0%qU6J#CgiS7gsr6VOw1X?ybdv$;Yicz`(wDvyttr8>CcNzP+X5>Kmp}95 z5j42yAO!Q=jpMvFCj^5xOV0nXLD7x9JL+EOMAdn-h>8E_krxXXkkT=hrqS$Eu3{tk zLLrniiQ|h`5Zoh~iP;B5Abiv>+Owh9R!MeZzelfWVAQve{TF_^i*p+WDz^>a#KG8} zEDW-(K0Dm=vB`IpG;9}$thjzX|2y9A(j{mtQI2vi4amP-kmNrj_VQEPUzh1vX9QiY z4#`=XU}tRjVpk=axUcbLje|GE8d9wCi|4Ctg6C;f*ud?|)SBTTvvvX3hr4m6oSBhi zo_707TwkN1i+5ptPK5Pin@e_PEM%;{M*f6itHQSzPvwEXhBNIc&)=wU-s}3-*Ne1o zCB32oaBGvM-^BG<^kqxDT;u}YrQXk0bAxmJ#oerZCdKGn?{&kLhLCk>*XuG#D5U>( zK)Cv|o!PTD^+WmC>5;LCV*a_f{0o@!<2?5#jiTy(3w_<>nC-QlhCW^$iqAWDVpU5< z|EfIzTo||JlN<$r;aZ2lo)L^oo3NaTmEe-i9YqPBbvoeqkOgG5J1pk6ei9Ql0p+MU zqUV(VAPz@Q&!WhlTbJVFxVLL>+G3yk0_X zpsS0-i^SMwZ-Uf_^&T)0fR(5_Up%3+Oqr8LA{prf9k8nR3tn%&z$Nu=i$x6yf2pB? ze7Wemz+dk{%M&K-$zBuZG^X!hskHdp1%3X2E7EgdmYD2eQvDUObl`7VrKj+<{oI+y zloqJWAG`>bvx{h5>bn^XKIoO1V10RPX%q(8IX3n(aE@2MR^ zJ}$s-AKnHZVAkwJ)4 z0`|Ln@n6V1BaU3AsOnwPV9@{ldZzr1D>*N(Oeplg%G{Pcj-HBWzcI|#637n4yJu~$ zGy4S}ke@c~jbHpIM&9ov>}Km81aU4!?lU)2Fvgaaw}g=v|B(H=eWWP*)PZ`MZjt!9 zOw(`R#)Md>hl&m<02+3#u1Rj^2B_((i}%;$uUx`)2&zGsX>#%^S1<`+2)MDQV^eFS zww53c(b|Ug4<#g)2DnL{EH&dx*vgL8Ro3=O2;WPUAt5oSU^myfuJGlAm2 zit6E;Qlkw^g{=nl{=068f0QT+?0oEk3kmn)wP0v`eoW;blk`O#O>=cKk*aM%+mz)d zZ_8Da4ZL7xmQu5HKV_Ulxv$?f-&}(a(CGMqaX_EDk>6ML15F9x{%d)E?&#a&<^Vn~q@Wg1!0J#H0xOo~f-bZ;H8pVa}`S&x}>3x&76t~F3w3~Zq)!yavFQ~*EpBnr9Vw2Omvq9=N%$yw0GnG9Iy()KeOJJJj>QJOA!^HmR|axG9;Y=KNmzE; z7fFdg!#gKyywgnYV?!)Y(6mx$DlSZpMp~K)ehg!|UTzI5H+=>BiY>W^g>>n!$O&{@+5k4%^cW0U*#dC1EgV zn+69_hORFGVDl7KfmLzA!YFyc9LG8;=ynR`m{n7Mb3Yr)9%JpkK&|R&6|uN@Xwdj} zUJK?in=phrySqF3);1~b3XVPiW~BjC(c0Hxi`FgvMITYxny7*+Dbw-$ll`~DpJ=6A zPS9(bUu3@C4Z+2x`TTWP{0Zg!Hcc_bK^7ub)w6YS*_?-RXzJ4+5$sw}fsB9WTPBb5 zs(g-Zq|z!1zPpE1nidhw*Jr*QuCbSP%VxGRfdbzm_-eEqnC|FgKH|Z*4Xt@b^@F>u z5UIpt6-{q|+#)T4eY!r)^OB52OjYTL@P0*-6>0xYgH>QwqL`UuAVnRI-ULBbVad_e z!Xkhnpc~z_Kdy#i$}UhgObo+8bj#RxTt?tG^Ris~$>PEvaVhOB}J( z9J`|IwQ?2{$0ZwPxZ1BJ3g*ZHlD~|!`Y{C!1(edB~-KqIt&h|z|aC7QQM~oPTYWh`=wYKi0X*7}sgCToO*YSGq zN$$OoF6xcN#K{rNSu)mzii9J9=eWniUwt*yB!a|K$zP4yf{3LC1g%St$ollt=PU=z zQZ#4nY83|7BE1YcjL_3yfaP|*4J4ymT-fta2bQ%-qDIVXkDa%DC3FZQMZqe_-WW9~0?N^f+Yh{^n>7%;?u^W*Mit>+0 zIIU9UM_f-aem+1#A7A&oqB*--c5aNFt2?x_xD)854;a1}pZcz3g8%fSiQ@KfvP1mE z^1-ifp`PMmxvS1*ebZjO%De7?OU&f?z}uRhf#(eIR2_LH$>TQPK>2%f(?k-5qdL*GP*0r#kV)V$n9+Iq)K5D% z*12)gXOrz#O3)>(=g+@JoT^a&6E|-uA*Nx%p7)qF7aqzJCY;BfaRkzAb`-K!`Cl0@ z(bf0dv7pX{FHqI8gi!e4AM*Q)a_lD$I^m9(Ac0-QAZH-c(3==_iSch_0tI@AR;xbp znBJLoTlb)l`Jd%Ib_!KOByy+{)wJpz>|83&r)6ub2^B zr^!E9q%ZQusgeVAwwrjkx(fKz|GIP9hJOSV%f+N<`m%jEAuX8fy9xK=z_@cmhQ{2K zeq}Ko3y0A)-$zlQJBK?v$?pl@`i6A0c{~p?BAZ;-yd^|+=78#=&IzvW+ui-yKS^{S z`Ba!baU>##KF&|o)3MV+iOwp!E7akbP#FU8lrc`O%FB6#juDgtN^O_O$&YAH2Ue!u z^vp|{Bo5y-EZCH=){&AN=4Yy2|?ll+n zSn7>cwYy_(Cw)0|32Qa8{ON3l6=!p-Kc{p;pZ4qMv4n`=&PQ_%`9wCQV(33@qOnO+dsD_^ju?Tj@e5t2UY&E3CCF z&_Zc8E>;lqo02vz+ZEw){zu;N;OI8G+m-jARBs*Vok?J<}%L)G^b1AsXs5t?W^+MxWDY+-gC%~I$9dSBFd>frA?LynK za7ruNg^k?MA0#;n%U@i6HhiKL2W&`2XKMNV2Jici^eRR)2E#=RsADMODpO(62O8G} z^9fZG1w!#4t$DwEIolgzkVUnn_^i{1ejHI&b)O6jgEGHhX3{kOqWXpC!>*YPb3GT- zwmEv5a&@U0H;_5t>+PI17=<67dsT34|gkNh=R@_kM|Q2CtjoEa3%%NLJ51(D=34EpwKdK7zi0 zdTy*70A}SIS08bs*g1saHE{*K>!d#CHx{^rgE+l!OIsYt!ITbZLp%ay+JBO%|4H2h zB8PGBNa~=ltLU366eu5u{nSxT81nCo*=XbvDI6Zqn+o+x^7XVa_^{n(swX|e$z z$w+Mt(_2Lly?3r~4y~$@I2(qdT?_xKvt%1F*_Y+*mF&sBNxlFKrIve+ipaN~YPI66 zu_X7{0QSxh2%$MAA=*1*D$Rf|QW1ZXxhf%eyiWArYfl=~2sY5x^wjDPK8HHC67fHz zmP^jSlj}CF!t^uux&ZmzWSDTgJnL}P9IKEWjX-sE*erdAxTrN*hR$cBDT6!IQA7Pw zo4L{vGjM4k8@;aSy*R+hJDtiq*Xmu&#{UFR zw-xDGK>W5JJU6@rNlAEUg1N(lv7lkeOz*KVfL;G=ABD`Z-{102Qt;o0vjO@uh8+08 zYB8w6m}2np2Jce#CBWW;g!5!|wN&OlyBx2RIC!)d^7L$JX7UWunL}XjIbw_%*08t^ zQC_%Q@nunOjp5q4#=gX&jUqqbZWnSuo6?x0Uzh)F0pz&i Date: Sat, 16 Aug 2025 10:54:05 +0800 Subject: [PATCH 18/76] c15 --- dumbdisplay/ddimpl.py | 65 +++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 345d2fb..cb3d5d4 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -13,10 +13,11 @@ #_DD_LIB_COMPATIBILITY = 7 # for :drag #_DD_LIB_COMPATIBILITY = 8 # for feedback type #_DD_LIB_COMPATIBILITY = 9 # joy stick valuerange (not used) -_DD_LIB_COMPATIBILITY = 14 # bring forward since v0.5.1 #_DD_SID = 'MicroPython-c2' -_DD_SID = f"MicroPython-c{_DD_LIB_COMPATIBILITY}" +#_DD_SID = "MicroPython-c9" # joy stick valuerange (not used) +#_DD_SID = "MicroPython-c14" # bring forward since v0.5.1 +_DD_SID = "MicroPython-c15" _DBG_TNL = False @@ -479,47 +480,57 @@ def _checkForFeedback(self): feedback = feedback[idx + 1:] if feedback != "": idx = feedback.index(':') - type = feedback[0:idx] - if len(type) == 1 and type >= "0" and type <= "9": - x = int(type) + fb_type = feedback[0:idx] + if len(fb_type) == 1 and fb_type >= "0" and fb_type <= "9": + x = int(fb_type) y = 0 text = None - type = "click" + fb_type = "click" else: - if type == "C": - type = "click" - elif type == "D": - type = "doubleclick" - elif type == "L": - type = "longpress" - elif type == "M": - type = "move" - elif type == "u": - type = "up" - elif type == "d": - type = "down" - elif type == "c": - type = "custom" + if fb_type == "C": + fb_type = "click" + elif fb_type == "D": + fb_type = "doubleclick" + elif fb_type == "L": + fb_type = "longpress" + elif fb_type == "M": + fb_type = "move" + elif fb_type == "u": + fb_type = "up" + elif fb_type == "d": + fb_type = "down" + elif fb_type == "c": + fb_type = "custom" feedback = feedback[idx + 1:] idx = feedback.index(',') idx2 = feedback.find(',', idx + 1) - x = int(feedback[0:idx]) + x_str = feedback[0:idx] + if x_str != "": + x = int(x_str) + idx += 1 + else: + x = 0 if idx2 == -1: - y = int(feedback[idx + 1:]) + y_str = feedback[idx:] text = None else: - y = int(feedback[idx + 1:idx2]) - text = feedback[idx2 + 1:] # TODO: set text as feedback text + y_str = feedback[idx:idx2] + text = feedback[idx2 + 1:] + if y_str != "": + y = int(y_str) + else: + y = 0 else: - type = "click" + fb_type = "click" x = 0 y = 0 + text = None layer = self._layers.get(lid) if layer is not None: - if type == "_": + if fb_type == "_": layer._handleAck(x, y, text) else: - layer._handleFeedback(type, x, y) + layer._handleFeedback(fb_type, x, y) # TODO: set text as feedback text except: pass def _readFeedback(self) -> str: From c9d85b1326112c8071f48725b29be137190690d3 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 12:18:34 +0800 Subject: [PATCH 19/76] updated --- dumbdisplay/ddlayer_turtle.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 5a56ba2..51fea97 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -12,12 +12,7 @@ def __init__(self, dd, width, height): :param width: width :param height: height ''' - self._init(dd, width, height, track_move=False) - def _init(self, dd, width, height, track_move: bool): - if track_move: - layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_BOOL_ARG(True)) - else: - layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height)) + layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height)) super().__init__(dd, layer_id) def forward(self, distance: int, with_pen: bool = True): ''' @@ -152,7 +147,7 @@ def __init__(self, dd, width, height): :param width: width :param height: height ''' - self._init(dd, width, height, track_move=True) + super().__init__(dd, width, height) self._x: int = 0 self._y: int = 0 self._next_ack_seq: int = _INIT_ACK_SEQ From 89303fb09572c984ffd262b55a60bf857d162281 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 12:43:31 +0800 Subject: [PATCH 20/76] updated --- dumbdisplay/ddlayer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index f7bf6d2..6f0c557 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -1,3 +1,5 @@ +from typing import Union + def DD_RGB_COLOR(r: int, g: int, b: int): return r * 0x10000 + g * 0x100 + b @@ -153,7 +155,7 @@ def disableFeedback(self): '''disable feedback''' self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(False)) self._feedback_handler = None - def getFeedback(self) -> DDFeedback: + def getFeedback(self) -> Union[DDFeedback, None]: ''' get any feedback as the structure {type, x, y} :return: None if none (or when "handler" set) From b3d76162dd5da86bb2f521067269821f4144e967 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 12:45:00 +0800 Subject: [PATCH 21/76] updated --- dumbdisplay/ddlayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 6f0c557..963191d 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, Optional def DD_RGB_COLOR(r: int, g: int, b: int): @@ -155,7 +155,7 @@ def disableFeedback(self): '''disable feedback''' self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(False)) self._feedback_handler = None - def getFeedback(self) -> Union[DDFeedback, None]: + def getFeedback(self) -> Optional[DDFeedback: ''' get any feedback as the structure {type, x, y} :return: None if none (or when "handler" set) From b14c5bdff2889e80e218b3c8cd770b197ead1fe7 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 12:46:07 +0800 Subject: [PATCH 22/76] updated --- dumbdisplay/ddlayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 963191d..7195b65 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -1,4 +1,4 @@ -from typing import Union, Optional +from typing import Optional def DD_RGB_COLOR(r: int, g: int, b: int): @@ -155,7 +155,7 @@ def disableFeedback(self): '''disable feedback''' self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(False)) self._feedback_handler = None - def getFeedback(self) -> Optional[DDFeedback: + def getFeedback(self) -> Optional[DDFeedback]: ''' get any feedback as the structure {type, x, y} :return: None if none (or when "handler" set) From 550da54f8adcc41c85556d29b44f8f6b8e2a2265 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 19:37:43 +0800 Subject: [PATCH 23/76] updated --- _dev_test.py | 61 +++++++++++++++++++++++++++++++++++++- dumbdisplay/ddlayer.py | 3 +- dumbdisplay/dumbdisplay.py | 11 ++++++- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 62bb96b..fd079a1 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -1,5 +1,5 @@ import random -from dumbdisplay.dumbdisplay import DumbDisplay +from dumbdisplay.full import * from dumbdisplay_examples.utils import create_example_wifi_dd @@ -133,6 +133,64 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): +def test_auto_pin_remaining(): + dd = create_example_wifi_dd() + status_layer = LayerGraphical(dd, 300, 80) + dd.configPinFrame(100, 100) + status_layer.pinLayer(0, 0, 100, 35) + + test_remaining = True + + ll = [] + if test_remaining: + dd.pinAutoPinLayers(AutoPin("H").build(), 0, 35, 100, 65) + for i in range(3): + l = LayerLedGrid(dd, i + 1, i + 1) + l.offColor("yellow") + ll.append(l) + dd.addRemainingAutoPinConfig(AutoPin("V", ll[0], ll[1]).build()) + else: + l1 = LayerLedGrid(dd) + l1.offColor("yellow") + l2 = LayerLedGrid(dd, 2, 2) + l2.offColor("pink") + dd.pinAutoPinLayers(AutoPin("H", l1, l2).build(), 0, 35, 100, 65) + ll.append(l1) + ll.append(l2) + + while True: + for l in ll: + l.toggle() + print("... ", end="") + dd.sleep(1) + print("...") + if dd.isReconnecting(): + break # since haven't setup for reconnection (like with recordLayerSetupCommands) ... may as well break out of the loop + print("... ASSUME disconnected") + + +def test_auto_pin(): + from dumbdisplay.layer_ledgrid import LayerLedGrid + dd = create_example_wifi_dd() + top_ll = [LayerLedGrid(dd) for _ in range(3)] + dd.addRemainingAutoPinConfig(AutoPin('V').build()) + # AutoPin('H', *top_ll).pin(dd) + # l1 = LayerLedGrid(dd, 2, 2) + # l1.offColor("yellow") + # l2 = LayerLedGrid(dd, 2, 2) + # l2.offColor("yellow") + # dd.addRemainingAutoPinConfig(AutoPin('V', l1, l2).build()) + while True: + for l in top_ll: + l.toggle() + print("... ", end="") + dd.sleep(1) + print("...") + if dd.isReconnecting(): + break # since haven't setup for reconnection (like with recordLayerSetupCommands) ... may as well break out of the loop + print("... ASSUME disconnected") + + def test_margin(): from dumbdisplay.layer_ledgrid import LayerLedGrid @@ -175,6 +233,7 @@ def test_find_packages(): if __name__ == "__main__": + test_auto_pin_remaining() test_passive_turtleTracked(sync=True) # run_debug() diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 7195b65..23f0e88 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -1,4 +1,3 @@ -from typing import Optional def DD_RGB_COLOR(r: int, g: int, b: int): @@ -155,7 +154,7 @@ def disableFeedback(self): '''disable feedback''' self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(False)) self._feedback_handler = None - def getFeedback(self) -> Optional[DDFeedback]: + def getFeedback(self) -> DDFeedback: ''' get any feedback as the structure {type, x, y} :return: None if none (or when "handler" set) diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index 3c97832..8ea2210 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -53,7 +53,6 @@ def _build_layout(self): layout_spec = super()._build_layout() return f"S/{self.left}-{self.top}-{self.right}-{self.bottom}({layout_spec})" - class DumbDisplay(DumbDisplayImpl): @staticmethod def runningWithMicropython(): @@ -117,6 +116,16 @@ def configAutoPin(self, layout_spec: str = "V(*)"): ''' self._connect() self._sendCommand(None, "CFGAP", layout_spec) + def addRemainingAutoPinConfig(self, rest_layout_spec: str): + ''' + add REST "auto pinning" spec for layers not already associated into existing "auto pinning" config + ''' + self._sendCommand(None, "ADDRESTAP", rest_layout_spec) + def deleteAllRemainingAutoPinConfigs(self, rest_layout_spec: str): + ''' + delete all REST "auto pinning" specs + ''' + self._sendCommand(None, "ADDRESTAP", rest_layout_spec) def configPinFrame(self, x_unit_count: int, y_unit_count: int): self._connect() self._sendCommand(None, "CFGPF", _DD_INT_ARG(x_unit_count), _DD_INT_ARG(y_unit_count)) From 11817d5e52fbcc11663a6cdca328dee9e370ae0f Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 16 Aug 2025 22:06:40 +0800 Subject: [PATCH 24/76] added DDRootLayer --- _dev_test.py | 30 +++++++----------------------- dumbdisplay/ddimpl.py | 24 +++++++++++++++++++++--- dumbdisplay/ddlayer_graphical.py | 31 ++++++++++++++++++++++--------- dumbdisplay/dumbdisplay.py | 8 ++++++++ dumbdisplay/layer_graphical.py | 1 + 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index fd079a1..afe950a 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -134,7 +134,14 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): def test_auto_pin_remaining(): + rooted = True + dd = create_example_wifi_dd() + + if rooted: + root_layer = DDRootLayer(dd, 200, 300) + root_layer.border(5, "darkgreen") + root_layer.backgroundColor("lightgreen") status_layer = LayerGraphical(dd, 300, 80) dd.configPinFrame(100, 100) status_layer.pinLayer(0, 0, 100, 35) @@ -169,29 +176,6 @@ def test_auto_pin_remaining(): print("... ASSUME disconnected") -def test_auto_pin(): - from dumbdisplay.layer_ledgrid import LayerLedGrid - dd = create_example_wifi_dd() - top_ll = [LayerLedGrid(dd) for _ in range(3)] - dd.addRemainingAutoPinConfig(AutoPin('V').build()) - # AutoPin('H', *top_ll).pin(dd) - # l1 = LayerLedGrid(dd, 2, 2) - # l1.offColor("yellow") - # l2 = LayerLedGrid(dd, 2, 2) - # l2.offColor("yellow") - # dd.addRemainingAutoPinConfig(AutoPin('V', l1, l2).build()) - while True: - for l in top_ll: - l.toggle() - print("... ", end="") - dd.sleep(1) - print("...") - if dd.isReconnecting(): - break # since haven't setup for reconnection (like with recordLayerSetupCommands) ... may as well break out of the loop - print("... ASSUME disconnected") - - - def test_margin(): from dumbdisplay.layer_ledgrid import LayerLedGrid dd = create_example_wifi_dd() diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index cb3d5d4..af87e70 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -1,7 +1,7 @@ import time, _thread from .ddiobase import DDInputOutput -from .ddlayer import DDLayer +from .ddlayer import DDLayer, _DD_INT_ARG # the followings will add time_ms and sleep_ms to the time module ... globally if not 'ticks_ms' in dir(time): @@ -19,6 +19,8 @@ #_DD_SID = "MicroPython-c14" # bring forward since v0.5.1 _DD_SID = "MicroPython-c15" +_ROOT_LAYER_ID = "00" # hardcoded + _DBG_TNL = False _HS_GAP: int = 1000 @@ -207,6 +209,7 @@ def __init__(self, io: DDInputOutput): self._connected = False self._compatibility = 0 self._connected_iop: IOProxy = None + self._root_layer = None self._layers: dict[str, DDLayer] = {} self._tunnels: dict = {} self.last_validate_ms = 0 @@ -238,6 +241,8 @@ def sleep_ms(self, ms: int = 0): def _master_reset(self): + if self._root_layer is not None: + self._root_layer.release() layers = set(self._layers.values()) for layer in layers: layer.release() @@ -290,16 +295,29 @@ def _createLayer(self, layer_type: str, *params) -> str: layer_id = str(self._allocLayerNid()) self._sendCommand(layer_id, "SU", layer_type, *params) return layer_id + def _setRootLayer(self, width, height, contained_alignment) -> str: + if self._root_layer is not None: + self._root_layer.release() + self._connect() + self._sendCommand(None, "ROOT", _DD_INT_ARG(width), _DD_INT_ARG(height), contained_alignment); + layer_id = _ROOT_LAYER_ID + return layer_id def _reorderLayer(self, layer_id: str, how: str): self._sendCommand(layer_id, "REORD", how) def _deleteLayer(self, layer_id: str): self._sendCommand(layer_id, "DEL") def _onCreatedLayer(self, layer: DDLayer): - self._layers[layer.layer_id] = layer + if layer.layer_id == _ROOT_LAYER_ID: + _root_layer = layer + else: + self._layers[layer.layer_id] = layer # def _onCreatedLayer(self, layer): # self.layers[layer.layer_id] = layer def _onDeletedLayer(self, layer_id: str): - del self._layers[layer_id] + if layer_id == _ROOT_LAYER_ID: + self._root_layer = None + else: + del self._layers[layer_id] # def _ensureConnectionReady(self): # if self._connected: diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index 0af0f1e..76cf00d 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -2,15 +2,8 @@ from .ddlayer_multilevel import DDLayerMultiLevel from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG -class DDLayerGraphical(DDLayerMultiLevel): - '''Graphical LCD''' - def __init__(self, dd, width, height): - ''' - :param dd: DumbDisplay object - :param width: width - :param height: height - ''' - layer_id = dd._createLayer("graphical", _DD_INT_ARG(width), _DD_INT_ARG(height)) +class DDLayerGraphicalBase(DDLayerMultiLevel): + def __init__(self, dd, layer_id): super().__init__(dd, layer_id) def setCursor(self, x, y): self.dd._sendCommand(self.layer_id, "setcursor", str(x), str(y)) @@ -143,3 +136,23 @@ def write(self, text, draw = False): self.dd._sendCommand(self.layer_id, "drawtext" if draw else "write", text) +class DDLayerGraphical(DDLayerGraphicalBase): + def __init__(self, dd, width, height): + ''' + :param dd: DumbDisplay object + :param width: width + :param height: height + ''' + layer_id = dd._createLayer("graphical", _DD_INT_ARG(width), _DD_INT_ARG(height)) + super().__init__(dd, layer_id) + + +class DDRootLayer(DDLayerGraphicalBase): + def __init__(self, dd, width, height, contained_alignment = ""): + ''' + :param dd: DumbDisplay object + :param width: width + :param height: height + ''' + layer_id = dd._setRootLayer(width, height, contained_alignment) + super().__init__(dd, layer_id) diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index 8ea2210..dd55aba 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -183,6 +183,14 @@ def tone(self, freq: int, duration: int): def notone(self): self._sendCommand(None, "NOTONE") + # def setRootLayer(self, width: int, height: int, contained_alignment: str = "") -> DDLayerGraphical: + # /// note that the "root" will always be placed as the container, and hence don't need be pined; + # /// @param containedAlignment the alignment of the contained layers; "L" / "T" / "LT"; "" means centered + # /// currently, "container" layer does not support "feedback" + # /// @since v0.9.9-r50 + # GraphicalDDLayer* setRootLayer(int width, int height, const String& containedAlignment = ""); + # + def onDetectedDisconnect(self, for_ms: int): diff --git a/dumbdisplay/layer_graphical.py b/dumbdisplay/layer_graphical.py index 4d9b771..981a765 100644 --- a/dumbdisplay/layer_graphical.py +++ b/dumbdisplay/layer_graphical.py @@ -1 +1,2 @@ from .ddlayer_graphical import DDLayerGraphical as LayerGraphical +from .ddlayer_graphical import DDRootLayer From 7c649348ef7e90c7d47724dfa68dfcebb344c979 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 17 Aug 2025 08:24:28 +0800 Subject: [PATCH 25/76] adding types --- README.md | 2 ++ dumbdisplay/ddimpl.py | 16 ++++++++-------- dumbdisplay/ddio_socket.py | 2 +- dumbdisplay/ddio_wifi.py | 2 +- dumbdisplay/ddlayer.py | 17 +++++++++-------- dumbdisplay/ddlayer_graphical.py | 4 ++++ dumbdisplay/ddlayer_turtle.py | 2 +- dumbdisplay/dumbdisplay.py | 13 +++++++------ 8 files changed, 33 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 8e83a74..d97ff2c 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,8 @@ MIT # Change History v0.5.1 +- added DDRootLayer +- added LayerTurtle - bug fixes v0.5.0 diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index af87e70..48d0d20 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -67,14 +67,14 @@ def _ACK_STR_TO_ACK_SEQ(ack_str: str) -> int: class IOProxy: - def __init__(self, io): + def __init__(self, io: DDInputOutput): self._io = io self.data: str = '' - self.last_keep_alive_ms = 0 - self.reconnect_keep_alive_ms = 0 - self.reconnecting = False - self.reconnect_enabled = False - self.reconnect_RC_id = None + self.last_keep_alive_ms: int = 0 + self.reconnect_keep_alive_ms: int = 0 + self.reconnecting: bool = False + self.reconnect_enabled: bool = False + self.reconnect_RC_id: str = None def available(self): done = '\n' in self.data while (not done) and self._io.available(): @@ -93,7 +93,7 @@ def read(self) -> str: # return self.data #def clear(self): # self.data = '' - def print(self, s): + def print(self, s: str): self._io.print(s) def keepAlive(self): self.last_keep_alive_ms = time.ticks_ms() @@ -295,7 +295,7 @@ def _createLayer(self, layer_type: str, *params) -> str: layer_id = str(self._allocLayerNid()) self._sendCommand(layer_id, "SU", layer_type, *params) return layer_id - def _setRootLayer(self, width, height, contained_alignment) -> str: + def _setRootLayer(self, width: int, height: int, contained_alignment: str) -> str: if self._root_layer is not None: self._root_layer.release() self._connect() diff --git a/dumbdisplay/ddio_socket.py b/dumbdisplay/ddio_socket.py index 50917f8..9d3f29b 100644 --- a/dumbdisplay/ddio_socket.py +++ b/dumbdisplay/ddio_socket.py @@ -4,7 +4,7 @@ import socket class DDIOSocket(DDInputOutput): - def __init__(self, port): + def __init__(self, port: int): self.ip = "???" self.port = port self.sock = None diff --git a/dumbdisplay/ddio_wifi.py b/dumbdisplay/ddio_wifi.py index 2a5620f..51229e2 100644 --- a/dumbdisplay/ddio_wifi.py +++ b/dumbdisplay/ddio_wifi.py @@ -6,7 +6,7 @@ import network class DDIOWifi(DDIOSocket): - def __init__(self, ssid, password, port = DD_DEF_PORT): + def __init__(self, ssid: str, password: str, port: int = DD_DEF_PORT): if ssid is None: raise Exception('SSID not provided') super().__init__(port) diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 23f0e88..14ffccd 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -42,8 +42,8 @@ class DDFeedback: ''' type: can be "click", "doubleclick", "longpress" ''' - def __init__(self, type, x, y): - self.type = type + def __init__(self, fb_type: str, x: int, y: int): + self.type = fb_type self.x = x self.y = y @@ -179,21 +179,22 @@ def release(self): self.dd._deleteLayer(self.layer_id) self.dd._onDeletedLayer(self.layer_id) self.dd = None - def pinLayer(self, uLeft: int, uTop: int, uWidth: int, uHeight: int, align: str = ""): - self.dd._pinLayer(self.layer_id, uLeft, uTop, uWidth, uHeight, align) + def pinLayer(self, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): + #self.dd.pinLayer(self.layer_id, u_left, u_top, u_width, u_height, align) + self.dd.pinLayer(self, u_left, u_top, u_width, u_height, align) def reorderLayer(self, how: str): self.dd._reorderLayer(self.layer_id, how) - def _handleFeedback(self, type, x, y): + def _handleFeedback(self, fb_type: str, x: int, y: int): #print("RAW FB: " + self.layer_id + '.' + type + ':' + str(x) + ',' + str(y)) if self._feedback_handler is not None: - self._feedback_handler(self, type, x, y) + self._feedback_handler(self, fb_type, x, y) else: - self._feedbacks.append((type, x, y)) + self._feedbacks.append((fb_type, x, y)) # self._shipFeedbacks() - def _handleAck(self, x, y, text: str): + def _handleAck(self, x: int, y: int, text: str): pass diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index 76cf00d..55737d7 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -148,8 +148,12 @@ def __init__(self, dd, width, height): class DDRootLayer(DDLayerGraphicalBase): + ''' + it is the root layer of the DumbDisplay; it is basically a graphical layer + ''' def __init__(self, dd, width, height, contained_alignment = ""): ''' + set the root layer of the DumbDisplay; it is basically a graphical layer :param dd: DumbDisplay object :param width: width :param height: height diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 51fea97..8c5d5ef 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -168,7 +168,7 @@ def _sendCommandTracked(self, command: str, *params): self._next_ack_seq = _NEXT_ACK_SEQ(self._next_ack_seq) self._pending_ack_seq = ack_seq self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq) - def _handleAck(self, x, y, text: str): + def _handleAck(self, x: int, y: int, text: str): ack_seq = _ACK_STR_TO_ACK_SEQ(text) if ack_seq == self._pending_ack_seq: self._pending_ack_seq = None diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index dd55aba..087ea66 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -1,7 +1,6 @@ -from dumbdisplay.ddimpl import DumbDisplayImpl -from dumbdisplay.ddiobase import DDInputOutput -from .ddlayer import _DD_INT_ARG, _DD_BOOL_ARG - +from .ddimpl import DumbDisplayImpl +from .ddiobase import DDInputOutput +from .ddlayer import _DD_INT_ARG, _DD_BOOL_ARG, DDLayer import sys @@ -129,8 +128,10 @@ def deleteAllRemainingAutoPinConfigs(self, rest_layout_spec: str): def configPinFrame(self, x_unit_count: int, y_unit_count: int): self._connect() self._sendCommand(None, "CFGPF", _DD_INT_ARG(x_unit_count), _DD_INT_ARG(y_unit_count)) - def _pinLayer(self, layer_id: str, uLeft: int, uTop: int, uWidth: int, uHeight: int, align: str = ""): - self._sendCommand(layer_id, "PIN", _DD_INT_ARG(uLeft), _DD_INT_ARG(uTop), _DD_INT_ARG(uWidth), _DD_INT_ARG(uHeight), align) + # def pinLayer(self, layer_id: str, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): + # self._sendCommand(layer_id, "PIN", _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) + def pinLayer(self, layer: DDLayer, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): + self._sendCommand(layer.layer_id, "PIN", _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) def pinAutoPinLayers(self, layout_spec: str, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): self._sendCommand(None, "PINAP", layout_spec, _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) def recordLayerSetupCommands(self): From 941241ee81ed5f8437ef8c8838963d87579b694c Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 17 Aug 2025 08:53:48 +0800 Subject: [PATCH 26/76] adding types --- _dev_test.py | 2 +- dumbdisplay/ddlayer_graphical.py | 8 +++++--- dumbdisplay/ddlayer_joystick.py | 4 +++- dumbdisplay/ddlayer_lcd.py | 10 ++++++---- dumbdisplay/ddlayer_ledgrid.py | 17 +++++++++-------- dumbdisplay/ddlayer_multilevel.py | 3 ++- dumbdisplay/ddlayer_plotter.py | 4 +++- dumbdisplay/ddlayer_selection.py | 4 +++- dumbdisplay/ddlayer_turtle.py | 3 ++- dumbdisplay/dumbdisplay.py | 6 +++--- dumbdisplay_examples/mnist/main.py | 15 +++++++++++++++ dumbdisplay_examples/passive_blink/main.py | 12 ++++++++++++ dumbdisplay_examples/sliding_puzzle/main.py | 14 ++++++++++++++ 13 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 dumbdisplay_examples/mnist/main.py create mode 100644 dumbdisplay_examples/passive_blink/main.py create mode 100644 dumbdisplay_examples/sliding_puzzle/main.py diff --git a/_dev_test.py b/_dev_test.py index afe950a..dff65bb 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -144,7 +144,7 @@ def test_auto_pin_remaining(): root_layer.backgroundColor("lightgreen") status_layer = LayerGraphical(dd, 300, 80) dd.configPinFrame(100, 100) - status_layer.pinLayer(0, 0, 100, 35) + dd.pinLayer(status_layer, 0, 0, 100, 35) test_remaining = True diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index 55737d7..1bd4a0f 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -1,9 +1,11 @@ #from ._ddlayer import DDLayer from .ddlayer_multilevel import DDLayerMultiLevel from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG +from .dumbdisplay import DumbDisplay + class DDLayerGraphicalBase(DDLayerMultiLevel): - def __init__(self, dd, layer_id): + def __init__(self, dd: DumbDisplay, layer_id: str): super().__init__(dd, layer_id) def setCursor(self, x, y): self.dd._sendCommand(self.layer_id, "setcursor", str(x), str(y)) @@ -137,7 +139,7 @@ def write(self, text, draw = False): class DDLayerGraphical(DDLayerGraphicalBase): - def __init__(self, dd, width, height): + def __init__(self, dd: DumbDisplay, width: int, height: int): ''' :param dd: DumbDisplay object :param width: width @@ -151,7 +153,7 @@ class DDRootLayer(DDLayerGraphicalBase): ''' it is the root layer of the DumbDisplay; it is basically a graphical layer ''' - def __init__(self, dd, width, height, contained_alignment = ""): + def __init__(self, dd: DumbDisplay, width: int, height: int, contained_alignment: str = ""): ''' set the root layer of the DumbDisplay; it is basically a graphical layer :param dd: DumbDisplay object diff --git a/dumbdisplay/ddlayer_joystick.py b/dumbdisplay/ddlayer_joystick.py index b244d1a..4d2e2a4 100644 --- a/dumbdisplay/ddlayer_joystick.py +++ b/dumbdisplay/ddlayer_joystick.py @@ -1,9 +1,11 @@ from .ddlayer import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG, _DD_FLOAT_ARG +from .dumbdisplay import DumbDisplay + class DDLayerJoystick(DDLayer): '''Virtual joystick''' - def __init__(self, dd, maxStickValue: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): + def __init__(self, dd: DumbDisplay, maxStickValue: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): ''' :param dd: DumbDisplay object :param maxStickValue: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 diff --git a/dumbdisplay/ddlayer_lcd.py b/dumbdisplay/ddlayer_lcd.py index afc53ad..53cd837 100644 --- a/dumbdisplay/ddlayer_lcd.py +++ b/dumbdisplay/ddlayer_lcd.py @@ -1,10 +1,12 @@ from .ddlayer import DDLayer from .ddlayer import _DD_BOOL_ARG from .ddlayer import _DD_COLOR_ARG +from .dumbdisplay import DumbDisplay + class DDLayerLcd(DDLayer): '''LCD''' - def __init__(self, dd, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''): + def __init__(self, dd: DumbDisplay, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''): ''' :param dd: DumbDisplay object :param col_count: number of columns @@ -14,7 +16,7 @@ def __init__(self, dd, col_count: int = 16, row_count: int = 2, char_height: int ''' layer_id = dd._createLayer("lcd", str(col_count), str(row_count), str(char_height), font_name) super().__init__(dd, layer_id) - def print(self, text): + def print(self, text: str): self.dd._sendCommand(self.layer_id, "print", str(text)) def home(self): self.dd._sendCommand(self.layer_id, "home") @@ -36,10 +38,10 @@ def scrollDisplayLeft(self): self.dd._sendCommand(self.layer_id, "scrollleft") def scrollDisplayRight(self): self.dd._sendCommand(self.layer_id, "scrollright") - def writeLine(self, text, y = 0, align = "L"): + def writeLine(self, text: str, y: int = 0, align: str = "L"): '''write text as a line, with alignment "L", "C", or "R"''' self.dd._sendCommand(self.layer_id, "writeline", str(y), align, str(text)) - def writeCenteredLine(self, text, y = 0): + def writeCenteredLine(self, text: str, y: int = 0): '''write text as a line, with align "centered"''' self.dd._sendCommand(self.layer_id, "writeline", str(y), "C", str(text)) def pixelColor(self, color): diff --git a/dumbdisplay/ddlayer_ledgrid.py b/dumbdisplay/ddlayer_ledgrid.py index ebf5f40..5f04026 100644 --- a/dumbdisplay/ddlayer_ledgrid.py +++ b/dumbdisplay/ddlayer_ledgrid.py @@ -1,10 +1,11 @@ from .ddlayer import DDLayer from .ddlayer import _DD_COLOR_ARG +from .dumbdisplay import DumbDisplay class DDLayerLedGrid(DDLayer): '''Grid of LEDs''' - def __init__(self, dd, col_count = 1, row_count = 1, sub_col_count = 1, sub_row_count = 1): + def __init__(self, dd: DumbDisplay, col_count: int = 1, row_count: int = 1, sub_col_count: int = 1, sub_row_count: int = 1): ''' :param dd: DumbDisplay object :param col_count: grid # columns @@ -17,20 +18,20 @@ def __init__(self, dd, col_count = 1, row_count = 1, sub_col_count = 1, sub_row_ def turnOn(self, x = 0, y = 0): '''turn on LED @ (x, y)''' self.dd._sendCommand(self.layer_id, "ledon", str(x), str(y)) - def turnOff(self, x = 0, y = 0): + def turnOff(self, x: int = 0, y: int = 0): '''turn off LED @ (x, y)''' self.dd._sendCommand(self.layer_id, "ledoff", str(x), str(y)) def toggle(self, x = 0, y = 0): '''toggle LED @ (x, y)''' self.dd._sendCommand(self.layer_id, "ledtoggle", str(x), str(y)) - def turnOnEx(self, x = 0, y = 0, onColor: str = ""): - self.dd._sendCommand(self.layer_id, "ledonex", str(x), str(y), _DD_COLOR_ARG(onColor)) - def horizontalBar(self, count: int, rightToLeft = False): + def turnOnEx(self, x: int = 0, y: int = 0, on_color =""): + self.dd._sendCommand(self.layer_id, "ledonex", str(x), str(y), _DD_COLOR_ARG(on_color)) + def horizontalBar(self, count: int, right_to_left: bool = False): '''turn on LEDs to form a horizontal "bar"''' - self.dd._sendCommand(self.layer_id, "ledhoribar", str(count), str(rightToLeft)) - def verticalBar(self, count: int, bottomToTop = True): + self.dd._sendCommand(self.layer_id, "ledhoribar", str(count), str(right_to_left)) + def verticalBar(self, count: int, bottom_to_top: bool = True): '''turn on LEDs to form a vertical "bar"''' - self.dd._sendCommand(self.layer_id, "ledvertbar", str(count), str(bottomToTop)) + self.dd._sendCommand(self.layer_id, "ledvertbar", str(count), str(bottom_to_top)) def onColor(self, color): '''set LED on color''' self.dd._sendCommand(self.layer_id, "ledoncolor", _DD_COLOR_ARG(color)) diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 515882c..771de7a 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -1,9 +1,10 @@ from dumbdisplay.ddlayer import DDLayer, _DD_BOOL_ARG, _DD_FLOAT_ARG, _DD_INT_ARG, _DD_FLOAT_IS_ZERO +from dumbdisplay.dumbdisplay import DumbDisplay DD_DEF_LAYER_LEVEL_ID = "_" class DDLayerMultiLevel(DDLayer): - def __init__(self, dd, layer_id): + def __init__(self, dd: DumbDisplay, layer_id: str): super().__init__(dd, layer_id) def addLevel(self, level_id: str, width: float = 0, height: float = 0, switch_to_it: bool = False): ''' diff --git a/dumbdisplay/ddlayer_plotter.py b/dumbdisplay/ddlayer_plotter.py index f8f4b06..2f267fd 100644 --- a/dumbdisplay/ddlayer_plotter.py +++ b/dumbdisplay/ddlayer_plotter.py @@ -1,10 +1,12 @@ from .ddlayer import DDLayer from .ddlayer import _DD_BOOL_ARG from .ddlayer import _DD_COLOR_ARG +from .dumbdisplay import DumbDisplay + class DDLayerPlotter(DDLayer): '''Plotter''' - def __init__(self, dd, width, height, pixels_per_second = 10): + def __init__(self, dd: DumbDisplay, width: int, height: int, pixels_per_second: int = 10): ''' :param dd: DumbDisplay object :param width: width diff --git a/dumbdisplay/ddlayer_selection.py b/dumbdisplay/ddlayer_selection.py index 2b09edf..fe07cb7 100644 --- a/dumbdisplay/ddlayer_selection.py +++ b/dumbdisplay/ddlayer_selection.py @@ -1,10 +1,12 @@ from .ddlayer import DDLayer, _DD_INT_ARG, _DD_FLOAT_ARG from .ddlayer import _DD_BOOL_ARG from .ddlayer import _DD_COLOR_ARG +from .dumbdisplay import DumbDisplay + class DDLayerSelection(DDLayer): '''Selection''' - def __init__(self, dd, + def __init__(self, dd: DumbDisplay, col_count: int = 16, row_count: int = 2, hori_selection_count: int = 1, vert_selection_count: int = 1, char_height: int = 0, font_name: str = "", diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 8c5d5ef..1d2b4a3 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -2,11 +2,12 @@ from .ddlayer_multilevel import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ, _ACK_STR_TO_ACK_SEQ +from .dumbdisplay import DumbDisplay class DDLayerTurtle(DDLayer): '''Turtle-like Layer''' - def __init__(self, dd, width, height): + def __init__(self, dd: DumbDisplay, width: int, height: int): ''' :param dd: DumbDisplay object :param width: width diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index 087ea66..ce6d655 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -19,12 +19,12 @@ def __init__(self, orientation: str, *layers): ''' self.orientation = orientation self.layers = layers - def build(self): + def build(self) -> str: return self._build_layout() def pin(self, dd): layout_spec = self._build_layout() dd.configAutoPin(layout_spec) - def _build_layout(self): + def _build_layout(self) -> str: layout_spec = None for layer in self.layers: if layout_spec is None: @@ -48,7 +48,7 @@ def __init__(self, orientation: str, left: int, top: int, right: int, bottom: in self.right = right self.bottom = bottom super().__init__(orientation, *layers) - def _build_layout(self): + def _build_layout(self) -> str: layout_spec = super()._build_layout() return f"S/{self.left}-{self.top}-{self.right}-{self.bottom}({layout_spec})" diff --git a/dumbdisplay_examples/mnist/main.py b/dumbdisplay_examples/mnist/main.py new file mode 100644 index 0000000..5936934 --- /dev/null +++ b/dumbdisplay_examples/mnist/main.py @@ -0,0 +1,15 @@ +### +# this is just a sample entrypoint +### + + +def sample_run(): + import random + from dumbdisplay_examples.mnist.mnist_app import MnistApp + print(f"*** Sample run of MnistApp ***") + inference_func = lambda board_manager: random.randint(0, 9) + app = MnistApp(inference_func=inference_func) + app.run() + + +sample_run() \ No newline at end of file diff --git a/dumbdisplay_examples/passive_blink/main.py b/dumbdisplay_examples/passive_blink/main.py new file mode 100644 index 0000000..bb31aa9 --- /dev/null +++ b/dumbdisplay_examples/passive_blink/main.py @@ -0,0 +1,12 @@ +### +# this is just a sample entrypoint +### + +def sample_run(): + from dumbdisplay_examples.passive_blink.passive_blink_app import PassiveBlinkApp + print(f"*** Sample Run of PassiveBlinkApp ***") + app = PassiveBlinkApp() + app.run() + + +sample_run() \ No newline at end of file diff --git a/dumbdisplay_examples/sliding_puzzle/main.py b/dumbdisplay_examples/sliding_puzzle/main.py new file mode 100644 index 0000000..c6ea6c6 --- /dev/null +++ b/dumbdisplay_examples/sliding_puzzle/main.py @@ -0,0 +1,14 @@ +### +# this is just a sample entrypoint +### + +def sample_run(): + import random + from dumbdisplay_examples.sliding_puzzle.sliding_puzzle_app import SlidingPuzzleApp + print(f"*** Sample Run of SlidingPuzzleApp ***") + suggest_move_from_dir_func = lambda board_manager: random.randint(0, 3) + app = SlidingPuzzleApp(suggest_move_from_dir_func=suggest_move_from_dir_func) + app.run() + + +sample_run() \ No newline at end of file From dda20fee0ec1081e22940bf7d51b8b73c3ac3f74 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 17 Aug 2025 10:40:49 +0800 Subject: [PATCH 27/76] adding types (2) --- dumbdisplay/ddlayer.py | 4 +--- dumbdisplay/ddlayer_7segrow.py | 3 ++- dumbdisplay/ddlayer_graphical.py | 8 ++++---- dumbdisplay/ddlayer_joystick.py | 4 ++-- dumbdisplay/ddlayer_lcd.py | 4 ++-- dumbdisplay/ddlayer_ledgrid.py | 4 ++-- dumbdisplay/ddlayer_multilevel.py | 4 ++-- dumbdisplay/ddlayer_plotter.py | 6 ++---- dumbdisplay/ddlayer_selection.py | 5 ++--- dumbdisplay/ddlayer_turtle.py | 5 ++--- dumbdisplay/dumbdisplay.py | 1 + dumbdisplay_examples/mnist/main.py | 3 ++- dumbdisplay_examples/passive_blink/main.py | 3 ++- dumbdisplay_examples/sliding_puzzle/main.py | 3 ++- samples/neopixels/main.py | 9 ++++++--- 15 files changed, 34 insertions(+), 32 deletions(-) diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 14ffccd..1ca7438 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -1,5 +1,3 @@ - - def DD_RGB_COLOR(r: int, g: int, b: int): return r * 0x10000 + g * 0x100 + b @@ -48,7 +46,7 @@ def __init__(self, fb_type: str, x: int, y: int): self.y = y class DDLayer: - def __init__(self, dd, layer_id: str): + def __init__(self, dd: 'DumbDisplayImpl', layer_id: str): self.dd = dd self.layer_id = layer_id self._feedback_handler = None diff --git a/dumbdisplay/ddlayer_7segrow.py b/dumbdisplay/ddlayer_7segrow.py index 0e710aa..d459fbc 100644 --- a/dumbdisplay/ddlayer_7segrow.py +++ b/dumbdisplay/ddlayer_7segrow.py @@ -1,9 +1,10 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer from .ddlayer import _DD_COLOR_ARG class DDLayer7SegmentRow(DDLayer): '''A row of 7 Segments''' - def __init__(self, dd, digit_count = 1): + def __init__(self, dd: DumbDisplayImpl, digit_count = 1): ''' :param dd: DumbDisplay object :param digit_count: number of digits / # rows diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index 1bd4a0f..799b261 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -1,11 +1,11 @@ #from ._ddlayer import DDLayer +from .ddimpl import DumbDisplayImpl from .ddlayer_multilevel import DDLayerMultiLevel from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG -from .dumbdisplay import DumbDisplay class DDLayerGraphicalBase(DDLayerMultiLevel): - def __init__(self, dd: DumbDisplay, layer_id: str): + def __init__(self, dd: DumbDisplayImpl, layer_id: str): super().__init__(dd, layer_id) def setCursor(self, x, y): self.dd._sendCommand(self.layer_id, "setcursor", str(x), str(y)) @@ -139,7 +139,7 @@ def write(self, text, draw = False): class DDLayerGraphical(DDLayerGraphicalBase): - def __init__(self, dd: DumbDisplay, width: int, height: int): + def __init__(self, dd: DumbDisplayImpl, width: int, height: int): ''' :param dd: DumbDisplay object :param width: width @@ -153,7 +153,7 @@ class DDRootLayer(DDLayerGraphicalBase): ''' it is the root layer of the DumbDisplay; it is basically a graphical layer ''' - def __init__(self, dd: DumbDisplay, width: int, height: int, contained_alignment: str = ""): + def __init__(self, dd: DumbDisplayImpl, width: int, height: int, contained_alignment: str = ""): ''' set the root layer of the DumbDisplay; it is basically a graphical layer :param dd: DumbDisplay object diff --git a/dumbdisplay/ddlayer_joystick.py b/dumbdisplay/ddlayer_joystick.py index 4d2e2a4..298299f 100644 --- a/dumbdisplay/ddlayer_joystick.py +++ b/dumbdisplay/ddlayer_joystick.py @@ -1,11 +1,11 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG, _DD_FLOAT_ARG -from .dumbdisplay import DumbDisplay class DDLayerJoystick(DDLayer): '''Virtual joystick''' - def __init__(self, dd: DumbDisplay, maxStickValue: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): + def __init__(self, dd: DumbDisplayImpl, maxStickValue: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): ''' :param dd: DumbDisplay object :param maxStickValue: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 diff --git a/dumbdisplay/ddlayer_lcd.py b/dumbdisplay/ddlayer_lcd.py index 53cd837..a5e4dbc 100644 --- a/dumbdisplay/ddlayer_lcd.py +++ b/dumbdisplay/ddlayer_lcd.py @@ -1,12 +1,12 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer from .ddlayer import _DD_BOOL_ARG from .ddlayer import _DD_COLOR_ARG -from .dumbdisplay import DumbDisplay class DDLayerLcd(DDLayer): '''LCD''' - def __init__(self, dd: DumbDisplay, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''): + def __init__(self, dd: DumbDisplayImpl, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''): ''' :param dd: DumbDisplay object :param col_count: number of columns diff --git a/dumbdisplay/ddlayer_ledgrid.py b/dumbdisplay/ddlayer_ledgrid.py index 5f04026..235bdea 100644 --- a/dumbdisplay/ddlayer_ledgrid.py +++ b/dumbdisplay/ddlayer_ledgrid.py @@ -1,11 +1,11 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer from .ddlayer import _DD_COLOR_ARG -from .dumbdisplay import DumbDisplay class DDLayerLedGrid(DDLayer): '''Grid of LEDs''' - def __init__(self, dd: DumbDisplay, col_count: int = 1, row_count: int = 1, sub_col_count: int = 1, sub_row_count: int = 1): + def __init__(self, dd: DumbDisplayImpl, col_count: int = 1, row_count: int = 1, sub_col_count: int = 1, sub_row_count: int = 1): ''' :param dd: DumbDisplay object :param col_count: grid # columns diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 771de7a..d41157f 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -1,10 +1,10 @@ +from dumbdisplay.ddimpl import DumbDisplayImpl from dumbdisplay.ddlayer import DDLayer, _DD_BOOL_ARG, _DD_FLOAT_ARG, _DD_INT_ARG, _DD_FLOAT_IS_ZERO -from dumbdisplay.dumbdisplay import DumbDisplay DD_DEF_LAYER_LEVEL_ID = "_" class DDLayerMultiLevel(DDLayer): - def __init__(self, dd: DumbDisplay, layer_id: str): + def __init__(self, dd: DumbDisplayImpl, layer_id: str): super().__init__(dd, layer_id) def addLevel(self, level_id: str, width: float = 0, height: float = 0, switch_to_it: bool = False): ''' diff --git a/dumbdisplay/ddlayer_plotter.py b/dumbdisplay/ddlayer_plotter.py index 2f267fd..c2e31cd 100644 --- a/dumbdisplay/ddlayer_plotter.py +++ b/dumbdisplay/ddlayer_plotter.py @@ -1,12 +1,10 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer -from .ddlayer import _DD_BOOL_ARG -from .ddlayer import _DD_COLOR_ARG -from .dumbdisplay import DumbDisplay class DDLayerPlotter(DDLayer): '''Plotter''' - def __init__(self, dd: DumbDisplay, width: int, height: int, pixels_per_second: int = 10): + def __init__(self, dd: DumbDisplayImpl, width: int, height: int, pixels_per_second: int = 10): ''' :param dd: DumbDisplay object :param width: width diff --git a/dumbdisplay/ddlayer_selection.py b/dumbdisplay/ddlayer_selection.py index fe07cb7..aa0a4d1 100644 --- a/dumbdisplay/ddlayer_selection.py +++ b/dumbdisplay/ddlayer_selection.py @@ -1,12 +1,11 @@ +from .ddimpl import DumbDisplayImpl from .ddlayer import DDLayer, _DD_INT_ARG, _DD_FLOAT_ARG from .ddlayer import _DD_BOOL_ARG -from .ddlayer import _DD_COLOR_ARG -from .dumbdisplay import DumbDisplay class DDLayerSelection(DDLayer): '''Selection''' - def __init__(self, dd: DumbDisplay, + def __init__(self, dd: DumbDisplayImpl, col_count: int = 16, row_count: int = 2, hori_selection_count: int = 1, vert_selection_count: int = 1, char_height: int = 0, font_name: str = "", diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 1d2b4a3..7bca0bf 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -1,13 +1,12 @@ #from ._ddlayer import DDLayer from .ddlayer_multilevel import DDLayer from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG -from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ, _ACK_STR_TO_ACK_SEQ -from .dumbdisplay import DumbDisplay +from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ, _ACK_STR_TO_ACK_SEQ, DumbDisplayImpl class DDLayerTurtle(DDLayer): '''Turtle-like Layer''' - def __init__(self, dd: DumbDisplay, width: int, height: int): + def __init__(self, dd: DumbDisplayImpl, width: int, height: int): ''' :param dd: DumbDisplay object :param width: width diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index ce6d655..6f388b6 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -52,6 +52,7 @@ def _build_layout(self) -> str: layout_spec = super()._build_layout() return f"S/{self.left}-{self.top}-{self.right}-{self.bottom}({layout_spec})" + class DumbDisplay(DumbDisplayImpl): @staticmethod def runningWithMicropython(): diff --git a/dumbdisplay_examples/mnist/main.py b/dumbdisplay_examples/mnist/main.py index 5936934..9ae1b91 100644 --- a/dumbdisplay_examples/mnist/main.py +++ b/dumbdisplay_examples/mnist/main.py @@ -12,4 +12,5 @@ def sample_run(): app.run() -sample_run() \ No newline at end of file +if __name__ == "__main__": + sample_run() \ No newline at end of file diff --git a/dumbdisplay_examples/passive_blink/main.py b/dumbdisplay_examples/passive_blink/main.py index bb31aa9..3319c53 100644 --- a/dumbdisplay_examples/passive_blink/main.py +++ b/dumbdisplay_examples/passive_blink/main.py @@ -9,4 +9,5 @@ def sample_run(): app.run() -sample_run() \ No newline at end of file +if __name__ == "__main__": + sample_run() \ No newline at end of file diff --git a/dumbdisplay_examples/sliding_puzzle/main.py b/dumbdisplay_examples/sliding_puzzle/main.py index c6ea6c6..ff3653c 100644 --- a/dumbdisplay_examples/sliding_puzzle/main.py +++ b/dumbdisplay_examples/sliding_puzzle/main.py @@ -11,4 +11,5 @@ def sample_run(): app.run() -sample_run() \ No newline at end of file +if __name__ == "__main__": + sample_run() \ No newline at end of file diff --git a/samples/neopixels/main.py b/samples/neopixels/main.py index 64c8c66..c9ba1bb 100644 --- a/samples/neopixels/main.py +++ b/samples/neopixels/main.py @@ -1,5 +1,7 @@ NUM_PIXELS = 4 NEO_PIXELS_IN_PIN = 22 +# NUM_PIXELS = 64 +# NEO_PIXELS_IN_PIN = 20 try: @@ -64,11 +66,12 @@ def ShowNeoPixels(*pixels): sm.put(grb, 8) # a word is 32 bits, so, pre-shift out (discard) 8 bits, leaving 24 bits of the GRB time.sleep_us(300) # make sure the NeoPixels is reset for the next round - print("PIO ready!") + print("!!! PIO ready!") -except: +except Exception as e: + print(f"xxx failed to import rp2 -- {e}") + print("!!! PIO not supported!") ShowNeoPixels = None - print("PIO not supported!") if ShowNeoPixels is None: From 8f66ea5f4308a689858be9ed452ca63063b53359 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 17 Aug 2025 20:43:33 +0800 Subject: [PATCH 28/76] adding freeze / unfreeze draw --- _dev_test.py | 28 +++++++++++++++++++++------- dumbdisplay/dumbdisplay.py | 6 ++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index dff65bb..b5d33a6 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -68,11 +68,6 @@ def _setup(dd: DumbDisplay) -> LayerTurtleTracked: l.border(3, "blue") return l def _loop(l: LayerTurtleTracked, i: int, distance: int): - if i > 300: - coor = l.pos(sync=sync) - print(f"* ENDED turtle pos: {coor}") - l.dd.sleep(2) - return l.penColor("red") l.penSize(20) l.forward(distance) @@ -115,11 +110,14 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): distance = 1 i = 0 l: LayerTurtleTracked = None + freeze_for_steps = 10 while True: (connected, reconnecting) = dd.connectPassive() if connected: if l is None: l = _setup(dd) + # if freeze_for_steps > 0: + # dd.freezeDraw() distance = 1 i = 0 else: @@ -127,7 +125,21 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): dd.masterReset() l = None else: - _loop(l, i=i, distance=distance) + if i <= 300: + if i < 300: + _loop(l, i=i, distance=distance) + if freeze_for_steps > 0: + if (i + 1) % freeze_for_steps == 0: + dd.unfreezeDraw(refreeze_after_draw=True) + print(f" --- unfreeze") + else: + if freeze_for_steps > 0: + dd.unfreezeDraw() + print(f" --- FINAL unfreeze") + else: + coor = l.pos(sync=sync) + print(f"* ENDED turtle pos: {coor}") + l.dd.sleep(2) distance = distance + 1 i = i + 1 @@ -217,9 +229,11 @@ def test_find_packages(): if __name__ == "__main__": - test_auto_pin_remaining() test_passive_turtleTracked(sync=True) + + #test_auto_pin_remaining() + # run_debug() # run_doodle() # run_graphical() diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index 6f388b6..eb7af0d 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -135,6 +135,12 @@ def pinLayer(self, layer: DDLayer, u_left: int, u_top: int, u_width: int, u_heig self._sendCommand(layer.layer_id, "PIN", _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) def pinAutoPinLayers(self, layout_spec: str, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): self._sendCommand(None, "PINAP", layout_spec, _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) + def freezeDraw(self): + self._connect() + self._sendCommand(None, "FRZDRW") + def unfreezeDraw(self, refreeze_after_draw: bool = False): + self._connect() + self._sendCommand(None, "UNFRZDRW", _DD_BOOL_ARG(refreeze_after_draw)) def recordLayerSetupCommands(self): self._connect() self._sendCommand(None, "RECC") From a558feb1a87c90cae3f765dee7bbdad6697fef2b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Mon, 18 Aug 2025 20:20:28 +0800 Subject: [PATCH 29/76] adding freeze / unfreeze draw --- _dev_test.py | 4 ++-- dumbdisplay/ddimpl.py | 5 +++-- dumbdisplay/ddlayer_turtle.py | 4 ++-- dumbdisplay/dumbdisplay.py | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index b5d33a6..4290dad 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -130,11 +130,11 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): _loop(l, i=i, distance=distance) if freeze_for_steps > 0: if (i + 1) % freeze_for_steps == 0: - dd.unfreezeDraw(refreeze_after_draw=True) + dd.unfreezeDrawing(refreeze_after_draw=True) print(f" --- unfreeze") else: if freeze_for_steps > 0: - dd.unfreezeDraw() + dd.unfreezeDrawing() print(f" --- FINAL unfreeze") else: coor = l.pos(sync=sync) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 48d0d20..5a45d41 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -545,8 +545,9 @@ def _checkForFeedback(self): text = None layer = self._layers.get(lid) if layer is not None: - if fb_type == "_": - layer._handleAck(x, y, text) + if fb_type.startswith("_"): + ack_seq = fb_type[1:] if len(fb_type) > 1 else None + layer._handleAck(ack_seq, x, y, text) else: layer._handleFeedback(fb_type, x, y) # TODO: set text as feedback text except: diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 7bca0bf..5d6f72b 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -168,8 +168,8 @@ def _sendCommandTracked(self, command: str, *params): self._next_ack_seq = _NEXT_ACK_SEQ(self._next_ack_seq) self._pending_ack_seq = ack_seq self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq) - def _handleAck(self, x: int, y: int, text: str): - ack_seq = _ACK_STR_TO_ACK_SEQ(text) + def _handleAck(self, ack_seq: str, x: int, y: int, text: str): + ack_seq = _ACK_STR_TO_ACK_SEQ(ack_seq) if ack_seq == self._pending_ack_seq: self._pending_ack_seq = None self._x = x diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index eb7af0d..12663bb 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -135,12 +135,12 @@ def pinLayer(self, layer: DDLayer, u_left: int, u_top: int, u_width: int, u_heig self._sendCommand(layer.layer_id, "PIN", _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) def pinAutoPinLayers(self, layout_spec: str, u_left: int, u_top: int, u_width: int, u_height: int, align: str = ""): self._sendCommand(None, "PINAP", layout_spec, _DD_INT_ARG(u_left), _DD_INT_ARG(u_top), _DD_INT_ARG(u_width), _DD_INT_ARG(u_height), align) - def freezeDraw(self): + def freezeDrawing(self): self._connect() - self._sendCommand(None, "FRZDRW") - def unfreezeDraw(self, refreeze_after_draw: bool = False): + self._sendCommand(None, "FRZ") + def unfreezeDrawing(self, refreeze_after_draw: bool = False): self._connect() - self._sendCommand(None, "UNFRZDRW", _DD_BOOL_ARG(refreeze_after_draw)) + self._sendCommand(None, "UNFRZ", _DD_BOOL_ARG(refreeze_after_draw)) def recordLayerSetupCommands(self): self._connect() self._sendCommand(None, "RECC") From a3f324dfece41145919370dbf3be9cec6272aae9 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 22 Aug 2025 13:11:00 +0800 Subject: [PATCH 30/76] trying out TetrisOneBlockApp --- README.md | 4 +- _dev_test.py | 13 +- dumbdisplay/ddlayer_turtle.py | 97 ++++++------- .../tetris_one_block/tetriz_one_block.py | 127 ++++++++++++++++++ setup.py | 2 +- 5 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 dumbdisplay_examples/tetris_one_block/tetriz_one_block.py diff --git a/README.md b/README.md index d97ff2c..cc53235 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# DumbDisplay MicroPython Library (v0.5.1) +# DumbDisplay MicroPython Library (v0.6.0) DumbDisplay MicroPython Library -- workable with Python 3 -- is a port of the [DumbDisplay Arduino Library](https://github.com/trevorwslee/Arduino-DumbDisplay) to MicroPython / Python 3 for the [DumbDisplay Android app](https://play.google.com/store/apps/details?id=nobody.trevorlee.dumbdisplay) @@ -349,7 +349,7 @@ MIT # Change History -v0.5.1 +v0.6.0 - added DDRootLayer - added LayerTurtle - bug fixes diff --git a/_dev_test.py b/_dev_test.py index 4290dad..0ca46cd 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -110,7 +110,7 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): distance = 1 i = 0 l: LayerTurtleTracked = None - freeze_for_steps = 10 + freeze_for_steps = 0 while True: (connected, reconnecting) = dd.connectPassive() if connected: @@ -135,11 +135,16 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): else: if freeze_for_steps > 0: dd.unfreezeDrawing() - print(f" --- FINAL unfreeze") + print(f" --- FINAL[{i}] unfreeze") else: coor = l.pos(sync=sync) - print(f"* ENDED turtle pos: {coor}") - l.dd.sleep(2) + print(f"* ENDED[{i}] turtle pos: {coor}") + l.dd.sleep(1) + if i > 305: + l.clear() + l.home(with_pen=False) + distance = 0 + i = -1 distance = distance + 1 i = i + 1 diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 5d6f72b..4f98af4 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -5,106 +5,106 @@ class DDLayerTurtle(DDLayer): - '''Turtle-like Layer''' + """Turtle-like Layer""" def __init__(self, dd: DumbDisplayImpl, width: int, height: int): - ''' + """ :param dd: DumbDisplay object :param width: width :param height: height - ''' + """ layer_id = dd._createLayer("turtle", _DD_INT_ARG(width), _DD_INT_ARG(height)) super().__init__(dd, layer_id) def forward(self, distance: int, with_pen: bool = True): - ''' + """ forward; with pen or not - ''' + """ self._sendCommandTracked("fd" if with_pen else "jfd", _DD_INT_ARG(distance)) def backward(self, distance: int, with_pen: bool = True): - ''' + """ backward; with pen or not - ''' + """ self._sendCommandTracked("bk" if with_pen else "jbk", _DD_INT_ARG(distance)) def leftTurn(self, angle: int): - ''' + """ left turn - ''' + """ self.dd._sendCommand(self.layer_id, "lt", _DD_INT_ARG(angle)) def rightTurn(self, angle: int): - ''' + """ right turn - ''' + """ self.dd._sendCommand(self.layer_id, "rt", _DD_INT_ARG(angle)) def home(self, with_pen: bool = True): - ''' + """ go home (0, 0); with pen or not - ''' + """ self._sendCommandTracked("home" if with_pen else "jhome") def goTo(self, x: int, y: int, with_pen: bool = True): - ''' + """ go to (x, y); with pen or not - ''' + """ self._sendCommandTracked("goto" if with_pen else "jto", _DD_INT_ARG(x), _DD_INT_ARG(y)) def goBy(self, by_x: int, by_y: int, with_pen: bool = True): - ''' + """ go by (by_x, by_y); with pen or not - ''' + """ self._sendCommandTracked("goby" if with_pen else "jby", _DD_INT_ARG(by_x), _DD_INT_ARG(by_y)) def setHeading(self, angle: int): - ''' + """ set heading angle (degree) - ''' + """ self.dd._sendCommand(self.layer_id, "seth", _DD_INT_ARG(angle)) def penSize(self, size: int): - ''' + """ set pen size - ''' + """ self.dd._sendCommand(self.layer_id, "pensize", _DD_INT_ARG(size)) def penColor(self, color: str): - ''' + """ set pen color - ''' + """ self.dd._sendCommand(self.layer_id, "pencolor", _DD_COLOR_ARG(color)) def fillColor(self, color: str): - ''' + """ set fill color - ''' + """ self.dd._sendCommand(self.layer_id, "fillcolor", _DD_COLOR_ARG(color)) def noColor(self): - ''' + """ set no fill color - ''' + """ self.dd._sendCommand(self.layer_id, "nofillcolor") def penFilled(self, filled: bool = True): - ''' + """ set pen filled or not - ''' + """ self.dd._sendCommand(self.layer_id, "pfilled", _DD_BOOL_ARG(filled)) def setTextSize(self, size: int): - ''' + """ set text size - ''' + """ self.dd._sendCommand(self.layer_id, "ptextsize", _DD_INT_ARG(size)) def setTextFont(self, font_name = "", text_size = 0): - ''' + """ set font @param font_name: empty means default @param text_size: 0 means default - ''' + """ self.dd._sendCommand(self.layer_id, "ptextfont", font_name, _DD_INT_ARG(text_size)) def penUp(self): - '''pen up''' + """pen up""" self.dd._sendCommand(self.layer_id, "pu") def penDown(self): - '''pen down''' + """pen down""" self.dd._sendCommand(self.layer_id, "pd") def beginFill(self): - '''begin fill''' + """begin fill""" self.dd._sendCommand(self.layer_id, "begin_fill") def endFill(self): - '''end fill''' + """end fill""" self.dd._sendCommand(self.layer_id, "end_fill") def dot(self, size: int, color: str): - '''draw a dot''' + """draw a dot""" self.dd._sendCommand(self.layer_id, "dot", _DD_INT_ARG(size), _DD_COLOR_ARG(color)) def circle(self, radius: int, centered: bool = False): """draw circle; centered or not""" @@ -113,7 +113,7 @@ def oval(self, width: int, height: int, centered: bool = False): """draw oval; centered or not""" self.dd._sendCommand(self.layer_id, "coval" if centered else "oval", _DD_INT_ARG(width), _DD_INT_ARG(height)) def arc(self, width: int, height: int, start_angle: int, sweep_angle: int, centered: bool = False): - """draw arc; centered or not""" + '''draw arc; centered or not''' self.dd._sendCommand(self.layer_id, "carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) def triangle(self, side1: int, angle: int, side2: int): """draw triangle (SAS)""" @@ -128,25 +128,30 @@ def polygon(self, side: int, vertex_count: int): """draw polygon given side and vertex count""" self.dd._sendCommand(self.layer_id, "poly", _DD_INT_ARG(side), _DD_INT_ARG(vertex_count)) def centeredPolygon(self, radius: int, vertex_count: int, inside: bool = False): - """draw polygon enclosed in an imaginary centered circle + """ + draw polygon enclosed in an imaginary centered circle - given circle radius and vertex count - - whether inside the imaginary circle or outside of it""" + - whether inside the imaginary circle or outside of it + """ self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) - def write(self, text: str, draw: bool = False): - """write text; draw means draw the text (honor heading)""" + def write(self, text: str, align: str = "L"): + """write text, with alignment 'L', 'C', or 'R'""" + self._sendCommandTracked("write", str(align), text) + def drawText(self, text: str, draw: bool = False): + """draw the text (honor heading)""" self._sendCommandTracked("drawtext" if draw else "write", text) def _sendCommandTracked(self, command: str, *params): self.dd._sendCommand(self.layer_id, command, *params) class DDLayerTurtleTracked(DDLayerTurtle): # TODO: working on DDLayerTurtleTracked - '''Graphical LCD''' + """Graphical LCD""" def __init__(self, dd, width, height): - ''' + """ :param dd: DumbDisplay object :param width: width :param height: height - ''' + """ super().__init__(dd, width, height) self._x: int = 0 self._y: int = 0 diff --git a/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py b/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py new file mode 100644 index 0000000..37422f0 --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py @@ -0,0 +1,127 @@ +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtleTracked + + +_WIDTH = 400 +_HEIGHT = 700 + + +class Shape(): + def __init__(self, grid): + self.x = 5 + self.y = 0 + self.color = 4 + self.grid = grid + self.move = 'go' + + def move_right(self): + if self.x < 11 and self.move == 'go': + if grid[self.y][self.x+1]==0: + grid[self.y][self.x]=0 + self.x += 1 + grid[self.y][self.x] = self.color + + def move_left(self): + if self.x > 0 and self.move == 'go': + if grid[self.y][self.x-1]==0: + grid[self.y][self.x]=0 + self.x -= 1 + grid[self.y][self.x] = self.color + + +grid = [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] + + +class TetrisOneBlockApp(): + def __init__(self, dd: DumbDisplay): + self.dd = dd + self.root = None + self.score = None + + def run(self): + while True: + (connected, reconnecting) = self.dd.connectPassive() + if connected: + if self.root is None: + self.initializeDD() + elif reconnecting: + self.dd.masterReset() + self.board = None + else: + self.updateDD() + elif reconnecting: + self.dd.masterReset() + self.root = None + + def initializeDD(self): + + root = DDRootLayer(self.dd, _WIDTH, _HEIGHT) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtleTracked(self.dd, _WIDTH, _HEIGHT) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtleTracked(self.dd, _WIDTH, _HEIGHT) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + + self.root = root + self.score = score + + def updateDD(self): + pass + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() diff --git a/setup.py b/setup.py index fd2f191..c907350 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setuptools.setup( name='uDumbDisplayLib', - version='0.5.1', + version='0.6.0', author='Trevor Lee', author_email='trevorwslee@gmail.com', description='MicroPython DumbDisplay Library', From 30a37c12f74e31f1b128686c99a6e9e8cf8cf9cb Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 22 Aug 2025 19:08:40 +0800 Subject: [PATCH 31/76] trying out TetrisOneBlockApp --- _dev_test.py | 2 +- dumbdisplay/ddlayer_turtle.py | 4 +- .../tetris_one_block/tetriz_one_block.py | 131 +++++++++++++----- 3 files changed, 102 insertions(+), 35 deletions(-) diff --git a/_dev_test.py b/_dev_test.py index 0ca46cd..37b697c 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -110,7 +110,7 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): distance = 1 i = 0 l: LayerTurtleTracked = None - freeze_for_steps = 0 + freeze_for_steps = 10 while True: (connected, reconnecting) = dd.connectPassive() if connected: diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 4f98af4..95ef457 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -145,7 +145,9 @@ def _sendCommandTracked(self, command: str, *params): class DDLayerTurtleTracked(DDLayerTurtle): # TODO: working on DDLayerTurtleTracked - """Graphical LCD""" + """ + EXPERIMENTAL: Turtle-like Layer, with position tracking + """ def __init__(self, dd, width, height): """ :param dd: DumbDisplay object diff --git a/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py b/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py index 37422f0..b39acb8 100644 --- a/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py @@ -1,36 +1,14 @@ from dumbdisplay.core import * from dumbdisplay.layer_graphical import DDRootLayer -from dumbdisplay.layer_turtle import LayerTurtleTracked +from dumbdisplay.layer_turtle import LayerTurtle _WIDTH = 400 _HEIGHT = 700 +_PEN_FILED = True -class Shape(): - def __init__(self, grid): - self.x = 5 - self.y = 0 - self.color = 4 - self.grid = grid - self.move = 'go' - - def move_right(self): - if self.x < 11 and self.move == 'go': - if grid[self.y][self.x+1]==0: - grid[self.y][self.x]=0 - self.x += 1 - grid[self.y][self.x] = self.color - - def move_left(self): - if self.x > 0 and self.move == 'go': - if grid[self.y][self.x-1]==0: - grid[self.y][self.x]=0 - self.x -= 1 - grid[self.y][self.x] = self.color - - -grid = [ +_grid = [ [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], @@ -58,11 +36,38 @@ def move_left(self): ] + + +class _Shape(): + def __init__(self, grid): + self.x = 5 + self.y = 0 + self.color = 4 + self.grid = grid + self.move = 'go' + + def move_right(self): + if self.x < 11 and self.move == 'go': + if _grid[self.y][self.x + 1]==0: + _grid[self.y][self.x]=0 + self.x += 1 + _grid[self.y][self.x] = self.color + + def move_left(self): + if self.x > 0 and self.move == 'go': + if _grid[self.y][self.x - 1]==0: + _grid[self.y][self.x]=0 + self.x -= 1 + _grid[self.y][self.x] = self.color + + class TetrisOneBlockApp(): def __init__(self, dd: DumbDisplay): self.dd = dd - self.root = None - self.score = None + self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + self.score_count = 0 def run(self): while True: @@ -72,12 +77,9 @@ def run(self): self.initializeDD() elif reconnecting: self.dd.masterReset() - self.board = None + self.root = None else: self.updateDD() - elif reconnecting: - self.dd.masterReset() - self.root = None def initializeDD(self): @@ -85,7 +87,7 @@ def initializeDD(self): root.border(5, "darkred", "round", 1) root.backgroundColor("black") - score = LayerTurtleTracked(self.dd, _WIDTH, _HEIGHT) + score = LayerTurtle(self.dd, _WIDTH, _HEIGHT) score.penColor('red') score.penUp() #score.hideturtle() @@ -94,7 +96,7 @@ def initializeDD(self): score.setTextFont("Courier", 24) score.write('Score: 0', 'C') - border = LayerTurtleTracked(self.dd, _WIDTH, _HEIGHT) + border = LayerTurtle(self.dd, _WIDTH, _HEIGHT) border.penSize(10) border.penUp() #border.hideturtle() @@ -113,13 +115,76 @@ def initializeDD(self): border.setTextFont("Courier", 36) border.write("TETRIS", "C") + pen = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + if _PEN_FILED: + pen.penFilled() + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) self.root = root self.score = score + self.pen = pen + + shape = _Shape(_grid) + _grid[shape.y][shape.x] = shape.color + + self.draw_grid(_grid) def updateDD(self): pass + def draw_grid(self, grid): + self.dd.freezeDrawing() + self.pen.clear() + top = 230 + left = -110 + colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', + 'purple'] + + for y in range(len(grid)): # 24 rows + for x in range(len(grid[0])): # 12 columns + screen_x = left + (x*20) # each turtle 20x20 pixels + screen_y = top - (y*20) + color_number = grid[y][x] + if color_number == 0: + continue + color = colors[color_number] + self.pen.penColor(color) + self.pen.goTo(screen_x, screen_y, with_pen=False) + #self.pen.stamp() + if _PEN_FILED: + self.pen.rectangle(18, 18, centered=True) + else: # TODO: thing of a faster way + self.pen.fillColor(color) + self.pen.beginFill() + self.pen.rectangle(18, 18, centered=True) + self.pen.endFill() + self.dd.unfreezeDrawing() + + + def check_grid(self): + #global score_count + # Check if each row is full: + for y in range(0,24): + is_full = True + y_erase = y + for x in range(0,12): + if _grid[y][x] == 0: + is_full = False + break + # Remove row and shift down + if is_full: + self.score_count += 1 + self.score.clear() + self.score.write(f'Score: {self.score_count}', align='center') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + _grid[y+1][x] = _grid[y][x] + if __name__ == "__main__": from dumbdisplay_examples.utils import create_example_wifi_dd From a118cd07b5b6db7634993a67d569301554c9ef2a Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 22 Aug 2025 19:33:45 +0800 Subject: [PATCH 32/76] ''' => """ --- README.md | 2 +- dd_demo.py | 15 ++-- dumbdisplay/_exp__ddtunnel_basic.py | 2 +- dumbdisplay/ddimpl.py | 4 +- dumbdisplay/ddio_ble.py | 6 +- dumbdisplay/ddio_inet.py | 2 +- dumbdisplay/ddlayer.py | 42 ++++----- dumbdisplay/ddlayer_7segrow.py | 32 +++---- dumbdisplay/ddlayer_graphical.py | 50 +++++------ dumbdisplay/ddlayer_joystick.py | 20 ++--- dumbdisplay/ddlayer_lcd.py | 20 +++-- dumbdisplay/ddlayer_ledgrid.py | 26 +++--- dumbdisplay/ddlayer_multilevel.py | 86 +++++++++---------- dumbdisplay/ddlayer_plotter.py | 10 +-- dumbdisplay/ddlayer_selection.py | 38 ++++---- dumbdisplay/ddlayer_turtle.py | 2 +- dumbdisplay/dumbdisplay.py | 42 ++++----- .../sliding_puzzle/sliding_puzzle_app.py | 8 +- dumbdisplay_examples/utils.py | 4 +- .../sliding_puzzle/sliding_puzzle_app.py | 8 +- .../sliding_puzzle/sliding_puzzle_app_OLD.py | 4 +- .../sliding_puzzle_app_SIMPLE.py | 4 +- experiments/OLD/testing/le.py | 6 +- experiments/picopio/pio_neo_blink.py | 4 +- experiments/testing/le.py | 6 +- samples/neopixels/main.py | 4 +- 26 files changed, 229 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index cc53235..e5e0108 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ Other then the `DumbDisplay` object, you will need to create one or more layer o as shown in the example * you can configure the joystick to be a horizontal or vertical slider by changing the `directions` parameter to `LayerJoystick` - - param `maxStickValue`: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 + - param `max_stick_value`: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 - param `directions`: "lr" or "hori": left-to-right; "tb" or "vert": top-to-bottom; "rl": right-to-left; "bt": bottom-to-top; use "+" combines the above like "lr+tb" to mean both directions; "" the same as "lr+tb" * feedback -- to be talked about later -- is enabled by default, and you can poll for feedback like diff --git a/dd_demo.py b/dd_demo.py index 40004f2..9115e97 100644 --- a/dd_demo.py +++ b/dd_demo.py @@ -5,12 +5,12 @@ def _create_demo_dd(): - ''' + """ Create a DumbDisplay instance for demo purposes. If in MicroPython, it will connect using WiFi (***assuming*** `_my_secret.py`). If in Python, it will connect using Inet. Note that there should be a single DumbDisplay instance for the whole application. - ''' + """ from dumbdisplay.dumbdisplay import DumbDisplay if DumbDisplay.runningWithMicropython(): # connect using WIFI: @@ -28,13 +28,13 @@ def _create_demo_dd(): def demo_LayerLedGrid(col_count = 1, row_count = 1, sub_col_count = 1, sub_row_count = 1): - ''' + """ Demonstrate LayerLedGrid. :param col_count: number of columns in the grid :param row_count: number of rows in the grid :param sub_col_count: number of sub-columns in each grid cell :param sub_row_count: number of sub-rows in each grid cell - ''' + """ from dumbdisplay.layer_ledgrid import LayerLedGrid # Create a DumbDisplay instance @@ -155,14 +155,14 @@ def demo_LayerPlotter(): dd.timeslice() -def demo_LayerJoystick(maxStickValue: int = 1023, directions: str = ""): +def demo_LayerJoystick(max_stick_value: int = 1023, directions: str = ""): from dumbdisplay.layer_joystick import LayerJoystick # Create a DumbDisplay instance dd = _create_demo_dd() - # Create a LayerJoystick layer with the specified maxStickValue and directions, and set it up, like border and colors - l = LayerJoystick(dd, maxStickValue=maxStickValue, directions=directions) + # Create a LayerJoystick layer with the specified max_stick_value and directions, and set it up, like border and colors + l = LayerJoystick(dd, max_stick_value=max_stick_value, directions=directions) l.border(5, "blue") l.colors(stick_color="green", stick_outline_color="darkgreen") @@ -315,6 +315,7 @@ def run_mnist_app(): if __name__ == "__main__": + demo_AutoPin() demo_LayerTurtle() if True: diff --git a/dumbdisplay/_exp__ddtunnel_basic.py b/dumbdisplay/_exp__ddtunnel_basic.py index 7cf9472..f994251 100644 --- a/dumbdisplay/_exp__ddtunnel_basic.py +++ b/dumbdisplay/_exp__ddtunnel_basic.py @@ -2,7 +2,7 @@ class _exp_DDBasicTunnel(_exp_DDTunnel): - '''tunnel is ONLY supported with DumbDisplayWifiBridge -- https://www.youtube.com/watch?v=0UhRmXXBQi8''' + """tunnel is ONLY supported with DumbDisplayWifiBridge -- https://www.youtube.com/watch?v=0UhRmXXBQi8""" def __init__(self, dd, end_point) -> None: super().__init__(dd, end_point) # tunnel_id = dd._createTunnel(end_point) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 5a45d41..ec2f417 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -218,9 +218,9 @@ def timeslice(self): self._checkForFeedback() def delay(self, seconds: float = 0): - ''' + """ deprecated; use sleep() instead - ''' + """ self.sleep_ms(seconds * 1000) def sleep(self, seconds: float = 0): diff --git a/dumbdisplay/ddio_ble.py b/dumbdisplay/ddio_ble.py index 4a59c4c..5efedd9 100644 --- a/dumbdisplay/ddio_ble.py +++ b/dumbdisplay/ddio_ble.py @@ -71,12 +71,12 @@ def _ble_irq(self, event, data): #print("E:" + str(event)) if event == 1: - '''Central disconnected''' + """Central disconnected""" self._conn_handle, _, _, = data self._connected() elif event == 2: - '''Central disconnected''' + """Central disconnected""" conn_handle, _, _, = data if conn_handle == self._conn_handle: self.conn_handle = None @@ -84,7 +84,7 @@ def _ble_irq(self, event, data): self._advertiser() elif event == 3:#4: - '''New message received''' + """New message received""" #conn_handle, value_handle, = data #print("...") buffer = self.ble.gatts_read(self._rx) diff --git a/dumbdisplay/ddio_inet.py b/dumbdisplay/ddio_inet.py index 8dde6b6..c685186 100644 --- a/dumbdisplay/ddio_inet.py +++ b/dumbdisplay/ddio_inet.py @@ -3,7 +3,7 @@ import socket class DDIOInet(DDIOSocket): - '''Python Internet connection''' + """Python Internet connection""" def __init__(self, port = DD_DEF_PORT): super().__init__(port) self.ip = self._get_ip() diff --git a/dumbdisplay/ddlayer.py b/dumbdisplay/ddlayer.py index 1ca7438..28c6151 100644 --- a/dumbdisplay/ddlayer.py +++ b/dumbdisplay/ddlayer.py @@ -37,9 +37,9 @@ def _DD_COLOR_ARG(c): class DDFeedback: - ''' + """ type: can be "click", "doubleclick", "longpress" - ''' + """ def __init__(self, fb_type: str, x: int, y: int): self.type = fb_type self.x = x @@ -54,28 +54,28 @@ def __init__(self, dd: 'DumbDisplayImpl', layer_id: str): #self.customData = "" dd._onCreatedLayer(self) def visibility(self, visible: bool): - '''set layer visibility''' + """set layer visibility""" self.dd._sendCommand(self.layer_id, "visible", _DD_BOOL_ARG(visible)) def disabled(self, disabled: bool = True): - '''set layer disabled''' + """set layer disabled""" self.dd._sendCommand(self.layer_id, "disabled", _DD_BOOL_ARG(disabled)) def transparent(self, transparent: bool): self.dd._sendCommand(self.layer_id, "transparent", _DD_BOOL_ARG(transparent)) def opacity(self, opacity: int): - '''set layer opacity percentage -- 0 to 100''' + """set layer opacity percentage -- 0 to 100""" self.dd._sendCommand(self.layer_id, "opacity", str(opacity)) def alpha(self, alpha: int): - '''set layer alpha -- 0 to 255''' + """set layer alpha -- 0 to 255""" self.dd._sendCommand(self.layer_id, "alpha", str(alpha)) def border(self, size: float, color: str, shape: str = "flat", extra_size: float = 0): - ''' + """ :param size: unit depends on the layer type: - LcdLayer; each character is composed of pixels - 7SegmentRowLayer; each 7-segment is composed of fixed 220 x 320 pixels - LedGridLayer; a LED is considered as a pixel :param shape: can be "flat", "round", "raised" or "sunken" :param extra_size just added to size; however if shape is "round", it affects the "roundness" - ''' + """ if _DD_FLOAT_IS_ZERO(extra_size): # was type(extra_size) == int and extra_size == 0 self.dd._sendCommand(self.layer_id, "border", _DD_FLOAT_ARG(size), _DD_COLOR_ARG(color), shape) else: @@ -83,7 +83,7 @@ def border(self, size: float, color: str, shape: str = "flat", extra_size: float def noBorder(self): self.dd._sendCommand(self.layer_id, "border") def padding(self, left: float, top: float = None, right: float = None, bottom: float = None): - '''see border() for size unit''' + """see border() for size unit""" if top is None and right is None and bottom is None: self.dd._sendCommand(self.layer_id, "padding", _DD_FLOAT_ARG(left)) else: @@ -97,7 +97,7 @@ def padding(self, left: float, top: float = None, right: float = None, bottom: f def noPadding(self): self.dd._sendCommand(self.layer_id, "padding") def margin(self, left: float, top: float = None, right: float = None, bottom: float = None): - '''see border() for size unit''' + """see border() for size unit""" if top is None and right is None and bottom is None: self.dd._sendCommand(self.layer_id, "margin", _DD_FLOAT_ARG(left)) else: @@ -111,10 +111,10 @@ def margin(self, left: float, top: float = None, right: float = None, bottom: fl def noMargin(self): self.dd._sendCommand(self.layer_id, "margin") def backgroundColor(self, color, opacity = 100): - ''' + """ :param opacity: background opacity (0 - 100) :return: - ''' + """ if opacity < 100: self.dd._sendCommand(self.layer_id, "bgcolor", _DD_COLOR_ARG(color), str(opacity)) else: @@ -122,7 +122,7 @@ def backgroundColor(self, color, opacity = 100): def noBackgroundColor(self): self.dd._sendCommand(self.layer_id, "nobgcolor") def clear(self): - '''clear the layer''' + """clear the layer""" self.dd._sendCommand(self.layer_id, "clear") def flash(self): self.dd._sendCommand(self.layer_id, "flash") @@ -131,7 +131,7 @@ def flashArea(self, x, y): # def writeComment(self, comment): # self.dd.writeComment(comment) def enableFeedback(self, auto_feedback_method: str = "", feedback_handler = None, allowed_feedback_types: str = ""): - ''' + """ :param auto_feedback_method: . "" -- no auto feedback flash (need explicitly call to flashArea() or flash() when detected feedback) . "f" -- flash the default way (layer + border) @@ -144,19 +144,19 @@ def enableFeedback(self, auto_feedback_method: str = "", feedback_handler = None . type -- "click" . x, y -- the "area" on the layer where was clicked . *args -- for future-proofing - ''' + """ self._feedback_handler = feedback_handler # TODO: have a feedback_handler_ex that will be called with kwargs self._feedbacks = [] self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(True), auto_feedback_method, allowed_feedback_types) def disableFeedback(self): - '''disable feedback''' + """disable feedback""" self.dd._sendCommand(self.layer_id, "feedback", _DD_BOOL_ARG(False)) self._feedback_handler = None def getFeedback(self) -> DDFeedback: - ''' + """ get any feedback as the structure {type, x, y} :return: None if none (or when "handler" set) - ''' + """ self.dd._checkForFeedback() if len(self._feedbacks) > 0: (type, x, y) = self._feedbacks.pop(0) @@ -167,13 +167,13 @@ def getFeedback(self) -> DDFeedback: # self._feedback_handler = feedback_handler # self._shipFeedbacks() def reorder(self, how: str): - ''' + """ recorder the layer :param how: can be "T" for top; or "B" for bottom; "U" for up; or "D" for down - ''' + """ self.dd._reorderLayer(self.layer_id, how) def release(self): - '''release the layer''' + """release the layer""" self.dd._deleteLayer(self.layer_id) self.dd._onDeletedLayer(self.layer_id) self.dd = None diff --git a/dumbdisplay/ddlayer_7segrow.py b/dumbdisplay/ddlayer_7segrow.py index d459fbc..05be562 100644 --- a/dumbdisplay/ddlayer_7segrow.py +++ b/dumbdisplay/ddlayer_7segrow.py @@ -3,47 +3,49 @@ from .ddlayer import _DD_COLOR_ARG class DDLayer7SegmentRow(DDLayer): - '''A row of 7 Segments''' + """A row of 7 Segments""" def __init__(self, dd: DumbDisplayImpl, digit_count = 1): - ''' + """ :param dd: DumbDisplay object :param digit_count: number of digits / # rows - ''' + """ layer_id = dd._createLayer("7segrow", str(digit_count)) super().__init__(dd, layer_id) def segmentColor(self, color): - '''set segment color''' + """set segment color""" self.dd._sendCommand(self.layer_id, "segcolor", _DD_COLOR_ARG(color)) def turnOn(self, segments, digit_idx = 0): - ''' + """ turn on one or more segments :param segments: each character represents a segment to turn off - 'a', 'b', 'c', 'd', 'e', 'f', 'g', '.' - ''' + """ self.dd._sendCommand(self.layer_id, "segon", segments, str(digit_idx)) def turnOff(self, segments, digit_idx = 0): - '''turn off one or more segments -- see turnOn()''' + """turn off one or more segments -- see turnOn()""" self.dd._sendCommand(self.layer_id, "segoff", segments, str(digit_idx)) def setOn(self, segments = "", digit_idx = 0): - '''like turnOn(), exception that the digit will be cleared first;''' - '''empty segments basically means turn all segments of the digit off''' + """ + like turnOn(), except that the digit will be cleared first; + empty segments basically means turn all segments of the digit off + """ self.dd._sendCommand(self.layer_id, "setsegon", segments, str(digit_idx)) def showDigit(self, digit: int, digit_idx: int = 0): - ''' + """ show a digit - ''' + """ self.dd._sendCommand(self.layer_id, "showdigit", str(digit) , str(digit_idx)) def showNumber(self, number): - '''show number''' + """show number""" self.dd._sendCommand(self.layer_id, "shownumber", str(number)) def showHexNumber(self, number): - '''show HEX number''' + """show HEX number""" self.dd._sendCommand(self.layer_id, "showhex", str(number)) def showFormatted(self, formatted): - ''' + """ show formatted number (even number with hex digits) -- e.g. "12.00", "00.34", "-.12", "0ff" - ''' + """ self.dd._sendCommand(self.layer_id, "showformatted", str(formatted)) diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index 799b261..e3b7df5 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -12,10 +12,10 @@ def setCursor(self, x, y): def moveCursorBy(self, by_x, by_y): self.dd._sendCommand(self.layer_id, "movecursorby", str(by_x), str(by_y)) def setTextColor(self, color, bg_color = ""): - ''' + """ set the text color (i.e. pen color) - :param bg_color: "" means means default - ''' + :param bg_color: "" means default + """ self.dd._sendCommand(self.layer_id, "textcolor", _DD_COLOR_ARG(color), _DD_COLOR_ARG(bg_color)) def setTextSize(self, size): self.dd._sendCommand(self.layer_id, "textsize", str(size)) @@ -33,23 +33,23 @@ def println(self, text = ""): def fillScreen(self, color): self.dd._sendCommand(self.layer_id, "fillscreen", _DD_COLOR_ARG(color)) def drawChar(self, x, y, char, color, bg_color = "", size = 0): - ''' + """ :param char: char to draw :param bg_color: "" means default :param size: 0 means defajult - ''' + """ self.dd._sendCommand(self.layer_id, "drawchar", str(x), str(y), _DD_COLOR_ARG(color), _DD_COLOR_ARG(bg_color), str(size), char) def drawStr(self, x, y, string, color, bg_color = "", size = 0): - ''' + """ :param bg_color: "" means default :param size: 0 means default - ''' + """ self.dd._sendCommand(self.layer_id, "drawstr", str(x), str(y), _DD_COLOR_ARG(color), _DD_COLOR_ARG(bg_color), str(size), string) def drawTextLine(self, text: str, y: int, align: str = "L", color: str = "", bgColor: str = "", size: int = 0): - ''' + """ similar to drawStr(), but draw string as a text line at (0, y) with alignment option :param align 'L', 'C', or 'R' - ''' + """ self.dd._sendCommand(self.layer_id, "drawtextline", _DD_INT_ARG(y), align, color, bgColor, _DD_INT_ARG(size), text) def drawPixel(self, x, y, color): self.dd._sendCommand(self.layer_id, "drawpixel", str(x), str(y), _DD_COLOR_ARG(color)) @@ -65,11 +65,11 @@ def drawRoundRect(self, x, y, w, h, r, color, filled = False): self.dd._sendCommand(self.layer_id, "drawroundrect", str(x), str(y), str(w), str(h), str(r), _DD_COLOR_ARG(color), _DD_BOOL_ARG(filled)) def drawImageFile(self, imageFileName: str, x: int = 0, y: int = 0, w: int = 0, h: int = 0, options = ""): - ''' + """ draw image file in cache (if not already loaded to cache, load it) :param x,y: position of the left-top corner :param w,h: image size to scale to; if both 0, will not scale, if any 0, will scale keeping aspect ratio - ''' + """ if x == 0 and y == 0 and w == 0 and h == 0: self.dd._sendCommand(self.layer_id, "drawimagefile", imageFileName, options) elif x == 0 and y == 0: @@ -83,18 +83,18 @@ def drawImageFile(self, imageFileName: str, x: int = 0, y: int = 0, w: int = 0, else: self.dd._sendCommand(self.layer_id, "drawimagefile", imageFileName, _DD_INT_ARG(x), _DD_INT_ARG(y), _DD_INT_ARG(w), _DD_INT_ARG(h), options) def drawImageFileFit(self, imageFileName: str, x: int = 0, y: int = 0, w: int = 0, h: int = 0, options: str = ""): - ''' + """ draw image file in cache (if not already loaded to cache, load it) :param x,y,w,h: rect to draw the image; 0 means the default value :param options (e.g. "LB"): left align "L"; right align "R"; top align "T"; bottom align "B"; default to fit centered - ''' + """ if x == 0 and y == 0 and w == 0 and h == 0 and options == "": self.dd._sendCommand(self.layer_id, "drawimagefilefit", imageFileName) else: self.dd._sendCommand(self.layer_id, "drawimagefilefit", imageFileName, _DD_INT_ARG(x), _DD_INT_ARG(y), _DD_INT_ARG(w), _DD_INT_ARG(h), options) def forward(self, distance): - '''draw forward relative to cursor position''' + """draw forward relative to cursor position""" self.dd._sendCommand(self.layer_id, "fd", str(distance)) def leftTurn(self, angle): self.dd._sendCommand(self.layer_id, "lt", str(angle)) @@ -121,44 +121,44 @@ def triangle(self, side1, angle, side2): def isoscelesTriangle(self, side, angle): self.dd._sendCommand(self.layer_id, "trisas", str(side), str(angle)) def polygon(self, side, vertex_count): - '''draw polygon given side and vertex count''' + """draw polygon given side and vertex count""" self.dd._sendCommand(self.layer_id, "poly", str(side), str(vertex_count)) def centeredPolygon(self, radius, vertex_count, inside = False): - ''' + """ draw polygon enclosed in an imaginary centered circle - given circle radius and vertex count - whether inside the imaginary circle or outside of it - ''' + """ self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", str(radius), str(vertex_count)) def write(self, text, draw = False): - ''' + """ write text; will not auto wrap :param draw: draw means draw the text (honor the heading direction) - ''' + """ self.dd._sendCommand(self.layer_id, "drawtext" if draw else "write", text) class DDLayerGraphical(DDLayerGraphicalBase): def __init__(self, dd: DumbDisplayImpl, width: int, height: int): - ''' + """ :param dd: DumbDisplay object :param width: width :param height: height - ''' + """ layer_id = dd._createLayer("graphical", _DD_INT_ARG(width), _DD_INT_ARG(height)) super().__init__(dd, layer_id) class DDRootLayer(DDLayerGraphicalBase): - ''' + """ it is the root layer of the DumbDisplay; it is basically a graphical layer - ''' + """ def __init__(self, dd: DumbDisplayImpl, width: int, height: int, contained_alignment: str = ""): - ''' + """ set the root layer of the DumbDisplay; it is basically a graphical layer :param dd: DumbDisplay object :param width: width :param height: height - ''' + """ layer_id = dd._setRootLayer(width, height, contained_alignment) super().__init__(dd, layer_id) diff --git a/dumbdisplay/ddlayer_joystick.py b/dumbdisplay/ddlayer_joystick.py index 298299f..2daaf4a 100644 --- a/dumbdisplay/ddlayer_joystick.py +++ b/dumbdisplay/ddlayer_joystick.py @@ -4,35 +4,35 @@ class DDLayerJoystick(DDLayer): - '''Virtual joystick''' - def __init__(self, dd: DumbDisplayImpl, maxStickValue: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): - ''' + """Virtual joystick""" + def __init__(self, dd: DumbDisplayImpl, max_stick_value: int = 1023, directions: str = "", stickLookScaleFactor: float = 1.0): + """ :param dd: DumbDisplay object - :param maxStickValue: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 + :param max_stick_value: the max value of the stick; e.g. 255 or 1023 (the default); min is 15 :param directions: "lr" or "hori": left-to-right; "tb" or "vert": top-to-bottom; "rl": right-to-left; "bt": bottom-to-top; use "+" combines the above like "lr+tb" to mean both directions; "" the same as "lr+tb" :param stickLookScaleFactor: the scaling factor of the stick (UI); 1 by default - ''' - layer_id = dd._createLayer("joystick", _DD_INT_ARG(maxStickValue), directions, _DD_FLOAT_ARG(stickLookScaleFactor)) + """ + layer_id = dd._createLayer("joystick", _DD_INT_ARG(max_stick_value), directions, _DD_FLOAT_ARG(stickLookScaleFactor)) super().__init__(dd, layer_id) def autoRecenter(self, auto_recenter: bool = True): self.dd._sendCommand(self.layer_id, "autorecenter", _DD_BOOL_ARG(auto_recenter)) def colors(self, stick_color: str = "", stick_outline_color: str = "", socket_color: str = "", socket_outline_color = ""): self.dd._sendCommand(self.layer_id, "colors", _DD_COLOR_ARG(stick_color), _DD_COLOR_ARG(stick_outline_color), _DD_COLOR_ARG(socket_color), _DD_COLOR_ARG(socket_outline_color)) def moveToPos(self, x: int, y: int, send_feedback: bool = False): - ''' + """ move joystick position (if joystick is single directional, will only move in the movable direction) :param bg_color: "" means default :param x: x to move to :param y: y to move to :param send_feedback: if true, will send "feedback" for the move (regardless of the current position) - ''' + """ self.dd._sendCommand(self.layer_id, "movetopos", str(x), str(y), _DD_BOOL_ARG(send_feedback)) def moveToCenter(self, send_feedback: bool = False): - ''' + """ move joystick to the center :param send_feedback: if true, will send "feedback" for the move (regardless of the current position) - ''' + """ self.dd._sendCommand(self.layer_id, "movetocenter", _DD_BOOL_ARG(send_feedback)) diff --git a/dumbdisplay/ddlayer_lcd.py b/dumbdisplay/ddlayer_lcd.py index a5e4dbc..6f192df 100644 --- a/dumbdisplay/ddlayer_lcd.py +++ b/dumbdisplay/ddlayer_lcd.py @@ -5,15 +5,15 @@ class DDLayerLcd(DDLayer): - '''LCD''' + """LCD""" def __init__(self, dd: DumbDisplayImpl, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''): - ''' + """ :param dd: DumbDisplay object :param col_count: number of columns :param row_count: numer of rows :param char_height: char height :param font_name: font name - ''' + """ layer_id = dd._createLayer("lcd", str(col_count), str(row_count), str(char_height), font_name) super().__init__(dd, layer_id) def print(self, text: str): @@ -39,19 +39,23 @@ def scrollDisplayLeft(self): def scrollDisplayRight(self): self.dd._sendCommand(self.layer_id, "scrollright") def writeLine(self, text: str, y: int = 0, align: str = "L"): - '''write text as a line, with alignment "L", "C", or "R"''' + """ + write text as a line, with alignment 'L', 'C', or 'R' + """ self.dd._sendCommand(self.layer_id, "writeline", str(y), align, str(text)) def writeCenteredLine(self, text: str, y: int = 0): - '''write text as a line, with align "centered"''' + """ + write text as a line, with align "centered" + """ self.dd._sendCommand(self.layer_id, "writeline", str(y), "C", str(text)) def pixelColor(self, color): - '''set pixel color''' + """set pixel color""" self.dd._sendCommand(self.layer_id, "pixelcolor", _DD_COLOR_ARG(color)) def bgPixelColor(self, color): - '''set "background" (off) pixel color''' + """set "background" (off) pixel color""" self.dd._sendCommand(self.layer_id, "bgpixelcolor", _DD_COLOR_ARG(color)) def noBgPixelColor(self): - '''set no "background" (off) pixel color''' + """set no "background" (off) pixel color""" self.dd._sendCommand(self.layer_id, "bgpixelcolor") diff --git a/dumbdisplay/ddlayer_ledgrid.py b/dumbdisplay/ddlayer_ledgrid.py index 235bdea..d8ffe2c 100644 --- a/dumbdisplay/ddlayer_ledgrid.py +++ b/dumbdisplay/ddlayer_ledgrid.py @@ -4,40 +4,44 @@ class DDLayerLedGrid(DDLayer): - '''Grid of LEDs''' + """Grid of LEDs""" def __init__(self, dd: DumbDisplayImpl, col_count: int = 1, row_count: int = 1, sub_col_count: int = 1, sub_row_count: int = 1): - ''' + """ :param dd: DumbDisplay object :param col_count: grid # columns :param row_count: grid # rows :param sub_col_count: # sub columns of each cell :param sub_row_count: # sub rows of each cell - ''' + """ layer_id = dd._createLayer("ledgrid", str(col_count), str(row_count), str(sub_col_count), str(sub_row_count)) super().__init__(dd, layer_id) def turnOn(self, x = 0, y = 0): - '''turn on LED @ (x, y)''' + """turn on LED @ (x, y)""" self.dd._sendCommand(self.layer_id, "ledon", str(x), str(y)) def turnOff(self, x: int = 0, y: int = 0): - '''turn off LED @ (x, y)''' + """turn off LED @ (x, y)""" self.dd._sendCommand(self.layer_id, "ledoff", str(x), str(y)) def toggle(self, x = 0, y = 0): - '''toggle LED @ (x, y)''' + """toggle LED @ (x, y)""" self.dd._sendCommand(self.layer_id, "ledtoggle", str(x), str(y)) def turnOnEx(self, x: int = 0, y: int = 0, on_color =""): self.dd._sendCommand(self.layer_id, "ledonex", str(x), str(y), _DD_COLOR_ARG(on_color)) def horizontalBar(self, count: int, right_to_left: bool = False): - '''turn on LEDs to form a horizontal "bar"''' + """ + turn on LEDs to form a horizontal "bar" + """ self.dd._sendCommand(self.layer_id, "ledhoribar", str(count), str(right_to_left)) def verticalBar(self, count: int, bottom_to_top: bool = True): - '''turn on LEDs to form a vertical "bar"''' + """ + turn on LEDs to form a vertical "bar" + """ self.dd._sendCommand(self.layer_id, "ledvertbar", str(count), str(bottom_to_top)) def onColor(self, color): - '''set LED on color''' + """set LED on color""" self.dd._sendCommand(self.layer_id, "ledoncolor", _DD_COLOR_ARG(color)) def offColor(self, color): - '''set LED off color''' + """set LED off color""" self.dd._sendCommand(self.layer_id, "ledoffcolor", _DD_COLOR_ARG(color)) def noOffColor(self): - '''/* set no LED off color */''' + """ set no LED off color """ self.dd._sendCommand(self.layer_id, "ledoffcolor") diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index d41157f..79abdc8 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -7,12 +7,12 @@ class DDLayerMultiLevel(DDLayer): def __init__(self, dd: DumbDisplayImpl, layer_id: str): super().__init__(dd, layer_id) def addLevel(self, level_id: str, width: float = 0, height: float = 0, switch_to_it: bool = False): - ''' + """ add a level, optionally change its "opening" size :param level_id: level ID; cannot be DD_DEF_LAYER_LEVEL_ID - :param width: width width of the level "opening"; 0 means the maximum width (the width of the layer) - :param height: height height of the level "opening"; 0 means the maximum height (the height of the layer) - ''' + :param width: width of the level "opening"; 0 means the maximum width (the width of the layer) + :param height: height of the level "opening"; 0 means the maximum height (the height of the layer) + """ if _DD_FLOAT_IS_ZERO(width) and _DD_FLOAT_IS_ZERO(height): if switch_to_it: self.dd._sendCommand(self.layer_id, "addlevel", level_id, _DD_BOOL_ARG(switch_to_it)) @@ -21,12 +21,12 @@ def addLevel(self, level_id: str, width: float = 0, height: float = 0, switch_to else: self.dd._sendCommand(self.layer_id, "addlevel", level_id, _DD_FLOAT_ARG(width), _DD_FLOAT_ARG(height), _DD_BOOL_ARG(switch_to_it)) def addTopLevel(self, level_id: str, width: float = 0, height:float = 0, switchToIt:bool = False): - ''' + """ add top level -- like addLevel() but add to the top (i.e. will be drawn last) :param level_id: level ID; cannot be DD_DEF_LAYER_LEVEL_ID - :param width: width width of the level "opening"; 0 means the maximum width (the width of the layer) - :param height: height height of the level "opening"; 0 means the maximum height (the height of the layer) - ''' + :param width: width of the level "opening"; 0 means the maximum width (the width of the layer) + :param height: height of the level "opening"; 0 means the maximum height (the height of the layer) + """ if _DD_FLOAT_IS_ZERO(width) and _DD_FLOAT_IS_ZERO(height): if switchToIt: self.dd._sendCommand(self.layer_id, "addtoplevel", level_id, _DD_BOOL_ARG(switchToIt)) @@ -35,119 +35,119 @@ def addTopLevel(self, level_id: str, width: float = 0, height:float = 0, switchT else: self.dd._sendCommand(self.layer_id, "aaddtoplevel", level_id, _DD_FLOAT_ARG(width), _DD_FLOAT_ARG(height), _DD_BOOL_ARG(switchToIt)) def switchLevel(self, level_id: str, add_if_missing: bool = True): - ''' + """ switch to a different level (which is like a sub-layer), making it the current level :param add_if_missing: if true, add the level if it is missing - ''' + """ self.dd._sendCommand(self.layer_id, "switchlevel", level_id, _DD_BOOL_ARG(add_if_missing)) def pushLevel(self): - ''' + """ push the current level onto the level stack, to be pop with popLevel() - ''' + """ self.dd._sendCommand(self.layer_id, "pushlevel") def pushLevelAndSwitchTo(self, switch_tolevel_id: str, add_if_missing: bool = True): - ''' + """ push the current level onto the level stack, to be pop with popLevel() :param switch_tolevel_id switch to level ID (after pushing current level) - ''' + """ self.dd._sendCommand2(self.layer_id, "pushlevel", switch_tolevel_id, _DD_BOOL_ARG(add_if_missing)) def popLevel(self): - ''' + """ pop a level from the level stack and make it the current level - ''' + """ self.dd._sendCommand2(self.layer_id, "poplevel") def levelOpacity(self, opacity: int): - ''' + """ set the opacity of the current level :param opacity background opacity (0 - 100) - ''' + """ self.dd._sendCommand(self.layer_id, "levelopacity", _DD_INT_ARG(opacity)) def levelTransparent(self, transparent: bool): - ''' + """ set whether level is transparent - ''' + """ self.dd._sendCommand(self.layer_id, "leveltransparent", _DD_INT_ARG(transparent)) def setLevelAnchor(self, x: float, y: float, reach_in_millis: int = 0): - ''' + """ set the anchor of the level; note that level anchor is the top-left corner of the level "opening" - ''' + """ if reach_in_millis > 0: self.dd._sendCommand(self.layer_id, "setlevelanchor", _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y), _DD_INT_ARG(reach_in_millis)) else: self.dd._sendCommand(self.layer_id, "setlevelanchor", _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y)) def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): - ''' + """ move the level anchor - ''' + """ if reach_in_millis > 0: self.dd._sendCommand(self.layer_id, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); else: self.dd._sendCommand(self.layer_id, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); def registerLevelBackground(self, background_id: str, background_image_name: str, draw_background_options: str = ""): - ''' + """ register an image for setting as level's background :param background_id id to identify the background -- see setLevelBackground() :param background_image_name name of the image can be a series of images like dumbdisplay_##0-7##.png (for dumbdisplay_0.png to dumbdisplay_7.png) which can be used for animation with animateLevelBackground() :param drawBackgroundOptions options for drawing the background; same means as the option param of GraphicalDDLayer::drawImageFiler() - ''' + """ self.dd._sendCommand(self.layer_id, "reglevelbg", background_id, background_image_name, draw_background_options) def exportLevelAsRegisteredBackground(self, background_id: str, replace: bool = True): - ''' + """ experimental: export the current level as a registered background image -- see exportLevelsAsImage() and registerLevelBackground() :param background_id id to identify the background -- see setLevelBackground() :param replace if true (default), replace the existing registered background image with the same id; if false, will add as an item of background image series that can be used for animation with animateLevelBackground() - ''' + """ self.dd.sendCommand(self.layer_id, "explevelasregbg", background_id, _DD_BOOL_ARG(replace)); def setLevelBackground(self, background_id: str, background_image_name: str = "", draw_background_options: str = ""): - ''' + """ set a registered background image as the current level's background :param background_id :param background_image_name if not registered, the name of the image to register; can be a series of images like dumbdisplay_##0-7##.png (for dumbdisplay_0.png to dumbdisplay_7.png) which can be used for animation with animateLevelBackground() :param draw_background_options if not registered, the options for drawing the background - ''' + """ if background_image_name == "": self.dd._sendCommand(self.layer_id, "setlevelbg", background_id) else: self.dd._sendCommand(self.layer_id, "setlevelbg", background_id, background_image_name, draw_background_options) def setLevelNoBackground(self): - ''' + """ set that the current level uses no background image - ''' + """ self.dd._sendCommand(self.layer_id, "setlevelnobg") def animateLevelBackground(self, fps: float, reset: bool = True, options: str = ""): - ''' + """ start animate level background (if level background has a series of images) :param fps frames per second which is used to calculate the interval between the series of images - :param reset reset to the first image in the series (before start animation) + :param reset to the first image in the series (before start animation) param options can be "r" to reverse the order of the series of images - ''' + """ self.dd._sendCommand(self.layer_id, "anilevelbg", _DD_FLOAT_ARG(fps), _DD_BOOL_ARG(reset), options) def stopAnimateLevelBackground(self, reset: bool = True): - ''' + """ stop animate level background :param reset reset to the first image in the series - ''' + """ self.dd._sendCommand(self.layer_id, "stopanilevelbg", _DD_FLOAT_ARG(reset)) def reorderLevel(self, level_id: str, how: str): - ''' + """ reorder the specified level (by moving it in the z-order plane) :param how can be "T" for top; or "B" for bottom; "U" for up; or "D" for down - ''' + """ self.dd._sendCommand(self.layer_id, "reordlevel", level_id, how) def exportLevelsAsImage(self, image_file_name: str, cache_it_not_save: bool = False): - ''' + """ export (and save) the levels as an image (without the decorations of the layer like border) - ''' + """ self.dd._sendCommand(self.layer_id, "explevelsasimg", image_file_name, _DD_BOOL_ARG(cache_it_not_save)) def deleteLevel(self, level_id: str): - ''' + """ delete the specified level - ''' + """ self.dd._sendCommand(self.layer_id, "dellevel", level_id) diff --git a/dumbdisplay/ddlayer_plotter.py b/dumbdisplay/ddlayer_plotter.py index c2e31cd..884e4d6 100644 --- a/dumbdisplay/ddlayer_plotter.py +++ b/dumbdisplay/ddlayer_plotter.py @@ -3,24 +3,24 @@ class DDLayerPlotter(DDLayer): - '''Plotter''' + """Plotter""" def __init__(self, dd: DumbDisplayImpl, width: int, height: int, pixels_per_second: int = 10): - ''' + """ :param dd: DumbDisplay object :param width: width :param height: height :param pixels_per_second: # pixel to scroll per second - ''' + """ layer_id = dd._createLayer("plotterview", str(width), str(height), str(pixels_per_second)) super().__init__(dd, layer_id) def label(self, no_key_label = None, **key_label_pairs): - '''set labels of keys; if key has no label, the key will be the label''' + """set labels of keys; if key has no label, the key will be the label""" if no_key_label is not None: self.dd._sendCommand(self.layer_id, "label", "", str(no_key_label)) for (key, lab) in key_label_pairs.items(): self.dd._sendCommand(self.layer_id, "label", key, str(lab)) def set(self, no_key_value = None, **key_value_pairs): - '''set values with multiple key value pairs; value should be numeric''' + """set values with multiple key value pairs; value should be numeric""" params = [] if no_key_value is not None: params.append("") diff --git a/dumbdisplay/ddlayer_selection.py b/dumbdisplay/ddlayer_selection.py index aa0a4d1..1a45727 100644 --- a/dumbdisplay/ddlayer_selection.py +++ b/dumbdisplay/ddlayer_selection.py @@ -4,7 +4,7 @@ class DDLayerSelection(DDLayer): - '''Selection''' + """Selection""" def __init__(self, dd: DumbDisplayImpl, col_count: int = 16, row_count: int = 2, hori_selection_count: int = 1, vert_selection_count: int = 1, @@ -16,54 +16,54 @@ def __init__(self, dd: DumbDisplayImpl, _DD_BOOL_ARG(can_draw_dots), _DD_FLOAT_ARG(selection_border_size_char_height_factor)) super().__init__(dd, layer_id) def text(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0, align: str = "L"): - ''' + """ set a "selection" unit text (of y-th row) :param align 'L', 'C', or 'R' - ''' + """ self.dd._sendCommand(self.layer_id, "text", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), align, text) def textCentered(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0): - ''' + """ set a "selection" unit centered text (of y-th row) - ''' + """ self.dd._sendCommand(self.layer_id, "text", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), "C", text) def textRightAligned(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0): - ''' + """ set a "selection" unit right-aligned text (of y-th row) - ''' + """ self.dd._sendCommand(self.layer_id, "text", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), "R", text) def unselectedText(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0, align: str = "L"): - ''' + """ set a "selection" unit text (of y-th row) when unselected (it defaults to the same text as selected) :param align 'L', 'C', or 'R' - ''' + """ self.dd._sendCommand(self.layer_id, "unselectedtext", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), align, text) def unselectedTextCentered(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0): - ''' + """ set a "selection" unit centered text (of y-th row) when unselected (it defaults to the same text as selected) - ''' + """ self.dd._sendCommand(self.layer_id, "unselectedtext", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), "C", text) def unselectedTextRightAligned(self, text: str, y: int = 0, hori_selection_idx: int = 0, vert_selection_idx: int = 0): - ''' + """ set a "selection" unit right-aligned text (of y-th row) when unselected (it defaults to the same text as selected) - ''' + """ self.dd._sendCommand(self.layer_id, "unselectedtext", _DD_INT_ARG(y), _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), "R", text) def select(self, hori_selection_idx: int = 0, vert_selection_idx: int = 0, deselect_the_others: bool = True): - ''' + """ select a "selection" unit :param deselect_the_others: if True, deselect the others - ''' + """ self.dd._sendCommand(self.layer_id, "select", _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), _DD_BOOL_ARG(deselect_the_others)) def deselect(self, hori_selection_idx: int = 0, vert_selection_idx: int = 0, select_the_others: bool = False): - ''' + """ deselect a "selection" unit :param select_the_others: if True, select the others - ''' + """ self.dd._sendCommand(self.layer_id, "deselect", _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), _DD_BOOL_ARG(select_the_others)) def selected(self, selected: bool, hori_selection_idx: int = 0, vert_selection_idx: int = 0, reverse_the_others: bool = False): - ''' + """ set a "selection" unit selected or not (combination of select() and deselect()) :param reverse_the_others: if True, reverse the others - ''' + """ if selected: self.dd._sendCommand(self.layer_id, "select", _DD_INT_ARG(hori_selection_idx), _DD_INT_ARG(vert_selection_idx), _DD_BOOL_ARG(reverse_the_others)) else: diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 95ef457..9f9802b 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -113,7 +113,7 @@ def oval(self, width: int, height: int, centered: bool = False): """draw oval; centered or not""" self.dd._sendCommand(self.layer_id, "coval" if centered else "oval", _DD_INT_ARG(width), _DD_INT_ARG(height)) def arc(self, width: int, height: int, start_angle: int, sweep_angle: int, centered: bool = False): - '''draw arc; centered or not''' + """draw arc; centered or not""" self.dd._sendCommand(self.layer_id, "carc" if centered else "arc", _DD_INT_ARG(width), _DD_INT_ARG(height), _DD_INT_ARG(start_angle), _DD_INT_ARG(sweep_angle)) def triangle(self, side1: int, angle: int, side2: int): """draw triangle (SAS)""" diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py index 12663bb..ee2bcbc 100644 --- a/dumbdisplay/dumbdisplay.py +++ b/dumbdisplay/dumbdisplay.py @@ -13,10 +13,10 @@ class DDAutoPin: def __init__(self, orientation: str, *layers): - ''' + """ :param orientation: H or V or S :param layers: layer or "pinner" - ''' + """ self.orientation = orientation self.layers = layers def build(self) -> str: @@ -65,17 +65,17 @@ def __init__(self, io: DDInputOutput = None, reset_machine_when_failed_to_send_c self.passive_state = None # None; "cing" (connecting); "c" (connected); "nc" (not connected) # def debugSetup(self, debug_led_pin): - # '''setup debug use flashing LED pin number''' + # """setup debug use flashing LED pin number""" # if _DD_HAS_LED: # self.debug_led = Pin(debug_led_pin, Pin.OUT) def connect(self): - '''explicit connect''' + """explicit connect""" self._connect() def connectPassive(self) -> (bool, bool): - ''' + """ will use a thread to connect :return: (connected, reconnecting) ... reconnecting is True when connected but detected connection loss - ''' + """ if self.passive_state is None: self.passive_state = "cing" try: @@ -100,31 +100,31 @@ def isReconnecting(self) -> bool: iop = self._connected_iop return iop is not None and iop.reconnecting def autoPin(self, orientation: str = 'V'): - ''' + """ auto pin layers :param orientation: H or V - ''' + """ layout_spec = str(orientation) + '(*)' self.configAutoPin(layout_spec) def configAutoPin(self, layout_spec: str = "V(*)"): - ''' + """ configure "auto pinning of layers" with the layer spec provided - horizontal: H(*) - vertical: V(*) - or nested, like H(0+V(1+2)+3) - where 0/1/2/3 are the layer ids - ''' + """ self._connect() self._sendCommand(None, "CFGAP", layout_spec) def addRemainingAutoPinConfig(self, rest_layout_spec: str): - ''' + """ add REST "auto pinning" spec for layers not already associated into existing "auto pinning" config - ''' + """ self._sendCommand(None, "ADDRESTAP", rest_layout_spec) def deleteAllRemainingAutoPinConfigs(self, rest_layout_spec: str): - ''' + """ delete all REST "auto pinning" specs - ''' + """ self._sendCommand(None, "ADDRESTAP", rest_layout_spec) def configPinFrame(self, x_unit_count: int, y_unit_count: int): self._connect() @@ -154,16 +154,16 @@ def recordLayerCommands(self): def playbackLayerCommands(self): self._sendCommand(None, "PLAYC") def backgroundColor(self, color: str): - '''set DD background color with common "color name"''' + """set DD background color with common "color name""" self._connect() self._sendCommand(None, "BGC", color) def writeComment(self, comment: str): - '''write out a comment to DD''' + """write out a comment to DD""" self._connect() self._sendCommand(None, '// ' + comment) print("# " + comment) def log(self, log_msg: str, is_error: bool = False): - '''log to DD''' + """log to DD""" if is_error: print("X " + log_msg) else: @@ -174,16 +174,16 @@ def log(self, log_msg: str, is_error: bool = False): else: self._sendCommand(None, '// ' + log_msg) def recordLayerCommands(self): - ''' + """ start recording layer commands (of any layers) and sort of freeze the display, until playback - ''' + """ self._sendCommand(None, "RECC") def playbackLayerCommands(self): - '''playback recorded commands (unfreeze the display)''' + """playback recorded commands (unfreeze the display)""" self._sendCommand(None, "PLAYC") def release(self): - '''release it''' + """release it""" super().release() def tone(self, freq: int, duration: int): diff --git a/dumbdisplay_examples/sliding_puzzle/sliding_puzzle_app.py b/dumbdisplay_examples/sliding_puzzle/sliding_puzzle_app.py index 2f35e08..92726fb 100644 --- a/dumbdisplay_examples/sliding_puzzle/sliding_puzzle_app.py +++ b/dumbdisplay_examples/sliding_puzzle/sliding_puzzle_app.py @@ -15,9 +15,9 @@ class SlidingPuzzleApp: def __init__(self, dd: DumbDisplay = create_example_wifi_dd(), tile_count: int = DEF_TILE_COUNT, suggest_move_from_dir_func = None): - ''' + """ :param suggest_move_from_dir_func: if not None, a function that access BoardManager and returns the next move (0 / 1 / 2 / 3) - ''' + """ self.tile_count = tile_count self.tile_size = BOARD_SIZE / tile_count self.suggest_move_from_dir_func = suggest_move_from_dir_func @@ -393,9 +393,9 @@ def posToHoleTileFromIdxes(self, x: int, y: int) -> (int, int, int, bool): def showHideHoleTile(self, show: bool): - ''' + """ show / hide the hole tile, which might not be in position - ''' + """ hole_tile_id = self.board_manager.board_tiles[self.board_manager.hole_tile_row_idx * self.tile_count + self.board_manager.hole_tile_col_idx] hole_tile_level_id = str(hole_tile_id) anchor_x = self.board_manager.hole_tile_col_idx * self.tile_size diff --git a/dumbdisplay_examples/utils.py b/dumbdisplay_examples/utils.py index 1d93896..daa6432 100644 --- a/dumbdisplay_examples/utils.py +++ b/dumbdisplay_examples/utils.py @@ -2,9 +2,9 @@ def create_example_wifi_dd(): - ''' + """ create example DumbDisplay ... if for MicroPython WiFi connection, assumes _my_secret.py - ''' + """ if DumbDisplay.runningWithMicropython(): # connect using WIFI: # assume a _my_secret.py Python script containing diff --git a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app.py b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app.py index e3e430c..6e80006 100644 --- a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app.py +++ b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app.py @@ -14,9 +14,9 @@ class SlidingPuzzleApp: def __init__(self, dd: DumbDisplay, tile_count: int = DEF_TILE_COUNT, suggest_move_from_dir_func = None): - ''' + """ :param suggest_move_from_dir_func: if not None, a function that access BoardManager and returns the next move (0 / 1 / 2 / 3) - ''' + """ self.tile_count = tile_count self.tile_size = BOARD_SIZE / tile_count self.suggest_move_from_dir_func = suggest_move_from_dir_func @@ -392,9 +392,9 @@ def posToHoleTileFromIdxes(self, x: int, y: int) -> (int, int, int, bool): def showHideHoleTile(self, show: bool): - ''' + """ show / hide the hole tile, which might not be in position - ''' + """ hole_tile_id = self.board_manager.board_tiles[self.board_manager.hole_tile_row_idx * self.tile_count + self.board_manager.hole_tile_col_idx] hole_tile_level_id = str(hole_tile_id) anchor_x = self.board_manager.hole_tile_col_idx * self.tile_size diff --git a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_OLD.py b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_OLD.py index f98e9c3..e4bd3fc 100644 --- a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_OLD.py +++ b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_OLD.py @@ -432,9 +432,9 @@ def posToHoleTileFromIdxes(self, x: int, y: int) -> (int, int, int, bool): def showHideHoleTile(self, show: bool): - ''' + """ show / hide the hole tile, which might not be in position - ''' + """ #holeTileId = self.boardTileIds[self.holeTileRowIdx][self.holeTileColIdx] hole_tile_id = self.board_tiles[self.hole_tile_row_idx * self.tile_count + self.hole_tile_col_idx] hole_tile_level_id = str(hole_tile_id) diff --git a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_SIMPLE.py b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_SIMPLE.py index da7210c..3a5f1cf 100644 --- a/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_SIMPLE.py +++ b/experiments/OLD/OLD_examples/sliding_puzzle/sliding_puzzle_app_SIMPLE.py @@ -375,9 +375,9 @@ def posToHoleTileFromIdxes(self, x: int, y: int) -> (int, int, int, bool): def showHideHoleTile(self, show: bool): - ''' + """ show / hide the hole tile, which might not be in position - ''' + """ holeTileId = self.boardTileIds[self.holeTileRowIdx][self.holeTileColIdx] holeTileLevelId = str(holeTileId) anchorX = self.holeTileColIdx * TILE_SIZE diff --git a/experiments/OLD/testing/le.py b/experiments/OLD/testing/le.py index 3e52a9a..7a6a8f1 100644 --- a/experiments/OLD/testing/le.py +++ b/experiments/OLD/testing/le.py @@ -43,13 +43,13 @@ def _ble_irq(self, event, data): print("E:" + str(event)) if event == 1: - '''Central disconnected''' + """Central disconnected""" self.conn_handle, _, _, = data self._connected() #self.led(1) elif event == 2: - '''Central disconnected''' + """Central disconnected""" conn_handle, _, _, = data if conn_handle == self._conn_handle: self.conn_handle = None @@ -57,7 +57,7 @@ def _ble_irq(self, event, data): self._advertiser() elif event == 3:#4: - '''New message received''' + """New message received""" #conn_handle, value_handle, = data #print("...") diff --git a/experiments/picopio/pio_neo_blink.py b/experiments/picopio/pio_neo_blink.py index 301bf77..22cdc34 100644 --- a/experiments/picopio/pio_neo_blink.py +++ b/experiments/picopio/pio_neo_blink.py @@ -49,9 +49,9 @@ def neo_prog(): def ShowNeoPixels(*pixels): - ''' + """ each pixel is the tuple (r, g, b) - ''' + """ pixel_count = len(pixels) sm.put(pixel_count - 1) for i in range(pixel_count): diff --git a/experiments/testing/le.py b/experiments/testing/le.py index 3e52a9a..7a6a8f1 100644 --- a/experiments/testing/le.py +++ b/experiments/testing/le.py @@ -43,13 +43,13 @@ def _ble_irq(self, event, data): print("E:" + str(event)) if event == 1: - '''Central disconnected''' + """Central disconnected""" self.conn_handle, _, _, = data self._connected() #self.led(1) elif event == 2: - '''Central disconnected''' + """Central disconnected""" conn_handle, _, _, = data if conn_handle == self._conn_handle: self.conn_handle = None @@ -57,7 +57,7 @@ def _ble_irq(self, event, data): self._advertiser() elif event == 3:#4: - '''New message received''' + """New message received""" #conn_handle, value_handle, = data #print("...") diff --git a/samples/neopixels/main.py b/samples/neopixels/main.py index c9ba1bb..b33521f 100644 --- a/samples/neopixels/main.py +++ b/samples/neopixels/main.py @@ -51,9 +51,9 @@ def neo_prog(): sm.active(1) def ShowNeoPixels(*pixels): - ''' + """ each pixel is the tuple (r, g, b) - ''' + """ pixel_count = len(pixels) sm.put(pixel_count - 1) for i in range(pixel_count): From 80bf1d2ad05584d4287e03b9732a9d81a88918d8 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 00:07:53 +0800 Subject: [PATCH 33/76] working on TetrisOneBlockApp --- _dev_test.py | 4 +- dumbdisplay_examples/tetris_one_block/main.py | 14 ++ .../tetris_one_block/tetris_one_block.py | 237 ++++++++++++++++++ .../tetris_one_block/tetriz_one_block.py | 192 -------------- dumbdisplay_examples/utils.py | 35 +++ 5 files changed, 289 insertions(+), 193 deletions(-) create mode 100644 dumbdisplay_examples/tetris_one_block/main.py create mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block.py delete mode 100644 dumbdisplay_examples/tetris_one_block/tetriz_one_block.py diff --git a/_dev_test.py b/_dev_test.py index 37b697c..6b9d8a0 100644 --- a/_dev_test.py +++ b/_dev_test.py @@ -117,7 +117,7 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): if l is None: l = _setup(dd) # if freeze_for_steps > 0: - # dd.freezeDraw() + # dd.freezeDrawing() distance = 1 i = 0 else: @@ -127,6 +127,8 @@ def _loop(l: LayerTurtleTracked, i: int, distance: int): else: if i <= 300: if i < 300: + # if freeze_for_steps > 0 and i == 0: + # dd.freezeDrawing() _loop(l, i=i, distance=distance) if freeze_for_steps > 0: if (i + 1) % freeze_for_steps == 0: diff --git a/dumbdisplay_examples/tetris_one_block/main.py b/dumbdisplay_examples/tetris_one_block/main.py new file mode 100644 index 0000000..7576fbe --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/main.py @@ -0,0 +1,14 @@ +### +# this is just a sample entrypoint +### + +def sample_run(): + import random + from dumbdisplay_examples.tetris_one_block.tetris_one_block import TetrisOneBlockApp + print(f"*** Sample Run of TetrisOneBlockApp ***") + app = TetrisOneBlockApp() + app.run() + + +if __name__ == "__main__": + sample_run() \ No newline at end of file diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py new file mode 100644 index 0000000..be2bd43 --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -0,0 +1,237 @@ +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_WIDTH = 400 +_HEIGHT = 700 + + +_grid = [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] +_score_count = 0 + + +class _Shape(): + def __init__(self): + self.x = 5 + self.y = 0 + self.color = 4 + self.move = 'go' + + def move_right(self): + if self.x < 11 and self.move == 'go': + if _grid[self.y][self.x + 1]==0: + _grid[self.y][self.x]=0 + self.x += 1 + _grid[self.y][self.x] = self.color + + def move_left(self): + if self.x > 0 and self.move == 'go': + if _grid[self.y][self.x - 1]==0: + _grid[self.y][self.x]=0 + self.x -= 1 + _grid[self.y][self.x] = self.color + + +def _draw_grid(pen: LayerTurtle): + pen.clear() + top = 230 + left = -110 + colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', + 'purple'] + + for y in range(len(_grid)): # 24 rows + for x in range(len(_grid[0])): # 12 columns + screen_x = left + (x*20) # each turtle 20x20 pixels + screen_y = top - (y*20) + color_number = _grid[y][x] + if color_number == 0: + continue + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #self.pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def _check_grid(score: LayerTurtle): + global _score_count + # Check if each row is full: + for y in range(0,24): + is_full = True + y_erase = y + for x in range(0,12): + if _grid[y][x] == 0: + is_full = False + break + # Remove row and shift down + if is_full: + _score_count += 1 + score.clear() + score.write(f'Score: {_score_count}', align='center') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + _grid[y+1][x] = _grid[y][x] + + + +class TetrisOneBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + self.left_button: LayerLcd = None + self.right_button: LayerLcd = None + self.shape: _Shape = None + self.last_update_time = None + + def initializeDD(self): + + root = DDRootLayer(self.dd, _WIDTH, _HEIGHT) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + pen = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) + pen.penFilled() + + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + + self.root = root + self.score = score + self.pen = pen + self.left_button = left_button + self.right_button = right_button + + shape = _Shape() + _grid[shape.y][shape.x] = shape.color + + self.shape = shape + + self.drawGrid() + + def updateDD(self): + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) > 0.1 + if need_update: + self.update() + self.last_update_time = now + + def update(self): + # Move shape + # Stop if at the bottom + if self.shape.y == 23: + self.shape.move = 'stop' + self.checkGrid() + self.shape = _Shape() + + # Drop down one space if empty below + elif _grid[self.shape.y+1][self.shape.x] == 0: + _grid[self.shape.y][self.shape.x]=0 + self.shape.y += 1 + _grid[self.shape.y][self.shape.x] = self.shape.color + + # Stop if above another block + else: + self.shape.move = 'stop' + self.shape = _Shape() + self.checkGrid() + + # Had to place it here for upcoming shapes... + # win.onkey(shape.move_right, 'Right') + # win.onkey(shape.move_left, 'Left') + + + self.drawGrid() + #time.sleep(0.1) + + def drawGrid(self): + self.dd.freezeDrawing() + _draw_grid(pen=self.pen) + self.dd.unfreezeDrawing() + + + def checkGrid(self): + _check_grid(score=self.score) + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py b/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py deleted file mode 100644 index b39acb8..0000000 --- a/dumbdisplay_examples/tetris_one_block/tetriz_one_block.py +++ /dev/null @@ -1,192 +0,0 @@ -from dumbdisplay.core import * -from dumbdisplay.layer_graphical import DDRootLayer -from dumbdisplay.layer_turtle import LayerTurtle - - -_WIDTH = 400 -_HEIGHT = 700 -_PEN_FILED = True - - -_grid = [ - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [4,3,3,1,0,1,0,3,3,4,0,1], - [5,1,6,5,3,2,1,0,5,5,0,3], - [1,2,3,4,5,2,0,4,2,3,0,4], - [1,3,1,2,4,2,0,3,1,2,4,3], - [1,1,2,3,0,2,2,2,0,3,0,1], - [0,1,1,2,3,0,0,0,4,0,2,0], - [2,0,1,2,3,0,6,5,5,5,0,2] -] - - - - -class _Shape(): - def __init__(self, grid): - self.x = 5 - self.y = 0 - self.color = 4 - self.grid = grid - self.move = 'go' - - def move_right(self): - if self.x < 11 and self.move == 'go': - if _grid[self.y][self.x + 1]==0: - _grid[self.y][self.x]=0 - self.x += 1 - _grid[self.y][self.x] = self.color - - def move_left(self): - if self.x > 0 and self.move == 'go': - if _grid[self.y][self.x - 1]==0: - _grid[self.y][self.x]=0 - self.x -= 1 - _grid[self.y][self.x] = self.color - - -class TetrisOneBlockApp(): - def __init__(self, dd: DumbDisplay): - self.dd = dd - self.root: DDRootLayer = None - self.score: LayerTurtle = None - self.pen: LayerTurtle = None - self.score_count = 0 - - def run(self): - while True: - (connected, reconnecting) = self.dd.connectPassive() - if connected: - if self.root is None: - self.initializeDD() - elif reconnecting: - self.dd.masterReset() - self.root = None - else: - self.updateDD() - - def initializeDD(self): - - root = DDRootLayer(self.dd, _WIDTH, _HEIGHT) - root.border(5, "darkred", "round", 1) - root.backgroundColor("black") - - score = LayerTurtle(self.dd, _WIDTH, _HEIGHT) - score.penColor('red') - score.penUp() - #score.hideturtle() - score.goTo(60, -300) - #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) - score.setTextFont("Courier", 24) - score.write('Score: 0', 'C') - - border = LayerTurtle(self.dd, _WIDTH, _HEIGHT) - border.penSize(10) - border.penUp() - #border.hideturtle() - border.goTo(-130, 240) - border.penDown() - border.penColor('white') - border.rightTurn(90) - border.forward(490) # Down - border.leftTurn(90) - border.forward(260) # Right - border.leftTurn(90) - border.forward(490) # Up - border.penUp() - border.goTo(0,260) - #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) - border.setTextFont("Courier", 36) - border.write("TETRIS", "C") - - pen = LayerTurtle(self.dd, _WIDTH, _HEIGHT) - if _PEN_FILED: - pen.penFilled() - #pen.up() - # pen.speed(0) - # pen.shape('square') - # pen.shapesize(0.9, 0.9) - # pen.setundobuffer(None) - - self.root = root - self.score = score - self.pen = pen - - shape = _Shape(_grid) - _grid[shape.y][shape.x] = shape.color - - self.draw_grid(_grid) - - def updateDD(self): - pass - - def draw_grid(self, grid): - self.dd.freezeDrawing() - self.pen.clear() - top = 230 - left = -110 - colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', - 'purple'] - - for y in range(len(grid)): # 24 rows - for x in range(len(grid[0])): # 12 columns - screen_x = left + (x*20) # each turtle 20x20 pixels - screen_y = top - (y*20) - color_number = grid[y][x] - if color_number == 0: - continue - color = colors[color_number] - self.pen.penColor(color) - self.pen.goTo(screen_x, screen_y, with_pen=False) - #self.pen.stamp() - if _PEN_FILED: - self.pen.rectangle(18, 18, centered=True) - else: # TODO: thing of a faster way - self.pen.fillColor(color) - self.pen.beginFill() - self.pen.rectangle(18, 18, centered=True) - self.pen.endFill() - self.dd.unfreezeDrawing() - - - def check_grid(self): - #global score_count - # Check if each row is full: - for y in range(0,24): - is_full = True - y_erase = y - for x in range(0,12): - if _grid[y][x] == 0: - is_full = False - break - # Remove row and shift down - if is_full: - self.score_count += 1 - self.score.clear() - self.score.write(f'Score: {self.score_count}', align='center') - - for y in range(y_erase-1, -1, -1): - for x in range(0,12): - _grid[y+1][x] = _grid[y][x] - - -if __name__ == "__main__": - from dumbdisplay_examples.utils import create_example_wifi_dd - app = TetrisOneBlockApp(create_example_wifi_dd()) - app.run() diff --git a/dumbdisplay_examples/utils.py b/dumbdisplay_examples/utils.py index daa6432..5065553 100644 --- a/dumbdisplay_examples/utils.py +++ b/dumbdisplay_examples/utils.py @@ -18,3 +18,38 @@ def create_example_wifi_dd(): from dumbdisplay.io_inet import io4Inet dd = DumbDisplay(io4Inet()) return dd + + +class DDAppBase(): + def __init__(self, dd: DumbDisplay): + self.dd = dd + self.initialized = False + + def run(self): + self.setup() + while True: + self.loop() + + def setup(self): + pass + + def loop(self): + (connected, reconnecting) = self.dd.connectPassive() + if connected: + if not self.initialized: + self.initializeDD() + self.initialized = True + elif reconnecting: + self.dd.masterReset() + self.initialized = False + else: + self.updateDD() + + def initializeDD(self): + raise Exception("must implement initializeDD") + + def updateDD(self): + pass + + + From 31179fa1a2b746e2fed2dd36334dd4ac84030a9a Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 07:36:59 +0800 Subject: [PATCH 34/76] added to TetrisOneBlockApp "dirty" indicator --- .../tetris_one_block/tetris_one_block.py | 87 +++++++++++++------ 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index be2bd43..b5fcb3c 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -37,10 +37,45 @@ [0,1,1,2,3,0,0,0,4,0,2,0], [2,0,1,2,3,0,6,5,5,5,0,2] ] -_score_count = 0 +_grid_dirty = None -class _Shape(): +class GridRow(): + def __init__(self, row_idx): + self.row_idx = row_idx + def __len__(self): + return len(_grid[self.row_idx]) + def __getitem__(self, col_idx): + return _grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + _grid[self.row_idx][col_idx] = value + _grid_dirty[self.row_idx][col_idx] = True + +class Grid(): + def __init__(self): + global _grid_dirty + if _grid_dirty is None: + _grid_dirty = [] + for grid_row in _grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + _grid_dirty.append(grid_dirty_row) + + def __len__(self): + return len(_grid) + def __getitem__(self, row_idx): + return GridRow(row_idx) + def is_dirty(self, row_idx, col_idx): + return _grid_dirty[row_idx][col_idx] != 0 + #return _grid_dirty[row_idx][col_idx] + +grid = Grid() +score_count = 0 + + +class Shape(): def __init__(self): self.x = 5 self.y = 0 @@ -49,17 +84,17 @@ def __init__(self): def move_right(self): if self.x < 11 and self.move == 'go': - if _grid[self.y][self.x + 1]==0: - _grid[self.y][self.x]=0 + if grid[self.y][self.x + 1]==0: + grid[self.y][self.x]=0 self.x += 1 - _grid[self.y][self.x] = self.color + grid[self.y][self.x] = self.color def move_left(self): if self.x > 0 and self.move == 'go': - if _grid[self.y][self.x - 1]==0: - _grid[self.y][self.x]=0 + if grid[self.y][self.x - 1]==0: + grid[self.y][self.x]=0 self.x -= 1 - _grid[self.y][self.x] = self.color + grid[self.y][self.x] = self.color def _draw_grid(pen: LayerTurtle): @@ -69,13 +104,13 @@ def _draw_grid(pen: LayerTurtle): colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] - for y in range(len(_grid)): # 24 rows - for x in range(len(_grid[0])): # 12 columns + for y in range(len(grid)): # 24 rows + for x in range(len(grid[0])): # 12 columns screen_x = left + (x*20) # each turtle 20x20 pixels screen_y = top - (y*20) - color_number = _grid[y][x] - if color_number == 0: + if not grid.is_dirty(y, x): continue + color_number = grid[y][x] color = colors[color_number] pen.penColor(color) pen.goTo(screen_x, screen_y, with_pen=False) @@ -84,24 +119,24 @@ def _draw_grid(pen: LayerTurtle): def _check_grid(score: LayerTurtle): - global _score_count + global score_count # Check if each row is full: for y in range(0,24): is_full = True y_erase = y for x in range(0,12): - if _grid[y][x] == 0: + if grid[y][x] == 0: is_full = False break # Remove row and shift down if is_full: - _score_count += 1 + score_count += 1 score.clear() - score.write(f'Score: {_score_count}', align='center') + score.write(f'Score: {score_count}', align='center') for y in range(y_erase-1, -1, -1): for x in range(0,12): - _grid[y+1][x] = _grid[y][x] + grid[y + 1][x] = grid[y][x] @@ -113,7 +148,7 @@ def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): self.pen: LayerTurtle = None self.left_button: LayerLcd = None self.right_button: LayerLcd = None - self.shape: _Shape = None + self.shape: Shape = None self.last_update_time = None def initializeDD(self): @@ -178,8 +213,8 @@ def initializeDD(self): self.left_button = left_button self.right_button = right_button - shape = _Shape() - _grid[shape.y][shape.x] = shape.color + shape = Shape() + grid[shape.y][shape.x] = shape.color self.shape = shape @@ -187,7 +222,7 @@ def initializeDD(self): def updateDD(self): now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) > 0.1 + need_update = self.last_update_time is None or (now - self.last_update_time) > 0.2 if need_update: self.update() self.last_update_time = now @@ -198,18 +233,18 @@ def update(self): if self.shape.y == 23: self.shape.move = 'stop' self.checkGrid() - self.shape = _Shape() + self.shape = Shape() # Drop down one space if empty below - elif _grid[self.shape.y+1][self.shape.x] == 0: - _grid[self.shape.y][self.shape.x]=0 + elif grid[self.shape.y + 1][self.shape.x] == 0: + grid[self.shape.y][self.shape.x]=0 self.shape.y += 1 - _grid[self.shape.y][self.shape.x] = self.shape.color + grid[self.shape.y][self.shape.x] = self.shape.color # Stop if above another block else: self.shape.move = 'stop' - self.shape = _Shape() + self.shape = Shape() self.checkGrid() # Had to place it here for upcoming shapes... From 4fa8942a4e9f15b8acdbce13efd6999d7da999d1 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 08:10:43 +0800 Subject: [PATCH 35/76] added to TetrisOneBlockApp "dirty" indicator --- .../tetris_one_block/tetris_one_block.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index b5fcb3c..688fba3 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -39,6 +39,7 @@ ] _grid_dirty = None +_dirty = False class GridRow(): def __init__(self, row_idx): @@ -48,8 +49,10 @@ def __len__(self): def __getitem__(self, col_idx): return _grid[self.row_idx][col_idx] def __setitem__(self, col_idx, value): + global _dirty _grid[self.row_idx][col_idx] = value _grid_dirty[self.row_idx][col_idx] = True + _dirty = True class Grid(): def __init__(self): @@ -62,14 +65,16 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) _grid_dirty.append(grid_dirty_row) - def __len__(self): return len(_grid) def __getitem__(self, row_idx): return GridRow(row_idx) - def is_dirty(self, row_idx, col_idx): - return _grid_dirty[row_idx][col_idx] != 0 - #return _grid_dirty[row_idx][col_idx] + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = _grid_dirty[row_idx][col_idx] + if not dirty: + return False + _grid_dirty[row_idx][col_idx] = False + return True grid = Grid() score_count = 0 @@ -98,7 +103,7 @@ def move_left(self): def _draw_grid(pen: LayerTurtle): - pen.clear() + #pen.clear() top = 230 left = -110 colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', @@ -108,7 +113,7 @@ def _draw_grid(pen: LayerTurtle): for x in range(len(grid[0])): # 12 columns screen_x = left + (x*20) # each turtle 20x20 pixels screen_y = top - (y*20) - if not grid.is_dirty(y, x): + if not grid.check_reset_need_redraw(y, x): continue color_number = grid[y][x] color = colors[color_number] @@ -197,10 +202,12 @@ def initializeDD(self): left_button = LayerLcd(self.dd, 2, 1, char_height=28) left_button.noBackgroundColor() left_button.writeLine("⬅️") + left_button.enableFeedback("fl", lambda *args: self.shape.move_left()) right_button = LayerLcd(self.dd, 2, 1, char_height=28) right_button.noBackgroundColor() right_button.writeLine("➡️") + right_button.enableFeedback("fl", lambda *args: self.shape.move_right()) AutoPin('V', AutoPin('S'), @@ -221,11 +228,17 @@ def initializeDD(self): self.drawGrid() def updateDD(self): + global _dirty now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) > 0.2 + need_update = self.last_update_time is None or (now - self.last_update_time) > .5 if need_update: self.update() self.last_update_time = now + _dirty = False + else: + if _dirty: + self.drawGrid() + _dirty = False def update(self): # Move shape @@ -251,7 +264,6 @@ def update(self): # win.onkey(shape.move_right, 'Right') # win.onkey(shape.move_left, 'Left') - self.drawGrid() #time.sleep(0.1) From e2f58f9c794e85cc75824eb1e55d2335c3baf215 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 10:03:58 +0800 Subject: [PATCH 36/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 688fba3..33a6510 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -1,3 +1,11 @@ +# *** +# *** Cloned from https://github.com/DimaGutierrez/Python-Games +# *** +# +# Tetris running blocks through grid +# Turtle only stamping the colors based on the number in the grid +# Blocks falling as number in the grid + import time from dumbdisplay.core import * @@ -7,9 +15,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_WIDTH = 400 -_HEIGHT = 700 - +_delay = 0.5 # For time/sleep _grid = [ [0,0,0,0,0,0,0,0,0,0,0,0], @@ -38,12 +44,14 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] + _grid_dirty = None _dirty = False class GridRow(): - def __init__(self, row_idx): - self.row_idx = row_idx + # def __init__(self, row_idx): + # self.row_idx = row_idx def __len__(self): return len(_grid[self.row_idx]) def __getitem__(self, col_idx): @@ -65,10 +73,13 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) _grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow() def __len__(self): return len(_grid) def __getitem__(self, row_idx): - return GridRow(row_idx) + #return GridRow(row_idx) + self.grid_row.row_idx = row_idx + return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): dirty = _grid_dirty[row_idx][col_idx] if not dirty: @@ -111,10 +122,10 @@ def _draw_grid(pen: LayerTurtle): for y in range(len(grid)): # 24 rows for x in range(len(grid[0])): # 12 columns - screen_x = left + (x*20) # each turtle 20x20 pixels - screen_y = top - (y*20) if not grid.check_reset_need_redraw(y, x): continue + screen_x = left + (x*20) # each turtle 20x20 pixels + screen_y = top - (y*20) color_number = grid[y][x] color = colors[color_number] pen.penColor(color) @@ -137,7 +148,7 @@ def _check_grid(score: LayerTurtle): if is_full: score_count += 1 score.clear() - score.write(f'Score: {score_count}', align='center') + score.write(f'Score: {score_count}', align='C') for y in range(y_erase-1, -1, -1): for x in range(0,12): @@ -157,12 +168,14 @@ def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): self.last_update_time = None def initializeDD(self): + width = 400 + height = 700 - root = DDRootLayer(self.dd, _WIDTH, _HEIGHT) + root = DDRootLayer(self.dd, width, height) root.border(5, "darkred", "round", 1) root.backgroundColor("black") - score = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + score = LayerTurtle(self.dd, width, height) score.penColor('red') score.penUp() #score.hideturtle() @@ -171,7 +184,7 @@ def initializeDD(self): score.setTextFont("Courier", 24) score.write('Score: 0', 'C') - border = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + border = LayerTurtle(self.dd, width, height) border.penSize(10) border.penUp() #border.hideturtle() @@ -190,7 +203,7 @@ def initializeDD(self): border.setTextFont("Courier", 36) border.write("TETRIS", "C") - pen = LayerTurtle(self.dd, _WIDTH, _HEIGHT) + pen = LayerTurtle(self.dd, width, height) #pen.up() # pen.speed(0) # pen.shape('square') @@ -213,7 +226,6 @@ def initializeDD(self): AutoPin('S'), AutoPin('H', left_button, right_button)).pin(self.dd) - self.root = root self.score = score self.pen = pen @@ -230,7 +242,7 @@ def initializeDD(self): def updateDD(self): global _dirty now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) > .5 + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay if need_update: self.update() self.last_update_time = now From a26e12dc8c2575e4517760c17a1436ee5cf94645 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 13:15:01 +0800 Subject: [PATCH 37/76] working on TetrisClassicApp --- .../tetris_classic/tetris_classic.py | 383 ++++++++++++++++++ .../tetris_one_block/tetris_one_block.py | 57 ++- 2 files changed, 411 insertions(+), 29 deletions(-) create mode 100644 dumbdisplay_examples/tetris_classic/tetris_classic.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/tetris_classic/tetris_classic.py new file mode 100644 index 0000000..bf8e955 --- /dev/null +++ b/dumbdisplay_examples/tetris_classic/tetris_classic.py @@ -0,0 +1,383 @@ +# *** +# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games +# *** - modified from dumbdisplay_examples/tetris_classic +# *** +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + + +_DRAW_SHAP_AFTER_MOVE = True + +_delay = 0.5 + +_grid = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +] + +_grid_dirty = None +#_dirty = False + +class GridRow(): + def __len__(self): + return len(_grid[self.row_idx]) + def __getitem__(self, col_idx): + return _grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + #global _dirty + _grid[self.row_idx][col_idx] = value + _grid_dirty[self.row_idx][col_idx] = True + #_dirty = True + +class Grid(): + def __init__(self): + global _grid_dirty + if _grid_dirty is None: + _grid_dirty = [] + for grid_row in _grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + _grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow() + def __len__(self): + return len(_grid) + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = _grid_dirty[row_idx][col_idx] + if not dirty: + return False + _grid_dirty[row_idx][col_idx] = False + return True + + +grid = Grid() +score_count = 0 + +class Shape(): + def __init__(self): + self.x = 5 + self.y = 0 + self.color = random.randint(1, 7) + + # Block Shape + square = [[1,1], + [1,1]] + + horizontal_line = [[1,1,1,1]] + + vertical_line = [[1], + [1], + [1], + [1]] + + left_l = [[1,0,0,0], + [1,1,1,1]] + + right_l = [[0,0,0,1], + [1,1,1,1]] + + left_s = [[1,1,0], + [0,1,1]] + + right_s = [[0,1,1], + [1,1,0]] + + t = [[0,1,0], + [1,1,1]] + + shapes = [square, horizontal_line, vertical_line, left_l, right_l, left_s, right_s, t] + + # Choose a random shape each time + self.shape = random.choice(shapes) + + + self.height = len(self.shape) + self.width = len(self.shape[0]) + + # print(self.height, self.width) + + def move_left(self): + if self.x > 0: + if grid[self.y][self.x - 1] == 0: + self.erase_shape() + self.x -= 1 + if _DRAW_SHAP_AFTER_MOVE: + self.draw_shape() + + def move_right(self): + if self.x < 12 - self.width: + if grid[self.y][self.x + self.width] == 0: + self.erase_shape() + self.x += 1 + if _DRAW_SHAP_AFTER_MOVE: + self.draw_shape() + + def draw_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + grid[self.y + y][self.x + x] = self.color + + def erase_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + grid[self.y + y][self.x + x] = 0 + + def can_move(self): + result = True + for x in range(self.width): + # Check if bottom is a 1 + if(self.shape[self.height-1][x] == 1): + if(grid[self.y + self.height][self.x + x] != 0): + result = False + return result + + def rotate(self): + # First erase_shape + self.erase_shape() + rotated_shape = [] + for x in range(len(self.shape[0])): + new_row = [] + for y in range(len(self.shape)-1, -1, -1): + new_row.append(self.shape[y][x]) + rotated_shape.append(new_row) + + right_side = self.x + len(rotated_shape[0]) + if right_side < len(grid[0]): + self.shape = rotated_shape + # Update the height and width + self.height = len(self.shape) + self.width = len(self.shape[0]) + +def draw_grid(pen: LayerTurtle): + #pen.clear() + top = 230 + left = -110 + + colors = ["black", "lightblue", "blue", "orange", "yellow", "green", "purple", "red"] + + for y in range(len(grid)): + for x in range(len(grid[0])): + if not grid.check_reset_need_redraw(y, x): + continue + screen_x = left + (x * 20) + screen_y = top - (y * 20) + color_number = grid[y][x] + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def check_grid(score: LayerTurtle): + # Check if each row is full + y = 23 + while y > 0: + is_full = True + for x in range(0, 12): + if grid[y][x] == 0: + is_full = False + y -= 1 + break + if is_full: + global score_count + score_count += 10 + score.clear() + score.write(f'Score: {score_count}', align='C') + + for copy_y in range(y, 0, -1): + for copy_x in range(0, 12): + grid[copy_y][copy_x] = grid[copy_y-1][copy_x] + +# def draw_score(pen: LayerTurtle): +# pen.penColor("blue") +# #pen.hideturtle() +# pen.goTo(-75, 350, with_pen=False) +# #pen.write("Score: {}".format(score), move=False, align="left", font=("Arial", 24, "normal")) +# pen.write("Score: {}".format(score), align="L") + + + +class TetrisClassicApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + self.left_button: LayerLcd = None + self.right_button: LayerLcd = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + pen = LayerTurtle(self.dd, width, height) + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.root = root + self.score = score + self.pen = pen + self.left_button = left_button + self.right_button = right_button + + # Create the basic shape for the start of the game + shape = Shape() + + # # Put the shape in the grid + # grid[shape.y][shape.x] = shape.color + + self.shape = shape + + # wn.listen() + # wn.onkeypress(lambda: shape.move_left(grid), "a") + # wn.onkeypress(lambda: shape.move_right(grid), "d") + # wn.onkeypress(lambda: shape.rotate(grid), "space") + + def updateDD(self): + #global _dirty + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.update() + self.last_update_time = now + #_dirty = False + # else: + # if _dirty: + # self.drawGrid() + # _dirty = False + + def update(self): + # Move the shape + # Open Row + # Check for the bottom + if self.shape.y == 23 - self.shape.height + 1: + self.shape = Shape() + self.checkGrid() + # Check for collision with next row + elif self.shape.can_move(): + # Erase the current shape + self.shape.erase_shape() + + # Move the shape by 1 + self.shape.y +=1 + + # Draw the shape again + self.shape.draw_shape() + + else: + self.shape = Shape() + self.checkGrid() + + # Draw the screen + self.drawGrid() + #draw_score(pen, score) + + def drawGrid(self): + self.dd.freezeDrawing() + draw_grid(pen=self.pen) + self.dd.unfreezeDrawing() + + def checkGrid(self): + check_grid(score=self.score) + + def moveShapeLeft(self): + self.shape.move_left() + self.drawGrid() + + def moveShapeRight(self): + self.shape.move_right() + self.drawGrid() + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisClassicApp(create_example_wifi_dd()) + app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 33a6510..95be1fb 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -1,10 +1,6 @@ # *** -# *** Cloned from https://github.com/DimaGutierrez/Python-Games +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games # *** -# -# Tetris running blocks through grid -# Turtle only stamping the colors based on the number in the grid -# Blocks falling as number in the grid import time @@ -44,23 +40,19 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] -#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] - _grid_dirty = None -_dirty = False +#_dirty = False class GridRow(): - # def __init__(self, row_idx): - # self.row_idx = row_idx def __len__(self): return len(_grid[self.row_idx]) def __getitem__(self, col_idx): return _grid[self.row_idx][col_idx] def __setitem__(self, col_idx, value): - global _dirty + #global _dirty _grid[self.row_idx][col_idx] = value _grid_dirty[self.row_idx][col_idx] = True - _dirty = True + #_dirty = True class Grid(): def __init__(self): @@ -77,7 +69,6 @@ def __init__(self): def __len__(self): return len(_grid) def __getitem__(self, row_idx): - #return GridRow(row_idx) self.grid_row.row_idx = row_idx return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): @@ -113,7 +104,7 @@ def move_left(self): grid[self.y][self.x] = self.color -def _draw_grid(pen: LayerTurtle): +def draw_grid(pen: LayerTurtle): #pen.clear() top = 230 left = -110 @@ -134,8 +125,7 @@ def _draw_grid(pen: LayerTurtle): pen.rectangle(18, 18, centered=True) -def _check_grid(score: LayerTurtle): - global score_count +def check_grid(score: LayerTurtle): # Check if each row is full: for y in range(0,24): is_full = True @@ -146,6 +136,7 @@ def _check_grid(score: LayerTurtle): break # Remove row and shift down if is_full: + global score_count score_count += 1 score.clear() score.write(f'Score: {score_count}', align='C') @@ -201,7 +192,7 @@ def initializeDD(self): border.goTo(0,260) #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) border.setTextFont("Courier", 36) - border.write("TETRIS", "C") + border.write("TETRIS (one block)", "C") pen = LayerTurtle(self.dd, width, height) #pen.up() @@ -211,16 +202,15 @@ def initializeDD(self): # pen.setundobuffer(None) pen.penFilled() - left_button = LayerLcd(self.dd, 2, 1, char_height=28) left_button.noBackgroundColor() left_button.writeLine("⬅️") - left_button.enableFeedback("fl", lambda *args: self.shape.move_left()) + left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) right_button = LayerLcd(self.dd, 2, 1, char_height=28) right_button.noBackgroundColor() right_button.writeLine("➡️") - right_button.enableFeedback("fl", lambda *args: self.shape.move_right()) + right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) AutoPin('V', AutoPin('S'), @@ -240,17 +230,17 @@ def initializeDD(self): self.drawGrid() def updateDD(self): - global _dirty + #global _dirty now = time.time() need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay if need_update: self.update() self.last_update_time = now - _dirty = False - else: - if _dirty: - self.drawGrid() - _dirty = False + #_dirty = False + # else: + # if _dirty: + # self.drawGrid() + # #_dirty = False def update(self): # Move shape @@ -280,17 +270,26 @@ def update(self): #time.sleep(0.1) def drawGrid(self): + #global _dirty self.dd.freezeDrawing() - _draw_grid(pen=self.pen) + draw_grid(pen=self.pen) self.dd.unfreezeDrawing() + #_dirty = False def checkGrid(self): - _check_grid(score=self.score) + check_grid(score=self.score) + + def moveShapeLeft(self): + self.shape.move_left() + self.drawGrid() + + def moveShapeRight(self): + self.shape.move_right() + self.drawGrid() if __name__ == "__main__": from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisOneBlockApp(create_example_wifi_dd()) app.run() From f7925ceb38da677d0e8346dfdc3bf1cef6d9290b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 14:15:46 +0800 Subject: [PATCH 38/76] TetrisOneBlockApp more encapulation --- .../tetris_classic/tetris_classic.py | 26 +++- .../tetris_one_block/tetris_one_block.py | 120 ++++++++++-------- 2 files changed, 87 insertions(+), 59 deletions(-) diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/tetris_classic/tetris_classic.py index bf8e955..1ebb822 100644 --- a/dumbdisplay_examples/tetris_classic/tetris_classic.py +++ b/dumbdisplay_examples/tetris_classic/tetris_classic.py @@ -183,6 +183,9 @@ def rotate(self): self.height = len(self.shape) self.width = len(self.shape[0]) + if _DRAW_SHAP_AFTER_MOVE: + self.draw_shape() + def draw_grid(pen: LayerTurtle): #pen.clear() top = 230 @@ -236,11 +239,11 @@ def check_grid(score: LayerTurtle): class TetrisClassicApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) - self.root: DDRootLayer = None + # self.root: DDRootLayer = None self.score: LayerTurtle = None self.pen: LayerTurtle = None - self.left_button: LayerLcd = None - self.right_button: LayerLcd = None + # self.left_button: LayerLcd = None + # self.right_button: LayerLcd = None self.shape: Shape = None self.last_update_time = None @@ -298,15 +301,20 @@ def initializeDD(self): right_button.writeLine("➡️") right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + rotate_button = LayerLcd(self.dd, 2, 1, char_height=28) + rotate_button.noBackgroundColor() + rotate_button.writeLine("🔄") + rotate_button.enableFeedback("f", lambda *args: self.shapeRotate()) + AutoPin('V', AutoPin('S'), - AutoPin('H', left_button, right_button)).pin(self.dd) + AutoPin('H', left_button, rotate_button, right_button)).pin(self.dd) - self.root = root + # self.root = root self.score = score self.pen = pen - self.left_button = left_button - self.right_button = right_button + # self.left_button = left_button + # self.right_button = right_button # Create the basic shape for the start of the game shape = Shape() @@ -376,6 +384,10 @@ def moveShapeRight(self): self.shape.move_right() self.drawGrid() + def shapeRotate(self): + self.shape.rotate() + self.drawGrid() + if __name__ == "__main__": from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 95be1fb..f864433 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -12,7 +12,6 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd _delay = 0.5 # For time/sleep - _grid = [ [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], @@ -40,50 +39,56 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] -_grid_dirty = None +# _grid = None +# _grid_dirty = None #_dirty = False -class GridRow(): +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 def __len__(self): - return len(_grid[self.row_idx]) + return len(self.grid[self.row_idx]) def __getitem__(self, col_idx): - return _grid[self.row_idx][col_idx] + return self.grid[self.row_idx][col_idx] def __setitem__(self, col_idx, value): - #global _dirty - _grid[self.row_idx][col_idx] = value - _grid_dirty[self.row_idx][col_idx] = True - #_dirty = True + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True -class Grid(): +class Grid: def __init__(self): - global _grid_dirty - if _grid_dirty is None: - _grid_dirty = [] - for grid_row in _grid: - grid_dirty_row = [] - for cell in grid_row: - dirty = True if cell != 0 else False - grid_dirty_row.append(dirty) - _grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow() + self.grid = [] + for grid_row in _grid: + self.grid.append(grid_row.copy()) + self.grid_dirty = [] + for grid_row in self.grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + self.grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow(self.grid, self.grid_dirty) def __len__(self): - return len(_grid) + return len(self.grid) def __getitem__(self, row_idx): self.grid_row.row_idx = row_idx return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): - dirty = _grid_dirty[row_idx][col_idx] + dirty = self.grid_dirty[row_idx][col_idx] if not dirty: return False - _grid_dirty[row_idx][col_idx] = False + self.grid_dirty[row_idx][col_idx] = False return True -grid = Grid() -score_count = 0 +# grid = None#Grid() +# score_count = 0 -class Shape(): +class Shape: def __init__(self): + self.grid = Grid() + self.score_count = 0 self.x = 5 self.y = 0 self.color = 4 @@ -91,20 +96,22 @@ def __init__(self): def move_right(self): if self.x < 11 and self.move == 'go': - if grid[self.y][self.x + 1]==0: - grid[self.y][self.x]=0 + if self.grid[self.y][self.x + 1]==0: + self.grid[self.y][self.x]=0 self.x += 1 - grid[self.y][self.x] = self.color + self.grid[self.y][self.x] = self.color def move_left(self): if self.x > 0 and self.move == 'go': - if grid[self.y][self.x - 1]==0: - grid[self.y][self.x]=0 + if self.grid[self.y][self.x - 1]==0: + self.grid[self.y][self.x]=0 self.x -= 1 - grid[self.y][self.x] = self.color + self.grid[self.y][self.x] = self.color -def draw_grid(pen: LayerTurtle): +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + #pen.clear() top = 230 left = -110 @@ -125,7 +132,9 @@ def draw_grid(pen: LayerTurtle): pen.rectangle(18, 18, centered=True) -def check_grid(score: LayerTurtle): +def check_grid(shape: Shape, score: LayerTurtle): + grid = shape.grid + # Check if each row is full: for y in range(0,24): is_full = True @@ -136,10 +145,9 @@ def check_grid(score: LayerTurtle): break # Remove row and shift down if is_full: - global score_count - score_count += 1 + shape.score_count += 1 score.clear() - score.write(f'Score: {score_count}', align='C') + score.write(f'Score: {shape.score_count}', align='C') for y in range(y_erase-1, -1, -1): for x in range(0,12): @@ -150,11 +158,11 @@ def check_grid(score: LayerTurtle): class TetrisOneBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) - self.root: DDRootLayer = None + # self.root: DDRootLayer = None self.score: LayerTurtle = None self.pen: LayerTurtle = None - self.left_button: LayerLcd = None - self.right_button: LayerLcd = None + # self.left_button: LayerLcd = None + # self.right_button: LayerLcd = None self.shape: Shape = None self.last_update_time = None @@ -216,14 +224,14 @@ def initializeDD(self): AutoPin('S'), AutoPin('H', left_button, right_button)).pin(self.dd) - self.root = root + # self.root = root self.score = score self.pen = pen - self.left_button = left_button - self.right_button = right_button + # self.left_button = left_button + # self.right_button = right_button shape = Shape() - grid[shape.y][shape.x] = shape.color + shape.grid[shape.y][shape.x] = shape.color self.shape = shape @@ -251,16 +259,24 @@ def update(self): self.shape = Shape() # Drop down one space if empty below - elif grid[self.shape.y + 1][self.shape.x] == 0: - grid[self.shape.y][self.shape.x]=0 + elif self.shape.grid[self.shape.y + 1][self.shape.x] == 0: + self.shape.grid[self.shape.y][self.shape.x]=0 self.shape.y += 1 - grid[self.shape.y][self.shape.x] = self.shape.color + self.shape.grid[self.shape.y][self.shape.x] = self.shape.color # Stop if above another block else: - self.shape.move = 'stop' - self.shape = Shape() - self.checkGrid() + if True: + self.checkGrid() + if self.shape.y > 0: + self.shape.x = 5 + self.shape.y = 0 + else: + self.shape.move = 'stop' + else: + self.shape.move = 'stop' + self.shape = Shape() + self.checkGrid() # Had to place it here for upcoming shapes... # win.onkey(shape.move_right, 'Right') @@ -272,13 +288,13 @@ def update(self): def drawGrid(self): #global _dirty self.dd.freezeDrawing() - draw_grid(pen=self.pen) + draw_grid(shape=self.shape, pen=self.pen) self.dd.unfreezeDrawing() #_dirty = False def checkGrid(self): - check_grid(score=self.score) + check_grid(shape=self.shape, score=self.score) def moveShapeLeft(self): self.shape.move_left() From 32f31c9409f4c53f20af293f2b37b364588ca6a5 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 15:31:27 +0800 Subject: [PATCH 39/76] working on TetrisClassicApp more encapsulation --- .../tetris_classic/tetris_classic.py | 138 ++++++++---------- .../tetris_one_block/tetris_one_block.py | 48 +++--- 2 files changed, 88 insertions(+), 98 deletions(-) diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/tetris_classic/tetris_classic.py index 1ebb822..ea691b9 100644 --- a/dumbdisplay_examples/tetris_classic/tetris_classic.py +++ b/dumbdisplay_examples/tetris_classic/tetris_classic.py @@ -13,81 +13,43 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_DRAW_SHAP_AFTER_MOVE = True - -_delay = 0.5 - -_grid = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -] - -_grid_dirty = None -#_dirty = False - -class GridRow(): +_delay = 0.3 + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 def __len__(self): - return len(_grid[self.row_idx]) + return len(self.grid[self.row_idx]) def __getitem__(self, col_idx): - return _grid[self.row_idx][col_idx] + return self.grid[self.row_idx][col_idx] def __setitem__(self, col_idx, value): - #global _dirty - _grid[self.row_idx][col_idx] = value - _grid_dirty[self.row_idx][col_idx] = True - #_dirty = True + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True class Grid(): def __init__(self): - global _grid_dirty - if _grid_dirty is None: - _grid_dirty = [] - for grid_row in _grid: - grid_dirty_row = [] - for cell in grid_row: - dirty = True if cell != 0 else False - grid_dirty_row.append(dirty) - _grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow() + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid_dirty = [[False for _ in range(12)] for _ in range(24)] + self.grid_row = GridRow(self.grid, self.grid_dirty) def __len__(self): - return len(_grid) + return len(self.grid) def __getitem__(self, row_idx): self.grid_row.row_idx = row_idx return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): - dirty = _grid_dirty[row_idx][col_idx] + dirty = self.grid_dirty[row_idx][col_idx] if not dirty: return False - _grid_dirty[row_idx][col_idx] = False + self.grid_dirty[row_idx][col_idx] = False return True -grid = Grid() -score_count = 0 - class Shape(): def __init__(self): + self.grid = Grid() + self.score_count = 0 self.x = 5 self.y = 0 self.color = random.randint(1, 7) @@ -131,38 +93,36 @@ def __init__(self): def move_left(self): if self.x > 0: - if grid[self.y][self.x - 1] == 0: + if self.grid[self.y][self.x - 1] == 0: self.erase_shape() self.x -= 1 - if _DRAW_SHAP_AFTER_MOVE: - self.draw_shape() + self.draw_shape() def move_right(self): if self.x < 12 - self.width: - if grid[self.y][self.x + self.width] == 0: + if self.grid[self.y][self.x + self.width] == 0: self.erase_shape() self.x += 1 - if _DRAW_SHAP_AFTER_MOVE: - self.draw_shape() + self.draw_shape() def draw_shape(self): for y in range(self.height): for x in range(self.width): if(self.shape[y][x]==1): - grid[self.y + y][self.x + x] = self.color + self.grid[self.y + y][self.x + x] = self.color def erase_shape(self): for y in range(self.height): for x in range(self.width): if(self.shape[y][x]==1): - grid[self.y + y][self.x + x] = 0 + self.grid[self.y + y][self.x + x] = 0 def can_move(self): result = True for x in range(self.width): # Check if bottom is a 1 if(self.shape[self.height-1][x] == 1): - if(grid[self.y + self.height][self.x + x] != 0): + if(self.grid[self.y + self.height][self.x + x] != 0): result = False return result @@ -177,16 +137,17 @@ def rotate(self): rotated_shape.append(new_row) right_side = self.x + len(rotated_shape[0]) - if right_side < len(grid[0]): + if right_side < len(self.grid[0]): self.shape = rotated_shape # Update the height and width self.height = len(self.shape) self.width = len(self.shape[0]) - if _DRAW_SHAP_AFTER_MOVE: - self.draw_shape() + self.draw_shape() + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid -def draw_grid(pen: LayerTurtle): #pen.clear() top = 230 left = -110 @@ -207,7 +168,9 @@ def draw_grid(pen: LayerTurtle): pen.rectangle(18, 18, centered=True) -def check_grid(score: LayerTurtle): +def check_grid(shape: Shape, score: LayerTurtle): + grid = shape.grid + # Check if each row is full y = 23 while y > 0: @@ -218,10 +181,9 @@ def check_grid(score: LayerTurtle): y -= 1 break if is_full: - global score_count - score_count += 10 + shape.score_count += 10 score.clear() - score.write(f'Score: {score_count}', align='C') + score.write(f'Score: {shape.score_count}', align='C') for copy_y in range(y, 0, -1): for copy_x in range(0, 12): @@ -347,8 +309,16 @@ def update(self): # Open Row # Check for the bottom if self.shape.y == 23 - self.shape.height + 1: - self.shape = Shape() - self.checkGrid() + if True: + self.checkGrid() + if self.shape.y == 23: + self.shape = None + else: + self.shape.x = 5 + self.shape.y = 0 + else: + self.shape = Shape() + self.checkGrid() # Check for collision with next row elif self.shape.can_move(): # Erase the current shape @@ -361,8 +331,16 @@ def update(self): self.shape.draw_shape() else: - self.shape = Shape() - self.checkGrid() + if True: + self.checkGrid() + if self.shape.y > 0: + self.shape.x = 5 + self.shape.y = 0 + else: + self.shape = None + else: + self.shape = Shape() + self.checkGrid() # Draw the screen self.drawGrid() @@ -370,11 +348,11 @@ def update(self): def drawGrid(self): self.dd.freezeDrawing() - draw_grid(pen=self.pen) + draw_grid(shape=self.shape, pen=self.pen) self.dd.unfreezeDrawing() def checkGrid(self): - check_grid(score=self.score) + check_grid(shape=self.shape, score=self.score) def moveShapeLeft(self): self.shape.move_left() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index f864433..3768d00 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -11,7 +11,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_delay = 0.5 # For time/sleep +_delay = 0.3 # For time/sleep _grid = [ [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], @@ -39,10 +39,6 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] -# _grid = None -# _grid_dirty = None -#_dirty = False - class GridRow: def __init__(self, grid, grid_dirty): self.grid = grid @@ -58,9 +54,14 @@ def __setitem__(self, col_idx, value): class Grid: def __init__(self): - self.grid = [] - for grid_row in _grid: - self.grid.append(grid_row.copy()) + if True: + self.grid = [] + for row in _grid: + self.grid.append(row.copy()) + else: + # debug + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] self.grid_dirty = [] for grid_row in self.grid: grid_dirty_row = [] @@ -81,10 +82,6 @@ def check_reset_need_redraw(self, row_idx, col_idx): self.grid_dirty[row_idx][col_idx] = False return True -# grid = None#Grid() -# score_count = 0 - - class Shape: def __init__(self): self.grid = Grid() @@ -132,7 +129,7 @@ def draw_grid(shape: Shape, pen: LayerTurtle): pen.rectangle(18, 18, centered=True) -def check_grid(shape: Shape, score: LayerTurtle): +def check_grid(shape: Shape, score: LayerTurtle) -> bool: grid = shape.grid # Check if each row is full: @@ -153,6 +150,8 @@ def check_grid(shape: Shape, score: LayerTurtle): for x in range(0,12): grid[y + 1][x] = grid[y][x] + return y_erase == 23 and is_full + class TetrisOneBlockApp(DDAppBase): @@ -251,12 +250,24 @@ def updateDD(self): # #_dirty = False def update(self): + if self.shape.move == 'stop': + print("... waiting to restart ...") + return + # Move shape # Stop if at the bottom if self.shape.y == 23: - self.shape.move = 'stop' - self.checkGrid() - self.shape = Shape() + if True: + if self.checkGrid(): + self.shape.move = 'stop' + print("*** YOU WON ***") + else: + self.shape.x = 5 + self.shape.y = 0 + else: + self.shape.move = 'stop' + self.checkGrid() + self.shape = Shape() # Drop down one space if empty below elif self.shape.grid[self.shape.y + 1][self.shape.x] == 0: @@ -273,6 +284,7 @@ def update(self): self.shape.y = 0 else: self.shape.move = 'stop' + print("*** GAME OVER ***") else: self.shape.move = 'stop' self.shape = Shape() @@ -293,8 +305,8 @@ def drawGrid(self): #_dirty = False - def checkGrid(self): - check_grid(shape=self.shape, score=self.score) + def checkGrid(self) -> bool: + return check_grid(shape=self.shape, score=self.score) def moveShapeLeft(self): self.shape.move_left() From 9f87ac9172e243fa5da403e73c6cc0f324008b89 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 18:59:50 +0800 Subject: [PATCH 40/76] working on TetrisClassicApp more encapsulation --- .../tetris_classic/tetris_classic.py | 32 +- .../tetris_classic/tetris_classic_OLD.py | 389 ++++++++++++++++++ 2 files changed, 413 insertions(+), 8 deletions(-) create mode 100644 dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/tetris_classic/tetris_classic.py index ea691b9..59aa8fc 100644 --- a/dumbdisplay_examples/tetris_classic/tetris_classic.py +++ b/dumbdisplay_examples/tetris_classic/tetris_classic.py @@ -13,6 +13,10 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd +# TODO: two issues +# . sometimes block can fall through the others +# . when rotate / move left / right, the block can go into others (or the grid) + _delay = 0.3 class GridRow: @@ -28,7 +32,7 @@ def __setitem__(self, col_idx, value): self.grid[self.row_idx][col_idx] = value self.grid_dirty[self.row_idx][col_idx] = True -class Grid(): +class Grid: def __init__(self): self.grid = [[0 for _ in range(12)] for _ in range(24)] self.grid_dirty = [[False for _ in range(12)] for _ in range(24)] @@ -46,10 +50,13 @@ def check_reset_need_redraw(self, row_idx, col_idx): return True -class Shape(): +class Shape: def __init__(self): self.grid = Grid() self.score_count = 0 + self.reset(for_init=True) + + def reset(self, for_init=False): self.x = 5 self.y = 0 self.color = random.randint(1, 7) @@ -89,6 +96,8 @@ def __init__(self): self.height = len(self.shape) self.width = len(self.shape[0]) + self.draw_shape() + # print(self.height, self.width) def move_left(self): @@ -305,6 +314,10 @@ def updateDD(self): # _dirty = False def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + # Move the shape # Open Row # Check for the bottom @@ -314,8 +327,9 @@ def update(self): if self.shape.y == 23: self.shape = None else: - self.shape.x = 5 - self.shape.y = 0 + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 else: self.shape = Shape() self.checkGrid() @@ -334,17 +348,19 @@ def update(self): if True: self.checkGrid() if self.shape.y > 0: - self.shape.x = 5 - self.shape.y = 0 + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 else: self.shape = None + print("*** GAME OVER ***") else: self.shape = Shape() self.checkGrid() # Draw the screen - self.drawGrid() - #draw_score(pen, score) + if self.shape is not None: + self.drawGrid() def drawGrid(self): self.dd.freezeDrawing() diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py new file mode 100644 index 0000000..59aa8fc --- /dev/null +++ b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py @@ -0,0 +1,389 @@ +# *** +# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games +# *** - modified from dumbdisplay_examples/tetris_classic +# *** +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + + +# TODO: two issues +# . sometimes block can fall through the others +# . when rotate / move left / right, the block can go into others (or the grid) + +_delay = 0.3 + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 + def __len__(self): + return len(self.grid[self.row_idx]) + def __getitem__(self, col_idx): + return self.grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True + +class Grid: + def __init__(self): + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid_dirty = [[False for _ in range(12)] for _ in range(24)] + self.grid_row = GridRow(self.grid, self.grid_dirty) + def __len__(self): + return len(self.grid) + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.reset(for_init=True) + + def reset(self, for_init=False): + self.x = 5 + self.y = 0 + self.color = random.randint(1, 7) + + # Block Shape + square = [[1,1], + [1,1]] + + horizontal_line = [[1,1,1,1]] + + vertical_line = [[1], + [1], + [1], + [1]] + + left_l = [[1,0,0,0], + [1,1,1,1]] + + right_l = [[0,0,0,1], + [1,1,1,1]] + + left_s = [[1,1,0], + [0,1,1]] + + right_s = [[0,1,1], + [1,1,0]] + + t = [[0,1,0], + [1,1,1]] + + shapes = [square, horizontal_line, vertical_line, left_l, right_l, left_s, right_s, t] + + # Choose a random shape each time + self.shape = random.choice(shapes) + + + self.height = len(self.shape) + self.width = len(self.shape[0]) + + self.draw_shape() + + # print(self.height, self.width) + + def move_left(self): + if self.x > 0: + if self.grid[self.y][self.x - 1] == 0: + self.erase_shape() + self.x -= 1 + self.draw_shape() + + def move_right(self): + if self.x < 12 - self.width: + if self.grid[self.y][self.x + self.width] == 0: + self.erase_shape() + self.x += 1 + self.draw_shape() + + def draw_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + self.grid[self.y + y][self.x + x] = self.color + + def erase_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + self.grid[self.y + y][self.x + x] = 0 + + def can_move(self): + result = True + for x in range(self.width): + # Check if bottom is a 1 + if(self.shape[self.height-1][x] == 1): + if(self.grid[self.y + self.height][self.x + x] != 0): + result = False + return result + + def rotate(self): + # First erase_shape + self.erase_shape() + rotated_shape = [] + for x in range(len(self.shape[0])): + new_row = [] + for y in range(len(self.shape)-1, -1, -1): + new_row.append(self.shape[y][x]) + rotated_shape.append(new_row) + + right_side = self.x + len(rotated_shape[0]) + if right_side < len(self.grid[0]): + self.shape = rotated_shape + # Update the height and width + self.height = len(self.shape) + self.width = len(self.shape[0]) + + self.draw_shape() + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + + #pen.clear() + top = 230 + left = -110 + + colors = ["black", "lightblue", "blue", "orange", "yellow", "green", "purple", "red"] + + for y in range(len(grid)): + for x in range(len(grid[0])): + if not grid.check_reset_need_redraw(y, x): + continue + screen_x = left + (x * 20) + screen_y = top - (y * 20) + color_number = grid[y][x] + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def check_grid(shape: Shape, score: LayerTurtle): + grid = shape.grid + + # Check if each row is full + y = 23 + while y > 0: + is_full = True + for x in range(0, 12): + if grid[y][x] == 0: + is_full = False + y -= 1 + break + if is_full: + shape.score_count += 10 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for copy_y in range(y, 0, -1): + for copy_x in range(0, 12): + grid[copy_y][copy_x] = grid[copy_y-1][copy_x] + +# def draw_score(pen: LayerTurtle): +# pen.penColor("blue") +# #pen.hideturtle() +# pen.goTo(-75, 350, with_pen=False) +# #pen.write("Score: {}".format(score), move=False, align="left", font=("Arial", 24, "normal")) +# pen.write("Score: {}".format(score), align="L") + + + +class TetrisClassicApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + # self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + # self.left_button: LayerLcd = None + # self.right_button: LayerLcd = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + pen = LayerTurtle(self.dd, width, height) + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + + rotate_button = LayerLcd(self.dd, 2, 1, char_height=28) + rotate_button.noBackgroundColor() + rotate_button.writeLine("🔄") + rotate_button.enableFeedback("f", lambda *args: self.shapeRotate()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, rotate_button, right_button)).pin(self.dd) + + # self.root = root + self.score = score + self.pen = pen + # self.left_button = left_button + # self.right_button = right_button + + # Create the basic shape for the start of the game + shape = Shape() + + # # Put the shape in the grid + # grid[shape.y][shape.x] = shape.color + + self.shape = shape + + # wn.listen() + # wn.onkeypress(lambda: shape.move_left(grid), "a") + # wn.onkeypress(lambda: shape.move_right(grid), "d") + # wn.onkeypress(lambda: shape.rotate(grid), "space") + + def updateDD(self): + #global _dirty + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.update() + self.last_update_time = now + #_dirty = False + # else: + # if _dirty: + # self.drawGrid() + # _dirty = False + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + # Move the shape + # Open Row + # Check for the bottom + if self.shape.y == 23 - self.shape.height + 1: + if True: + self.checkGrid() + if self.shape.y == 23: + self.shape = None + else: + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 + else: + self.shape = Shape() + self.checkGrid() + # Check for collision with next row + elif self.shape.can_move(): + # Erase the current shape + self.shape.erase_shape() + + # Move the shape by 1 + self.shape.y +=1 + + # Draw the shape again + self.shape.draw_shape() + + else: + if True: + self.checkGrid() + if self.shape.y > 0: + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 + else: + self.shape = None + print("*** GAME OVER ***") + else: + self.shape = Shape() + self.checkGrid() + + # Draw the screen + if self.shape is not None: + self.drawGrid() + + def drawGrid(self): + self.dd.freezeDrawing() + draw_grid(shape=self.shape, pen=self.pen) + self.dd.unfreezeDrawing() + + def checkGrid(self): + check_grid(shape=self.shape, score=self.score) + + def moveShapeLeft(self): + self.shape.move_left() + self.drawGrid() + + def moveShapeRight(self): + self.shape.move_right() + self.drawGrid() + + def shapeRotate(self): + self.shape.rotate() + self.drawGrid() + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisClassicApp(create_example_wifi_dd()) + app.run() From 31bd129026facf4f1aecfd0bb4ff0f26842e192b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 20:22:22 +0800 Subject: [PATCH 41/76] working on TetrisClassicApp more encapsulation --- .../tetris_classic/tetris_classic_OLD_OLD.py | 389 ++++++++++++++++++ .../tetris_one_block/tetris_one_block.py | 64 +-- .../tetris_one_block/tetris_one_block_OLD.py | 306 ++++++++++++++ 3 files changed, 716 insertions(+), 43 deletions(-) create mode 100644 dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py create mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py new file mode 100644 index 0000000..59aa8fc --- /dev/null +++ b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py @@ -0,0 +1,389 @@ +# *** +# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games +# *** - modified from dumbdisplay_examples/tetris_classic +# *** +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + + +# TODO: two issues +# . sometimes block can fall through the others +# . when rotate / move left / right, the block can go into others (or the grid) + +_delay = 0.3 + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 + def __len__(self): + return len(self.grid[self.row_idx]) + def __getitem__(self, col_idx): + return self.grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True + +class Grid: + def __init__(self): + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid_dirty = [[False for _ in range(12)] for _ in range(24)] + self.grid_row = GridRow(self.grid, self.grid_dirty) + def __len__(self): + return len(self.grid) + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.reset(for_init=True) + + def reset(self, for_init=False): + self.x = 5 + self.y = 0 + self.color = random.randint(1, 7) + + # Block Shape + square = [[1,1], + [1,1]] + + horizontal_line = [[1,1,1,1]] + + vertical_line = [[1], + [1], + [1], + [1]] + + left_l = [[1,0,0,0], + [1,1,1,1]] + + right_l = [[0,0,0,1], + [1,1,1,1]] + + left_s = [[1,1,0], + [0,1,1]] + + right_s = [[0,1,1], + [1,1,0]] + + t = [[0,1,0], + [1,1,1]] + + shapes = [square, horizontal_line, vertical_line, left_l, right_l, left_s, right_s, t] + + # Choose a random shape each time + self.shape = random.choice(shapes) + + + self.height = len(self.shape) + self.width = len(self.shape[0]) + + self.draw_shape() + + # print(self.height, self.width) + + def move_left(self): + if self.x > 0: + if self.grid[self.y][self.x - 1] == 0: + self.erase_shape() + self.x -= 1 + self.draw_shape() + + def move_right(self): + if self.x < 12 - self.width: + if self.grid[self.y][self.x + self.width] == 0: + self.erase_shape() + self.x += 1 + self.draw_shape() + + def draw_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + self.grid[self.y + y][self.x + x] = self.color + + def erase_shape(self): + for y in range(self.height): + for x in range(self.width): + if(self.shape[y][x]==1): + self.grid[self.y + y][self.x + x] = 0 + + def can_move(self): + result = True + for x in range(self.width): + # Check if bottom is a 1 + if(self.shape[self.height-1][x] == 1): + if(self.grid[self.y + self.height][self.x + x] != 0): + result = False + return result + + def rotate(self): + # First erase_shape + self.erase_shape() + rotated_shape = [] + for x in range(len(self.shape[0])): + new_row = [] + for y in range(len(self.shape)-1, -1, -1): + new_row.append(self.shape[y][x]) + rotated_shape.append(new_row) + + right_side = self.x + len(rotated_shape[0]) + if right_side < len(self.grid[0]): + self.shape = rotated_shape + # Update the height and width + self.height = len(self.shape) + self.width = len(self.shape[0]) + + self.draw_shape() + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + + #pen.clear() + top = 230 + left = -110 + + colors = ["black", "lightblue", "blue", "orange", "yellow", "green", "purple", "red"] + + for y in range(len(grid)): + for x in range(len(grid[0])): + if not grid.check_reset_need_redraw(y, x): + continue + screen_x = left + (x * 20) + screen_y = top - (y * 20) + color_number = grid[y][x] + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def check_grid(shape: Shape, score: LayerTurtle): + grid = shape.grid + + # Check if each row is full + y = 23 + while y > 0: + is_full = True + for x in range(0, 12): + if grid[y][x] == 0: + is_full = False + y -= 1 + break + if is_full: + shape.score_count += 10 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for copy_y in range(y, 0, -1): + for copy_x in range(0, 12): + grid[copy_y][copy_x] = grid[copy_y-1][copy_x] + +# def draw_score(pen: LayerTurtle): +# pen.penColor("blue") +# #pen.hideturtle() +# pen.goTo(-75, 350, with_pen=False) +# #pen.write("Score: {}".format(score), move=False, align="left", font=("Arial", 24, "normal")) +# pen.write("Score: {}".format(score), align="L") + + + +class TetrisClassicApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + # self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + # self.left_button: LayerLcd = None + # self.right_button: LayerLcd = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + pen = LayerTurtle(self.dd, width, height) + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + + rotate_button = LayerLcd(self.dd, 2, 1, char_height=28) + rotate_button.noBackgroundColor() + rotate_button.writeLine("🔄") + rotate_button.enableFeedback("f", lambda *args: self.shapeRotate()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, rotate_button, right_button)).pin(self.dd) + + # self.root = root + self.score = score + self.pen = pen + # self.left_button = left_button + # self.right_button = right_button + + # Create the basic shape for the start of the game + shape = Shape() + + # # Put the shape in the grid + # grid[shape.y][shape.x] = shape.color + + self.shape = shape + + # wn.listen() + # wn.onkeypress(lambda: shape.move_left(grid), "a") + # wn.onkeypress(lambda: shape.move_right(grid), "d") + # wn.onkeypress(lambda: shape.rotate(grid), "space") + + def updateDD(self): + #global _dirty + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.update() + self.last_update_time = now + #_dirty = False + # else: + # if _dirty: + # self.drawGrid() + # _dirty = False + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + # Move the shape + # Open Row + # Check for the bottom + if self.shape.y == 23 - self.shape.height + 1: + if True: + self.checkGrid() + if self.shape.y == 23: + self.shape = None + else: + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 + else: + self.shape = Shape() + self.checkGrid() + # Check for collision with next row + elif self.shape.can_move(): + # Erase the current shape + self.shape.erase_shape() + + # Move the shape by 1 + self.shape.y +=1 + + # Draw the shape again + self.shape.draw_shape() + + else: + if True: + self.checkGrid() + if self.shape.y > 0: + self.shape.reset() + # self.shape.x = 5 + # self.shape.y = 0 + else: + self.shape = None + print("*** GAME OVER ***") + else: + self.shape = Shape() + self.checkGrid() + + # Draw the screen + if self.shape is not None: + self.drawGrid() + + def drawGrid(self): + self.dd.freezeDrawing() + draw_grid(shape=self.shape, pen=self.pen) + self.dd.unfreezeDrawing() + + def checkGrid(self): + check_grid(shape=self.shape, score=self.score) + + def moveShapeLeft(self): + self.shape.move_left() + self.drawGrid() + + def moveShapeRight(self): + self.shape.move_right() + self.drawGrid() + + def shapeRotate(self): + self.shape.rotate() + self.drawGrid() + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisClassicApp(create_example_wifi_dd()) + app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 3768d00..5f47d1f 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -1,7 +1,7 @@ # *** # *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games # *** - +import random import time from dumbdisplay.core import * @@ -86,10 +86,13 @@ class Shape: def __init__(self): self.grid = Grid() self.score_count = 0 + self.move = 'go' + self.reset_block(for_init=True) + + def reset_block(self, for_init=False): self.x = 5 self.y = 0 - self.color = 4 - self.move = 'go' + self.color = random.randint(1, 7) def move_right(self): if self.x < 11 and self.move == 'go': @@ -133,12 +136,18 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: grid = shape.grid # Check if each row is full: + empty_count = 23 for y in range(0,24): is_full = True + is_empty = True y_erase = y for x in range(0,12): if grid[y][x] == 0: is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 break # Remove row and shift down if is_full: @@ -150,7 +159,7 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: for x in range(0,12): grid[y + 1][x] = grid[y][x] - return y_erase == 23 and is_full + return empty_count == 23 @@ -241,68 +250,37 @@ def updateDD(self): now = time.time() need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay if need_update: - self.update() self.last_update_time = now - #_dirty = False - # else: - # if _dirty: - # self.drawGrid() - # #_dirty = False + self.update() def update(self): if self.shape.move == 'stop': print("... waiting to restart ...") return - # Move shape - # Stop if at the bottom - if self.shape.y == 23: - if True: - if self.checkGrid(): - self.shape.move = 'stop' - print("*** YOU WON ***") - else: - self.shape.x = 5 - self.shape.y = 0 - else: - self.shape.move = 'stop' - self.checkGrid() - self.shape = Shape() - - # Drop down one space if empty below - elif self.shape.grid[self.shape.y + 1][self.shape.x] == 0: + if self.shape.y < 23 and self.shape.grid[self.shape.y + 1][self.shape.x] == 0: self.shape.grid[self.shape.y][self.shape.x]=0 self.shape.y += 1 self.shape.grid[self.shape.y][self.shape.x] = self.shape.color - # Stop if above another block else: - if True: - self.checkGrid() + won = self.checkGrid() + if won: + self.shape.move = 'stop' + print("*** YOU WON ***") + else: if self.shape.y > 0: - self.shape.x = 5 - self.shape.y = 0 + self.shape.reset_block() else: self.shape.move = 'stop' print("*** GAME OVER ***") - else: - self.shape.move = 'stop' - self.shape = Shape() - self.checkGrid() - - # Had to place it here for upcoming shapes... - # win.onkey(shape.move_right, 'Right') - # win.onkey(shape.move_left, 'Left') self.drawGrid() - #time.sleep(0.1) def drawGrid(self): - #global _dirty self.dd.freezeDrawing() draw_grid(shape=self.shape, pen=self.pen) self.dd.unfreezeDrawing() - #_dirty = False def checkGrid(self) -> bool: diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py new file mode 100644 index 0000000..d48a780 --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py @@ -0,0 +1,306 @@ +# *** +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games +# *** + +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_delay = 0.3 # For time/sleep +_grid = [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 + def __len__(self): + return len(self.grid[self.row_idx]) + def __getitem__(self, col_idx): + return self.grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True + +class Grid: + def __init__(self): + if True: + self.grid = [] + for row in _grid: + self.grid.append(row.copy()) + else: + # debug + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] + self.grid_dirty = [] + for grid_row in self.grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + self.grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow(self.grid, self.grid_dirty) + def __len__(self): + return len(self.grid) + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.x = 5 + self.y = 0 + self.color = 4 + self.move = 'go' + + def move_right(self): + if self.x < 11 and self.move == 'go': + if self.grid[self.y][self.x + 1]==0: + self.grid[self.y][self.x]=0 + self.x += 1 + self.grid[self.y][self.x] = self.color + + def move_left(self): + if self.x > 0 and self.move == 'go': + if self.grid[self.y][self.x - 1]==0: + self.grid[self.y][self.x]=0 + self.x -= 1 + self.grid[self.y][self.x] = self.color + + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + + #pen.clear() + top = 230 + left = -110 + colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', + 'purple'] + + for y in range(len(grid)): # 24 rows + for x in range(len(grid[0])): # 12 columns + if not grid.check_reset_need_redraw(y, x): + continue + screen_x = left + (x*20) # each turtle 20x20 pixels + screen_y = top - (y*20) + color_number = grid[y][x] + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #self.pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def check_grid(shape: Shape, score: LayerTurtle) -> bool: + grid = shape.grid + + # Check if each row is full: + for y in range(0,24): + is_full = True + y_erase = y + for x in range(0,12): + if grid[y][x] == 0: + is_full = False + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid[y + 1][x] = grid[y][x] + + return y_erase == 23 and is_full + + + +class TetrisOneBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + # self.root: DDRootLayer = None + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + # self.left_button: LayerLcd = None + # self.right_button: LayerLcd = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + #score.hideturtle() + score.goTo(60, -300) + #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + #border.hideturtle() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) + border.setTextFont("Courier", 36) + border.write("TETRIS (one block)", "C") + + pen = LayerTurtle(self.dd, width, height) + #pen.up() + # pen.speed(0) + # pen.shape('square') + # pen.shapesize(0.9, 0.9) + # pen.setundobuffer(None) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + # self.root = root + self.score = score + self.pen = pen + # self.left_button = left_button + # self.right_button = right_button + + shape = Shape() + shape.grid[shape.y][shape.x] = shape.color + + self.shape = shape + + self.drawGrid() + + def updateDD(self): + #global _dirty + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.update() + self.last_update_time = now + #_dirty = False + # else: + # if _dirty: + # self.drawGrid() + # #_dirty = False + + def update(self): + if self.shape.move == 'stop': + print("... waiting to restart ...") + return + + # Move shape + # Stop if at the bottom + if self.shape.y == 23: + if self.checkGrid(): + self.shape.move = 'stop' + print("*** YOU WON ***") + else: + self.shape.x = 5 + self.shape.y = 0 + + # Drop down one space if empty below + elif self.shape.grid[self.shape.y + 1][self.shape.x] == 0: + self.shape.grid[self.shape.y][self.shape.x]=0 + self.shape.y += 1 + self.shape.grid[self.shape.y][self.shape.x] = self.shape.color + + # Stop if above another block + else: + self.checkGrid() + if self.shape.y > 0: + self.shape.x = 5 + self.shape.y = 0 + else: + self.shape.move = 'stop' + print("*** GAME OVER ***") + + self.drawGrid() + + def drawGrid(self): + self.dd.freezeDrawing() + draw_grid(shape=self.shape, pen=self.pen) + self.dd.unfreezeDrawing() + + + def checkGrid(self) -> bool: + return check_grid(shape=self.shape, score=self.score) + + def moveShapeLeft(self): + self.shape.move_left() + self.drawGrid() + + def moveShapeRight(self): + self.shape.move_right() + self.drawGrid() + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() From 0875a24212a75b96eda6f16ed9cceb68d26c3b03 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 20:54:15 +0800 Subject: [PATCH 42/76] refactoring TetrisOneBlockApp --- .../tetris_classic/tetris_classic.py | 4 + .../tetris_one_block/tetris_one_block.py | 106 ++++++++++-------- 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/tetris_classic/tetris_classic.py index 59aa8fc..680b45a 100644 --- a/dumbdisplay_examples/tetris_classic/tetris_classic.py +++ b/dumbdisplay_examples/tetris_classic/tetris_classic.py @@ -55,6 +55,10 @@ def __init__(self): self.grid = Grid() self.score_count = 0 self.reset(for_init=True) + self.x = None + self.y = None + self.color = None + self.reset(for_init=True) def reset(self, for_init=False): self.x = 5 diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 5f47d1f..cf4cbd4 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -82,31 +82,61 @@ def check_reset_need_redraw(self, row_idx, col_idx): self.grid_dirty[row_idx][col_idx] = False return True -class Shape: +class Block: def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.move = 'go' - self.reset_block(for_init=True) - - def reset_block(self, for_init=False): self.x = 5 self.y = 0 self.color = random.randint(1, 7) - def move_right(self): - if self.x < 11 and self.move == 'go': - if self.grid[self.y][self.x + 1]==0: - self.grid[self.y][self.x]=0 + def commit(self, grid): + grid[self.y][self.x] = self.color + + def move_down(self, grid) -> bool: + if self.y < 23 and grid[self.y + 1][self.x] == 0: + grid[self.y][self.x]=0 + self.y += 1 + grid[self.y][self.x] = self.color + return True + return False + + def move_right(self, grid) -> bool: + if self.x < 11: + if grid[self.y][self.x + 1]==0: + grid[self.y][self.x]=0 self.x += 1 - self.grid[self.y][self.x] = self.color - - def move_left(self): - if self.x > 0 and self.move == 'go': - if self.grid[self.y][self.x - 1]==0: - self.grid[self.y][self.x]=0 + grid[self.y][self.x] = self.color + return True + return False + + def move_left(self, grid) -> bool: + if self.x > 0: + if grid[self.y][self.x - 1]==0: + grid[self.y][self.x]=0 self.x -= 1 - self.grid[self.y][self.x] = self.color + grid[self.y][self.x] = self.color + return True + return False + + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.block: Block = None + self.reset() + + def reset(self): + self.block = Block() + self.block.commit(self.grid) + + def move_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_left(self) -> bool: + return self.block.move_left(self.grid) def draw_grid(shape: Shape, pen: LayerTurtle): @@ -232,21 +262,13 @@ def initializeDD(self): AutoPin('S'), AutoPin('H', left_button, right_button)).pin(self.dd) - # self.root = root self.score = score self.pen = pen - # self.left_button = left_button - # self.right_button = right_button - shape = Shape() - shape.grid[shape.y][shape.x] = shape.color - - self.shape = shape - - self.drawGrid() + self.shape = Shape() + #self.drawGrid() def updateDD(self): - #global _dirty now = time.time() need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay if need_update: @@ -254,28 +276,24 @@ def updateDD(self): self.update() def update(self): - if self.shape.move == 'stop': + if self.shape is None: print("... waiting to restart ...") return - if self.shape.y < 23 and self.shape.grid[self.shape.y + 1][self.shape.x] == 0: - self.shape.grid[self.shape.y][self.shape.x]=0 - self.shape.y += 1 - self.shape.grid[self.shape.y][self.shape.x] = self.shape.color - - else: + if not self.shape.move_down(): won = self.checkGrid() if won: - self.shape.move = 'stop' + self.shape = None print("*** YOU WON ***") else: - if self.shape.y > 0: - self.shape.reset_block() + if self.shape.block.y > 0: + self.shape.reset() else: - self.shape.move = 'stop' + self.shape = None print("*** GAME OVER ***") - self.drawGrid() + if self.shape is not None: + self.drawGrid() def drawGrid(self): self.dd.freezeDrawing() @@ -287,12 +305,12 @@ def checkGrid(self) -> bool: return check_grid(shape=self.shape, score=self.score) def moveShapeLeft(self): - self.shape.move_left() - self.drawGrid() + if self.shape.move_left(): + self.drawGrid() def moveShapeRight(self): - self.shape.move_right() - self.drawGrid() + if self.shape.move_right(): + self.drawGrid() if __name__ == "__main__": From b073a4300ae284030577cff5ec6065ff7e769d61 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 21:22:07 +0800 Subject: [PATCH 43/76] refactoring TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index cf4cbd4..bebd4ec 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -251,12 +251,12 @@ def initializeDD(self): left_button = LayerLcd(self.dd, 2, 1, char_height=28) left_button.noBackgroundColor() left_button.writeLine("⬅️") - left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) right_button = LayerLcd(self.dd, 2, 1, char_height=28) right_button.noBackgroundColor() right_button.writeLine("➡️") - right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) AutoPin('V', AutoPin('S'), @@ -280,37 +280,50 @@ def update(self): print("... waiting to restart ...") return - if not self.shape.move_down(): - won = self.checkGrid() - if won: - self.shape = None - print("*** YOU WON ***") + moved_down = self.moveBlockDown() + won = self.checkGrid() + if won: + self.shape = None + print("*** YOU WON ***") + elif not moved_down: + if self.shape.block.y > 0: + self.shape.reset() else: - if self.shape.block.y > 0: - self.shape.reset() - else: - self.shape = None - print("*** GAME OVER ***") + self.shape = None + print("*** GAME OVER ***") - if self.shape is not None: - self.drawGrid() + # if self.shape is not None: + # self.drawGrid() def drawGrid(self): - self.dd.freezeDrawing() + #self.dd.freezeDrawing() draw_grid(shape=self.shape, pen=self.pen) - self.dd.unfreezeDrawing() + #self.dd.unfreezeDrawing() def checkGrid(self) -> bool: - return check_grid(shape=self.shape, score=self.score) + check_result = check_grid(shape=self.shape, score=self.score) + self.drawGrid() # should only redraw if any lines were cleared + return check_result + + + def moveBlockDown(self) -> bool: + if self.shape.move_down(): + self.drawGrid() + return True + return False - def moveShapeLeft(self): + def moveBlockLeft(self) -> bool: if self.shape.move_left(): self.drawGrid() + return True + return False - def moveShapeRight(self): + def moveBlockRight(self) -> bool: if self.shape.move_right(): self.drawGrid() + return True + return False if __name__ == "__main__": From 2f7a85fc6fcd96f4cb7b826d05d63a38b96bfc6c Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 21:42:16 +0800 Subject: [PATCH 44/76] refactoring TetrisOneBlockApp --- ...classic_OLD.py => tetris_classic_OLD_0.py} | 0 .../tetris_classic/tetris_classic_OLD_OLD.py | 389 ------------------ .../tetris_one_block/tetris_one_block.py | 25 +- ...block_OLD.py => tetris_one_block_OLD_0.py} | 0 .../tetris_one_block_OLD_1.py | 319 ++++++++++++++ 5 files changed, 325 insertions(+), 408 deletions(-) rename dumbdisplay_examples/tetris_classic/{tetris_classic_OLD.py => tetris_classic_OLD_0.py} (100%) delete mode 100644 dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py rename dumbdisplay_examples/tetris_one_block/{tetris_one_block_OLD.py => tetris_one_block_OLD_0.py} (100%) create mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_0.py similarity index 100% rename from dumbdisplay_examples/tetris_classic/tetris_classic_OLD.py rename to dumbdisplay_examples/tetris_classic/tetris_classic_OLD_0.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py b/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py deleted file mode 100644 index 59aa8fc..0000000 --- a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_OLD.py +++ /dev/null @@ -1,389 +0,0 @@ -# *** -# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games -# *** - modified from dumbdisplay_examples/tetris_classic -# *** -import random -import time - -from dumbdisplay.core import * -from dumbdisplay.layer_graphical import DDRootLayer -from dumbdisplay.layer_turtle import LayerTurtle -from dumbdisplay.layer_lcd import LayerLcd - -from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd - - -# TODO: two issues -# . sometimes block can fall through the others -# . when rotate / move left / right, the block can go into others (or the grid) - -_delay = 0.3 - -class GridRow: - def __init__(self, grid, grid_dirty): - self.grid = grid - self.grid_dirty = grid_dirty - self.row_idx = 0 - def __len__(self): - return len(self.grid[self.row_idx]) - def __getitem__(self, col_idx): - return self.grid[self.row_idx][col_idx] - def __setitem__(self, col_idx, value): - self.grid[self.row_idx][col_idx] = value - self.grid_dirty[self.row_idx][col_idx] = True - -class Grid: - def __init__(self): - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid_dirty = [[False for _ in range(12)] for _ in range(24)] - self.grid_row = GridRow(self.grid, self.grid_dirty) - def __len__(self): - return len(self.grid) - def __getitem__(self, row_idx): - self.grid_row.row_idx = row_idx - return self.grid_row - def check_reset_need_redraw(self, row_idx, col_idx): - dirty = self.grid_dirty[row_idx][col_idx] - if not dirty: - return False - self.grid_dirty[row_idx][col_idx] = False - return True - - -class Shape: - def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.reset(for_init=True) - - def reset(self, for_init=False): - self.x = 5 - self.y = 0 - self.color = random.randint(1, 7) - - # Block Shape - square = [[1,1], - [1,1]] - - horizontal_line = [[1,1,1,1]] - - vertical_line = [[1], - [1], - [1], - [1]] - - left_l = [[1,0,0,0], - [1,1,1,1]] - - right_l = [[0,0,0,1], - [1,1,1,1]] - - left_s = [[1,1,0], - [0,1,1]] - - right_s = [[0,1,1], - [1,1,0]] - - t = [[0,1,0], - [1,1,1]] - - shapes = [square, horizontal_line, vertical_line, left_l, right_l, left_s, right_s, t] - - # Choose a random shape each time - self.shape = random.choice(shapes) - - - self.height = len(self.shape) - self.width = len(self.shape[0]) - - self.draw_shape() - - # print(self.height, self.width) - - def move_left(self): - if self.x > 0: - if self.grid[self.y][self.x - 1] == 0: - self.erase_shape() - self.x -= 1 - self.draw_shape() - - def move_right(self): - if self.x < 12 - self.width: - if self.grid[self.y][self.x + self.width] == 0: - self.erase_shape() - self.x += 1 - self.draw_shape() - - def draw_shape(self): - for y in range(self.height): - for x in range(self.width): - if(self.shape[y][x]==1): - self.grid[self.y + y][self.x + x] = self.color - - def erase_shape(self): - for y in range(self.height): - for x in range(self.width): - if(self.shape[y][x]==1): - self.grid[self.y + y][self.x + x] = 0 - - def can_move(self): - result = True - for x in range(self.width): - # Check if bottom is a 1 - if(self.shape[self.height-1][x] == 1): - if(self.grid[self.y + self.height][self.x + x] != 0): - result = False - return result - - def rotate(self): - # First erase_shape - self.erase_shape() - rotated_shape = [] - for x in range(len(self.shape[0])): - new_row = [] - for y in range(len(self.shape)-1, -1, -1): - new_row.append(self.shape[y][x]) - rotated_shape.append(new_row) - - right_side = self.x + len(rotated_shape[0]) - if right_side < len(self.grid[0]): - self.shape = rotated_shape - # Update the height and width - self.height = len(self.shape) - self.width = len(self.shape[0]) - - self.draw_shape() - -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid - - #pen.clear() - top = 230 - left = -110 - - colors = ["black", "lightblue", "blue", "orange", "yellow", "green", "purple", "red"] - - for y in range(len(grid)): - for x in range(len(grid[0])): - if not grid.check_reset_need_redraw(y, x): - continue - screen_x = left + (x * 20) - screen_y = top - (y * 20) - color_number = grid[y][x] - color = colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - #pen.stamp() - pen.rectangle(18, 18, centered=True) - - -def check_grid(shape: Shape, score: LayerTurtle): - grid = shape.grid - - # Check if each row is full - y = 23 - while y > 0: - is_full = True - for x in range(0, 12): - if grid[y][x] == 0: - is_full = False - y -= 1 - break - if is_full: - shape.score_count += 10 - score.clear() - score.write(f'Score: {shape.score_count}', align='C') - - for copy_y in range(y, 0, -1): - for copy_x in range(0, 12): - grid[copy_y][copy_x] = grid[copy_y-1][copy_x] - -# def draw_score(pen: LayerTurtle): -# pen.penColor("blue") -# #pen.hideturtle() -# pen.goTo(-75, 350, with_pen=False) -# #pen.write("Score: {}".format(score), move=False, align="left", font=("Arial", 24, "normal")) -# pen.write("Score: {}".format(score), align="L") - - - -class TetrisClassicApp(DDAppBase): - def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): - super().__init__(dd) - # self.root: DDRootLayer = None - self.score: LayerTurtle = None - self.pen: LayerTurtle = None - # self.left_button: LayerLcd = None - # self.right_button: LayerLcd = None - self.shape: Shape = None - self.last_update_time = None - - def initializeDD(self): - width = 400 - height = 700 - - root = DDRootLayer(self.dd, width, height) - root.border(5, "darkred", "round", 1) - root.backgroundColor("black") - - score = LayerTurtle(self.dd, width, height) - score.penColor('red') - score.penUp() - #score.hideturtle() - score.goTo(60, -300) - #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) - score.setTextFont("Courier", 24) - score.write('Score: 0', 'C') - - border = LayerTurtle(self.dd, width, height) - border.penSize(10) - border.penUp() - #border.hideturtle() - border.goTo(-130, 240) - border.penDown() - border.penColor('white') - border.rightTurn(90) - border.forward(490) # Down - border.leftTurn(90) - border.forward(260) # Right - border.leftTurn(90) - border.forward(490) # Up - border.penUp() - border.goTo(0,260) - #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) - border.setTextFont("Courier", 36) - border.write("TETRIS", "C") - - pen = LayerTurtle(self.dd, width, height) - #pen.up() - # pen.speed(0) - # pen.shape('square') - # pen.shapesize(0.9, 0.9) - # pen.setundobuffer(None) - pen.penFilled() - - left_button = LayerLcd(self.dd, 2, 1, char_height=28) - left_button.noBackgroundColor() - left_button.writeLine("⬅️") - left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) - - right_button = LayerLcd(self.dd, 2, 1, char_height=28) - right_button.noBackgroundColor() - right_button.writeLine("➡️") - right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) - - rotate_button = LayerLcd(self.dd, 2, 1, char_height=28) - rotate_button.noBackgroundColor() - rotate_button.writeLine("🔄") - rotate_button.enableFeedback("f", lambda *args: self.shapeRotate()) - - AutoPin('V', - AutoPin('S'), - AutoPin('H', left_button, rotate_button, right_button)).pin(self.dd) - - # self.root = root - self.score = score - self.pen = pen - # self.left_button = left_button - # self.right_button = right_button - - # Create the basic shape for the start of the game - shape = Shape() - - # # Put the shape in the grid - # grid[shape.y][shape.x] = shape.color - - self.shape = shape - - # wn.listen() - # wn.onkeypress(lambda: shape.move_left(grid), "a") - # wn.onkeypress(lambda: shape.move_right(grid), "d") - # wn.onkeypress(lambda: shape.rotate(grid), "space") - - def updateDD(self): - #global _dirty - now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay - if need_update: - self.update() - self.last_update_time = now - #_dirty = False - # else: - # if _dirty: - # self.drawGrid() - # _dirty = False - - def update(self): - if self.shape is None: - print("... waiting to restart ...") - return - - # Move the shape - # Open Row - # Check for the bottom - if self.shape.y == 23 - self.shape.height + 1: - if True: - self.checkGrid() - if self.shape.y == 23: - self.shape = None - else: - self.shape.reset() - # self.shape.x = 5 - # self.shape.y = 0 - else: - self.shape = Shape() - self.checkGrid() - # Check for collision with next row - elif self.shape.can_move(): - # Erase the current shape - self.shape.erase_shape() - - # Move the shape by 1 - self.shape.y +=1 - - # Draw the shape again - self.shape.draw_shape() - - else: - if True: - self.checkGrid() - if self.shape.y > 0: - self.shape.reset() - # self.shape.x = 5 - # self.shape.y = 0 - else: - self.shape = None - print("*** GAME OVER ***") - else: - self.shape = Shape() - self.checkGrid() - - # Draw the screen - if self.shape is not None: - self.drawGrid() - - def drawGrid(self): - self.dd.freezeDrawing() - draw_grid(shape=self.shape, pen=self.pen) - self.dd.unfreezeDrawing() - - def checkGrid(self): - check_grid(shape=self.shape, score=self.score) - - def moveShapeLeft(self): - self.shape.move_left() - self.drawGrid() - - def moveShapeRight(self): - self.shape.move_right() - self.drawGrid() - - def shapeRotate(self): - self.shape.rotate() - self.drawGrid() - - -if __name__ == "__main__": - from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisClassicApp(create_example_wifi_dd()) - app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index bebd4ec..208d0d7 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -123,7 +123,7 @@ def __init__(self): self.grid = Grid() self.score_count = 0 self.block: Block = None - self.reset() + #self.reset() def reset(self): self.block = Block() @@ -196,11 +196,8 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: class TetrisOneBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) - # self.root: DDRootLayer = None self.score: LayerTurtle = None self.pen: LayerTurtle = None - # self.left_button: LayerLcd = None - # self.right_button: LayerLcd = None self.shape: Shape = None self.last_update_time = None @@ -215,16 +212,13 @@ def initializeDD(self): score = LayerTurtle(self.dd, width, height) score.penColor('red') score.penUp() - #score.hideturtle() score.goTo(60, -300) - #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) score.setTextFont("Courier", 24) score.write('Score: 0', 'C') border = LayerTurtle(self.dd, width, height) border.penSize(10) border.penUp() - #border.hideturtle() border.goTo(-130, 240) border.penDown() border.penColor('white') @@ -236,16 +230,10 @@ def initializeDD(self): border.forward(490) # Up border.penUp() border.goTo(0,260) - #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) border.setTextFont("Courier", 36) border.write("TETRIS (one block)", "C") pen = LayerTurtle(self.dd, width, height) - #pen.up() - # pen.speed(0) - # pen.shape('square') - # pen.shapesize(0.9, 0.9) - # pen.setundobuffer(None) pen.penFilled() left_button = LayerLcd(self.dd, 2, 1, char_height=28) @@ -266,7 +254,7 @@ def initializeDD(self): self.pen = pen self.shape = Shape() - #self.drawGrid() + self.resetBlock() def updateDD(self): now = time.time() @@ -292,13 +280,8 @@ def update(self): self.shape = None print("*** GAME OVER ***") - # if self.shape is not None: - # self.drawGrid() - def drawGrid(self): - #self.dd.freezeDrawing() draw_grid(shape=self.shape, pen=self.pen) - #self.dd.unfreezeDrawing() def checkGrid(self) -> bool: @@ -306,6 +289,10 @@ def checkGrid(self) -> bool: self.drawGrid() # should only redraw if any lines were cleared return check_result + def resetBlock(self): + self.shape.reset() + self.drawGrid() + def moveBlockDown(self) -> bool: if self.shape.move_down(): diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py similarity index 100% rename from dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD.py rename to dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py new file mode 100644 index 0000000..208d0d7 --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py @@ -0,0 +1,319 @@ +# *** +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games +# *** +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_delay = 0.3 # For time/sleep +_grid = [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 + def __len__(self): + return len(self.grid[self.row_idx]) + def __getitem__(self, col_idx): + return self.grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True + +class Grid: + def __init__(self): + if True: + self.grid = [] + for row in _grid: + self.grid.append(row.copy()) + else: + # debug + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] + self.grid_dirty = [] + for grid_row in self.grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + self.grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow(self.grid, self.grid_dirty) + def __len__(self): + return len(self.grid) + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + +class Block: + def __init__(self): + self.x = 5 + self.y = 0 + self.color = random.randint(1, 7) + + def commit(self, grid): + grid[self.y][self.x] = self.color + + def move_down(self, grid) -> bool: + if self.y < 23 and grid[self.y + 1][self.x] == 0: + grid[self.y][self.x]=0 + self.y += 1 + grid[self.y][self.x] = self.color + return True + return False + + def move_right(self, grid) -> bool: + if self.x < 11: + if grid[self.y][self.x + 1]==0: + grid[self.y][self.x]=0 + self.x += 1 + grid[self.y][self.x] = self.color + return True + return False + + def move_left(self, grid) -> bool: + if self.x > 0: + if grid[self.y][self.x - 1]==0: + grid[self.y][self.x]=0 + self.x -= 1 + grid[self.y][self.x] = self.color + return True + return False + + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.block: Block = None + #self.reset() + + def reset(self): + self.block = Block() + self.block.commit(self.grid) + + def move_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_left(self) -> bool: + return self.block.move_left(self.grid) + + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + + #pen.clear() + top = 230 + left = -110 + colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', + 'purple'] + + for y in range(len(grid)): # 24 rows + for x in range(len(grid[0])): # 12 columns + if not grid.check_reset_need_redraw(y, x): + continue + screen_x = left + (x*20) # each turtle 20x20 pixels + screen_y = top - (y*20) + color_number = grid[y][x] + color = colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + #self.pen.stamp() + pen.rectangle(18, 18, centered=True) + + +def check_grid(shape: Shape, score: LayerTurtle) -> bool: + grid = shape.grid + + # Check if each row is full: + empty_count = 23 + for y in range(0,24): + is_full = True + is_empty = True + y_erase = y + for x in range(0,12): + if grid[y][x] == 0: + is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid[y + 1][x] = grid[y][x] + + return empty_count == 23 + + + +class TetrisOneBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.score: LayerTurtle = None + self.pen: LayerTurtle = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + score.goTo(60, -300) + score.setTextFont("Courier", 24) + score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + border.setTextFont("Courier", 36) + border.write("TETRIS (one block)", "C") + + pen = LayerTurtle(self.dd, width, height) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.score = score + self.pen = pen + + self.shape = Shape() + self.resetBlock() + + def updateDD(self): + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.last_update_time = now + self.update() + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + moved_down = self.moveBlockDown() + won = self.checkGrid() + if won: + self.shape = None + print("*** YOU WON ***") + elif not moved_down: + if self.shape.block.y > 0: + self.shape.reset() + else: + self.shape = None + print("*** GAME OVER ***") + + def drawGrid(self): + draw_grid(shape=self.shape, pen=self.pen) + + + def checkGrid(self) -> bool: + check_result = check_grid(shape=self.shape, score=self.score) + self.drawGrid() # should only redraw if any lines were cleared + return check_result + + def resetBlock(self): + self.shape.reset() + self.drawGrid() + + + def moveBlockDown(self) -> bool: + if self.shape.move_down(): + self.drawGrid() + return True + return False + + def moveBlockLeft(self) -> bool: + if self.shape.move_left(): + self.drawGrid() + return True + return False + + def moveBlockRight(self) -> bool: + if self.shape.move_right(): + self.drawGrid() + return True + return False + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() From 753d73e4285ec1c932b5b0e7c94f19e7d22d2162 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 22:59:13 +0800 Subject: [PATCH 45/76] refactoring TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 117 ++++++++++++------ 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 208d0d7..5d9ef9f 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -44,10 +44,13 @@ def __init__(self, grid, grid_dirty): self.grid = grid self.grid_dirty = grid_dirty self.row_idx = 0 + def __len__(self): return len(self.grid[self.row_idx]) + def __getitem__(self, col_idx): return self.grid[self.row_idx][col_idx] + def __setitem__(self, col_idx, value): self.grid[self.row_idx][col_idx] = value self.grid_dirty[self.row_idx][col_idx] = True @@ -70,11 +73,14 @@ def __init__(self): grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) self.grid_row = GridRow(self.grid, self.grid_dirty) + def __len__(self): return len(self.grid) + def __getitem__(self, row_idx): self.grid_row.row_idx = row_idx return self.grid_row + def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] if not dirty: @@ -93,27 +99,27 @@ def commit(self, grid): def move_down(self, grid) -> bool: if self.y < 23 and grid[self.y + 1][self.x] == 0: - grid[self.y][self.x]=0 + #grid[self.y][self.x]=0 self.y += 1 - grid[self.y][self.x] = self.color + #grid[self.y][self.x] = self.color return True return False def move_right(self, grid) -> bool: if self.x < 11: - if grid[self.y][self.x + 1]==0: - grid[self.y][self.x]=0 + if grid[self.y][self.x + 1] == 0: + #grid[self.y][self.x]=0 self.x += 1 - grid[self.y][self.x] = self.color + #grid[self.y][self.x] = self.color return True return False def move_left(self, grid) -> bool: if self.x > 0: - if grid[self.y][self.x - 1]==0: - grid[self.y][self.x]=0 + if grid[self.y][self.x - 1] == 0: + #grid[self.y][self.x]=0 self.x -= 1 - grid[self.y][self.x] = self.color + #grid[self.y][self.x] = self.color return True return False @@ -125,45 +131,56 @@ def __init__(self): self.block: Block = None #self.reset() - def reset(self): + + def reset_block(self): self.block = Block() + #self.block.commit(self.grid) + + def commit_block(self): self.block.commit(self.grid) - def move_down(self) -> bool: + def move_block_down(self) -> bool: return self.block.move_down(self.grid) - def move_right(self) -> bool: + def move_block_right(self) -> bool: return self.block.move_right(self.grid) - def move_left(self) -> bool: + def move_block_left(self) -> bool: return self.block.move_left(self.grid) -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid - #pen.clear() - top = 230 - left = -110 - colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', - 'purple'] +_top = 230 +_left = -110 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'purple'] + + +def _draw(x, y, color_number, pen: LayerTurtle): + screen_x = _left + (x * 20) # each turtle 20x20 pixels + screen_y = _top - (y * 20) + color = _colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + pen.rectangle(18, 18, centered=True) - for y in range(len(grid)): # 24 rows - for x in range(len(grid[0])): # 12 columns +def draw_block(shape: Shape, block_pen: LayerTurtle): + block = shape.block + block_pen.clear() + _draw(block.x, block.y, block.color, block_pen) + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + for y in range(24): + for x in range(12): if not grid.check_reset_need_redraw(y, x): continue - screen_x = left + (x*20) # each turtle 20x20 pixels - screen_y = top - (y*20) color_number = grid[y][x] - color = colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - #self.pen.stamp() - pen.rectangle(18, 18, centered=True) - + _draw(x, y, color_number, pen) def check_grid(shape: Shape, score: LayerTurtle) -> bool: grid = shape.grid + block = shape.block # Check if each row is full: empty_count = 23 @@ -197,6 +214,7 @@ class TetrisOneBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) self.score: LayerTurtle = None + self.block_pen: LayerTurtle = None self.pen: LayerTurtle = None self.shape: Shape = None self.last_update_time = None @@ -233,6 +251,9 @@ def initializeDD(self): border.setTextFont("Courier", 36) border.write("TETRIS (one block)", "C") + block_pen = LayerTurtle(self.dd, width, height) + block_pen.penFilled() + pen = LayerTurtle(self.dd, width, height) pen.penFilled() @@ -251,6 +272,7 @@ def initializeDD(self): AutoPin('H', left_button, right_button)).pin(self.dd) self.score = score + self.block_pen = block_pen self.pen = pen self.shape = Shape() @@ -269,20 +291,32 @@ def update(self): return moved_down = self.moveBlockDown() + if not moved_down: + self.shape.commit_block() won = self.checkGrid() if won: - self.shape = None - print("*** YOU WON ***") + self.endGame(won=True) elif not moved_down: if self.shape.block.y > 0: - self.shape.reset() + #self.shape.reset_block() + self.resetBlock() else: - self.shape = None - print("*** GAME OVER ***") + self.endGame(won=False) + + def drawBlock(self): + draw_block(shape=self.shape, block_pen=self.block_pen) def drawGrid(self): draw_grid(shape=self.shape, pen=self.pen) + def endGame(self, won: bool): + self.block_pen.clear() + self.shape = None + if won: + print("*** YOU WON ***") + else: + print("*** GAME OVER ***") + def checkGrid(self) -> bool: check_result = check_grid(shape=self.shape, score=self.score) @@ -290,25 +324,26 @@ def checkGrid(self) -> bool: return check_result def resetBlock(self): - self.shape.reset() + self.shape.reset_block() self.drawGrid() + self.drawBlock() def moveBlockDown(self) -> bool: - if self.shape.move_down(): - self.drawGrid() + if self.shape.move_block_down(): + self.drawBlock() return True return False def moveBlockLeft(self) -> bool: - if self.shape.move_left(): - self.drawGrid() + if self.shape.move_block_left(): + self.drawBlock() return True return False def moveBlockRight(self) -> bool: - if self.shape.move_right(): - self.drawGrid() + if self.shape.move_block_right(): + self.drawBlock() return True return False From a1a1bc081c7ac086899326b4c8607fb53e53589d Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 23:09:28 +0800 Subject: [PATCH 46/76] refactoring TetrisOneBlockApp --- dumbdisplay_examples/tetris_one_block/tetris_one_block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 5d9ef9f..18650d6 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -249,7 +249,7 @@ def initializeDD(self): border.penUp() border.goTo(0,260) border.setTextFont("Courier", 36) - border.write("TETRIS (one block)", "C") + border.write("One-Block TETRIS", "C") block_pen = LayerTurtle(self.dd, width, height) block_pen.penFilled() From 3408d9d5c74b3be433abb2975de0882fd8a834b0 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 23 Aug 2025 23:51:21 +0800 Subject: [PATCH 47/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 18650d6..df8bc91 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -232,7 +232,7 @@ def initializeDD(self): score.penUp() score.goTo(60, -300) score.setTextFont("Courier", 24) - score.write('Score: 0', 'C') + #score.write('Score: 0', 'C') border = LayerTurtle(self.dd, width, height) border.penSize(10) @@ -253,6 +253,7 @@ def initializeDD(self): block_pen = LayerTurtle(self.dd, width, height) block_pen.penFilled() + block_pen.setTextSize(32) pen = LayerTurtle(self.dd, width, height) pen.penFilled() @@ -275,8 +276,9 @@ def initializeDD(self): self.block_pen = block_pen self.pen = pen - self.shape = Shape() - self.resetBlock() + self.startGame() + #self.shape = Shape() + #self.resetBlock() def updateDD(self): now = time.time() @@ -309,13 +311,29 @@ def drawBlock(self): def drawGrid(self): draw_grid(shape=self.shape, pen=self.pen) + def startGame(self): + self.score.clear() + self.score.write('Score: 0', 'C') + self.pen.clear() + self.block_pen.clear() + self.shape = Shape() + self.resetBlock() + + def endGame(self, won: bool): self.block_pen.clear() self.shape = None if won: - print("*** YOU WON ***") + msg = "🥳 YOU WON 🥳" + color = "purple" else: - print("*** GAME OVER ***") + msg = "GAME OVER 😔" + color = "darkgray" + self.block_pen.home(with_pen=False) + self.block_pen.penColor("white") + self.block_pen.oval(300, 100, centered=True) + self.block_pen.penColor(color) + self.block_pen.write(msg, align='C') def checkGrid(self) -> bool: @@ -328,7 +346,6 @@ def resetBlock(self): self.drawGrid() self.drawBlock() - def moveBlockDown(self) -> bool: if self.shape.move_block_down(): self.drawBlock() @@ -336,12 +353,18 @@ def moveBlockDown(self) -> bool: return False def moveBlockLeft(self) -> bool: + if self.shape is None: + self.startGame() + return False if self.shape.move_block_left(): self.drawBlock() return True return False def moveBlockRight(self) -> bool: + if self.shape is None: + self.startGame() + return False if self.shape.move_block_right(): self.drawBlock() return True From 777f0cbc454a45d43e2e102e94c2c0a2aaa6e32a Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 24 Aug 2025 01:32:17 +0800 Subject: [PATCH 48/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 78 ++-- .../tetris_one_block_OLD_2.py | 377 ++++++++++++++++++ 2 files changed, 417 insertions(+), 38 deletions(-) create mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index df8bc91..106d101 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -39,21 +39,21 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] -class GridRow: - def __init__(self, grid, grid_dirty): - self.grid = grid - self.grid_dirty = grid_dirty - self.row_idx = 0 - - def __len__(self): - return len(self.grid[self.row_idx]) - - def __getitem__(self, col_idx): - return self.grid[self.row_idx][col_idx] - - def __setitem__(self, col_idx, value): - self.grid[self.row_idx][col_idx] = value - self.grid_dirty[self.row_idx][col_idx] = True +# class GridRow: +# def __init__(self, grid, grid_dirty): +# self.grid = grid +# self.grid_dirty = grid_dirty +# self.row_idx = 0 +# +# def __len__(self): +# return len(self.grid[self.row_idx]) +# +# def __getitem__(self, col_idx): +# return self.grid[self.row_idx][col_idx] +# +# def __setitem__(self, col_idx, value): +# self.grid[self.row_idx][col_idx] = value +# self.grid_dirty[self.row_idx][col_idx] = True class Grid: def __init__(self): @@ -72,14 +72,14 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow(self.grid, self.grid_dirty) + # self.grid_row = GridRow(self.grid, self.grid_dirty) - def __len__(self): - return len(self.grid) + # def __len__(self): + # return len(self.grid) - def __getitem__(self, row_idx): - self.grid_row.row_idx = row_idx - return self.grid_row + # def __getitem__(self, row_idx): + # self.grid_row.row_idx = row_idx + # return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -88,38 +88,40 @@ def check_reset_need_redraw(self, row_idx, col_idx): self.grid_dirty[row_idx][col_idx] = False return True + def get_value(self, row_idx, col_idx): + return self.grid[row_idx][col_idx] + + def set_value(self, row_idx, col_idx, value): + old_value = self.grid[row_idx][col_idx] + self.grid[row_idx][col_idx] = value + self.grid_dirty[row_idx][col_idx] = old_value != value + class Block: def __init__(self): self.x = 5 self.y = 0 self.color = random.randint(1, 7) - def commit(self, grid): - grid[self.y][self.x] = self.color + def commit(self, grid: Grid): + grid.set_value(self.y, self.x, self.color) - def move_down(self, grid) -> bool: - if self.y < 23 and grid[self.y + 1][self.x] == 0: - #grid[self.y][self.x]=0 + def move_down(self, grid: Grid) -> bool: + if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: self.y += 1 - #grid[self.y][self.x] = self.color return True return False - def move_right(self, grid) -> bool: + def move_right(self, grid: Grid) -> bool: if self.x < 11: - if grid[self.y][self.x + 1] == 0: - #grid[self.y][self.x]=0 + if grid.get_value(self.y, self.x + 1) == 0: self.x += 1 - #grid[self.y][self.x] = self.color return True return False - def move_left(self, grid) -> bool: + def move_left(self, grid: Grid) -> bool: if self.x > 0: - if grid[self.y][self.x - 1] == 0: - #grid[self.y][self.x]=0 + if grid.get_value(self.y, self.x - 1) == 0: self.x -= 1 - #grid[self.y][self.x] = self.color return True return False @@ -175,7 +177,7 @@ def draw_grid(shape: Shape, pen: LayerTurtle): for x in range(12): if not grid.check_reset_need_redraw(y, x): continue - color_number = grid[y][x] + color_number = grid.get_value(y, x) _draw(x, y, color_number, pen) def check_grid(shape: Shape, score: LayerTurtle) -> bool: @@ -189,7 +191,7 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: is_empty = True y_erase = y for x in range(0,12): - if grid[y][x] == 0: + if grid.get_value(y, x) == 0: is_full = False else: is_empty = False @@ -204,7 +206,7 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: for y in range(y_erase-1, -1, -1): for x in range(0,12): - grid[y + 1][x] = grid[y][x] + grid.set_value(y + 1, x, grid.get_value(y, x)) return empty_count == 23 diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py new file mode 100644 index 0000000..df8bc91 --- /dev/null +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py @@ -0,0 +1,377 @@ +# *** +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games +# *** +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_delay = 0.3 # For time/sleep +_grid = [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] + +class GridRow: + def __init__(self, grid, grid_dirty): + self.grid = grid + self.grid_dirty = grid_dirty + self.row_idx = 0 + + def __len__(self): + return len(self.grid[self.row_idx]) + + def __getitem__(self, col_idx): + return self.grid[self.row_idx][col_idx] + + def __setitem__(self, col_idx, value): + self.grid[self.row_idx][col_idx] = value + self.grid_dirty[self.row_idx][col_idx] = True + +class Grid: + def __init__(self): + if True: + self.grid = [] + for row in _grid: + self.grid.append(row.copy()) + else: + # debug + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] + self.grid_dirty = [] + for grid_row in self.grid: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + self.grid_dirty.append(grid_dirty_row) + self.grid_row = GridRow(self.grid, self.grid_dirty) + + def __len__(self): + return len(self.grid) + + def __getitem__(self, row_idx): + self.grid_row.row_idx = row_idx + return self.grid_row + + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + +class Block: + def __init__(self): + self.x = 5 + self.y = 0 + self.color = random.randint(1, 7) + + def commit(self, grid): + grid[self.y][self.x] = self.color + + def move_down(self, grid) -> bool: + if self.y < 23 and grid[self.y + 1][self.x] == 0: + #grid[self.y][self.x]=0 + self.y += 1 + #grid[self.y][self.x] = self.color + return True + return False + + def move_right(self, grid) -> bool: + if self.x < 11: + if grid[self.y][self.x + 1] == 0: + #grid[self.y][self.x]=0 + self.x += 1 + #grid[self.y][self.x] = self.color + return True + return False + + def move_left(self, grid) -> bool: + if self.x > 0: + if grid[self.y][self.x - 1] == 0: + #grid[self.y][self.x]=0 + self.x -= 1 + #grid[self.y][self.x] = self.color + return True + return False + + +class Shape: + def __init__(self): + self.grid = Grid() + self.score_count = 0 + self.block: Block = None + #self.reset() + + + def reset_block(self): + self.block = Block() + #self.block.commit(self.grid) + + def commit_block(self): + self.block.commit(self.grid) + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + + +_top = 230 +_left = -110 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'purple'] + + +def _draw(x, y, color_number, pen: LayerTurtle): + screen_x = _left + (x * 20) # each turtle 20x20 pixels + screen_y = _top - (y * 20) + color = _colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + pen.rectangle(18, 18, centered=True) + +def draw_block(shape: Shape, block_pen: LayerTurtle): + block = shape.block + block_pen.clear() + _draw(block.x, block.y, block.color, block_pen) + +def draw_grid(shape: Shape, pen: LayerTurtle): + grid = shape.grid + for y in range(24): + for x in range(12): + if not grid.check_reset_need_redraw(y, x): + continue + color_number = grid[y][x] + _draw(x, y, color_number, pen) + +def check_grid(shape: Shape, score: LayerTurtle) -> bool: + grid = shape.grid + block = shape.block + + # Check if each row is full: + empty_count = 23 + for y in range(0,24): + is_full = True + is_empty = True + y_erase = y + for x in range(0,12): + if grid[y][x] == 0: + is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid[y + 1][x] = grid[y][x] + + return empty_count == 23 + + + +class TetrisOneBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.score: LayerTurtle = None + self.block_pen: LayerTurtle = None + self.pen: LayerTurtle = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + width = 400 + height = 700 + + root = DDRootLayer(self.dd, width, height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + score = LayerTurtle(self.dd, width, height) + score.penColor('red') + score.penUp() + score.goTo(60, -300) + score.setTextFont("Courier", 24) + #score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, width, height) + border.penSize(10) + border.penUp() + border.goTo(-130, 240) + border.penDown() + border.penColor('white') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + border.setTextFont("Courier", 36) + border.write("One-Block TETRIS", "C") + + block_pen = LayerTurtle(self.dd, width, height) + block_pen.penFilled() + block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, width, height) + pen.penFilled() + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.score = score + self.block_pen = block_pen + self.pen = pen + + self.startGame() + #self.shape = Shape() + #self.resetBlock() + + def updateDD(self): + now = time.time() + need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + if need_update: + self.last_update_time = now + self.update() + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + moved_down = self.moveBlockDown() + if not moved_down: + self.shape.commit_block() + won = self.checkGrid() + if won: + self.endGame(won=True) + elif not moved_down: + if self.shape.block.y > 0: + #self.shape.reset_block() + self.resetBlock() + else: + self.endGame(won=False) + + def drawBlock(self): + draw_block(shape=self.shape, block_pen=self.block_pen) + + def drawGrid(self): + draw_grid(shape=self.shape, pen=self.pen) + + def startGame(self): + self.score.clear() + self.score.write('Score: 0', 'C') + self.pen.clear() + self.block_pen.clear() + self.shape = Shape() + self.resetBlock() + + + def endGame(self, won: bool): + self.block_pen.clear() + self.shape = None + if won: + msg = "🥳 YOU WON 🥳" + color = "purple" + else: + msg = "GAME OVER 😔" + color = "darkgray" + self.block_pen.home(with_pen=False) + self.block_pen.penColor("white") + self.block_pen.oval(300, 100, centered=True) + self.block_pen.penColor(color) + self.block_pen.write(msg, align='C') + + + def checkGrid(self) -> bool: + check_result = check_grid(shape=self.shape, score=self.score) + self.drawGrid() # should only redraw if any lines were cleared + return check_result + + def resetBlock(self): + self.shape.reset_block() + self.drawGrid() + self.drawBlock() + + def moveBlockDown(self) -> bool: + if self.shape.move_block_down(): + self.drawBlock() + return True + return False + + def moveBlockLeft(self) -> bool: + if self.shape is None: + self.startGame() + return False + if self.shape.move_block_left(): + self.drawBlock() + return True + return False + + def moveBlockRight(self) -> bool: + if self.shape is None: + self.startGame() + return False + if self.shape.move_block_right(): + self.drawBlock() + return True + return False + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() From ac8abd2b1d573219b9fb8eeefe04e6150cda9926 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 24 Aug 2025 12:49:29 +0800 Subject: [PATCH 49/76] working on TetrisOneBlockApp --- dumbdisplay/ddlayer_multilevel.py | 8 +-- dumbdisplay/ddlayer_turtle.py | 4 +- .../tetris_one_block/tetris_one_block.py | 63 ++++++------------- 3 files changed, 26 insertions(+), 49 deletions(-) diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 79abdc8..4ee1176 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -20,7 +20,7 @@ def addLevel(self, level_id: str, width: float = 0, height: float = 0, switch_to self.dd._sendCommand(self.layer_id, "addlevel", level_id) else: self.dd._sendCommand(self.layer_id, "addlevel", level_id, _DD_FLOAT_ARG(width), _DD_FLOAT_ARG(height), _DD_BOOL_ARG(switch_to_it)) - def addTopLevel(self, level_id: str, width: float = 0, height:float = 0, switchToIt:bool = False): + def addTopLevel(self, level_id: str, width: float = 0, height:float = 0, switch_to_it:bool = False): """ add top level -- like addLevel() but add to the top (i.e. will be drawn last) :param level_id: level ID; cannot be DD_DEF_LAYER_LEVEL_ID @@ -28,12 +28,12 @@ def addTopLevel(self, level_id: str, width: float = 0, height:float = 0, switchT :param height: height of the level "opening"; 0 means the maximum height (the height of the layer) """ if _DD_FLOAT_IS_ZERO(width) and _DD_FLOAT_IS_ZERO(height): - if switchToIt: - self.dd._sendCommand(self.layer_id, "addtoplevel", level_id, _DD_BOOL_ARG(switchToIt)) + if switch_to_it: + self.dd._sendCommand(self.layer_id, "addtoplevel", level_id, _DD_BOOL_ARG(switch_to_it)) else: self.dd._sendCommand(self.layer_id, "addtoplevel", level_id) else: - self.dd._sendCommand(self.layer_id, "aaddtoplevel", level_id, _DD_FLOAT_ARG(width), _DD_FLOAT_ARG(height), _DD_BOOL_ARG(switchToIt)) + self.dd._sendCommand(self.layer_id, "aaddtoplevel", level_id, _DD_FLOAT_ARG(width), _DD_FLOAT_ARG(height), _DD_BOOL_ARG(switch_to_it)) def switchLevel(self, level_id: str, add_if_missing: bool = True): """ switch to a different level (which is like a sub-layer), making it the current level diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 9f9802b..5ac8358 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -1,10 +1,10 @@ #from ._ddlayer import DDLayer -from .ddlayer_multilevel import DDLayer +from .ddlayer_multilevel import DDLayerMultiLevel from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG from .ddimpl import _INIT_ACK_SEQ, _NEXT_ACK_SEQ, _ACK_STR_TO_ACK_SEQ, DumbDisplayImpl -class DDLayerTurtle(DDLayer): +class DDLayerTurtle(DDLayerMultiLevel): """Turtle-like Layer""" def __init__(self, dd: DumbDisplayImpl, width: int, height: int): """ diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 106d101..23df65e 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -39,22 +39,6 @@ [2,0,1,2,3,0,6,5,5,5,0,2] ] -# class GridRow: -# def __init__(self, grid, grid_dirty): -# self.grid = grid -# self.grid_dirty = grid_dirty -# self.row_idx = 0 -# -# def __len__(self): -# return len(self.grid[self.row_idx]) -# -# def __getitem__(self, col_idx): -# return self.grid[self.row_idx][col_idx] -# -# def __setitem__(self, col_idx, value): -# self.grid[self.row_idx][col_idx] = value -# self.grid_dirty[self.row_idx][col_idx] = True - class Grid: def __init__(self): if True: @@ -72,14 +56,6 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - # self.grid_row = GridRow(self.grid, self.grid_dirty) - - # def __len__(self): - # return len(self.grid) - - # def __getitem__(self, row_idx): - # self.grid_row.row_idx = row_idx - # return self.grid_row def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -92,9 +68,13 @@ def get_value(self, row_idx, col_idx): return self.grid[row_idx][col_idx] def set_value(self, row_idx, col_idx, value): - old_value = self.grid[row_idx][col_idx] - self.grid[row_idx][col_idx] = value - self.grid_dirty[row_idx][col_idx] = old_value != value + if self.grid[row_idx][col_idx] != value: + self.grid[row_idx][col_idx] = value + self.grid_dirty[row_idx][col_idx] = True + # old_value = self.grid[row_idx][col_idx] + # self.grid[row_idx][col_idx] = value + # self.grid_dirty[row_idx][col_idx] = old_value != value + class Block: def __init__(self): @@ -155,7 +135,7 @@ def move_block_left(self) -> bool: _top = 230 _left = -110 #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] def _draw(x, y, color_number, pen: LayerTurtle): @@ -166,13 +146,11 @@ def _draw(x, y, color_number, pen: LayerTurtle): pen.goTo(screen_x, screen_y, with_pen=False) pen.rectangle(18, 18, centered=True) -def draw_block(shape: Shape, block_pen: LayerTurtle): - block = shape.block +def draw_block(block: Block, block_pen: LayerTurtle): block_pen.clear() _draw(block.x, block.y, block.color, block_pen) -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid +def draw_grid(grid: Grid, pen: LayerTurtle): for y in range(24): for x in range(12): if not grid.check_reset_need_redraw(y, x): @@ -211,7 +189,6 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: return empty_count == 23 - class TetrisOneBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) @@ -229,6 +206,13 @@ def initializeDD(self): root.border(5, "darkred", "round", 1) root.backgroundColor("black") + block_pen = LayerTurtle(self.dd, width, height) + block_pen.penFilled() + block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, width, height) + pen.penFilled() + score = LayerTurtle(self.dd, width, height) score.penColor('red') score.penUp() @@ -241,7 +225,7 @@ def initializeDD(self): border.penUp() border.goTo(-130, 240) border.penDown() - border.penColor('white') + border.penColor('linen') border.rightTurn(90) border.forward(490) # Down border.leftTurn(90) @@ -253,13 +237,6 @@ def initializeDD(self): border.setTextFont("Courier", 36) border.write("One-Block TETRIS", "C") - block_pen = LayerTurtle(self.dd, width, height) - block_pen.penFilled() - block_pen.setTextSize(32) - - pen = LayerTurtle(self.dd, width, height) - pen.penFilled() - left_button = LayerLcd(self.dd, 2, 1, char_height=28) left_button.noBackgroundColor() left_button.writeLine("⬅️") @@ -308,10 +285,10 @@ def update(self): self.endGame(won=False) def drawBlock(self): - draw_block(shape=self.shape, block_pen=self.block_pen) + draw_block(block=self.shape.block, block_pen=self.block_pen) def drawGrid(self): - draw_grid(shape=self.shape, pen=self.pen) + draw_grid(grid=self.shape.grid, pen=self.pen) def startGame(self): self.score.clear() From b4325c2687efb9e15d4c76abf94ba26ee4c73905 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 24 Aug 2025 19:19:41 +0800 Subject: [PATCH 50/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 172 ++++++++++-------- 1 file changed, 98 insertions(+), 74 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 23df65e..cfb5dba 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -76,11 +76,41 @@ def set_value(self, row_idx, col_idx, value): # self.grid_dirty[row_idx][col_idx] = old_value != value + +_top = 230 +_left = -110 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] + + +def _draw(x, y, color_number, pen: LayerTurtle): + screen_x = _left + (x * 20) # each turtle 20x20 pixels + screen_y = _top - (y * 20) + color = _colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + pen.rectangle(18, 18, centered=True) + +# def _draw_block(block: 'Block', block_pen: LayerTurtle): +# block_pen.clear() +# _draw(block.x, block.y, block.color, block_pen) + +def _draw_grid(grid: Grid, pen: LayerTurtle): + for y in range(24): + for x in range(12): + if not grid.check_reset_need_redraw(y, x): + continue + color_number = grid.get_value(y, x) + _draw(x, y, color_number, pen) + + + class Block: - def __init__(self): + def __init__(self, block_pen: LayerTurtle): self.x = 5 self.y = 0 self.color = random.randint(1, 7) + self.block_pen = block_pen def commit(self, grid: Grid): grid.set_value(self.y, self.x, self.color) @@ -88,6 +118,7 @@ def commit(self, grid: Grid): def move_down(self, grid: Grid) -> bool: if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: self.y += 1 + self.sync_image() return True return False @@ -95,6 +126,7 @@ def move_right(self, grid: Grid) -> bool: if self.x < 11: if grid.get_value(self.y, self.x + 1) == 0: self.x += 1 + self.sync_image() return True return False @@ -102,63 +134,18 @@ def move_left(self, grid: Grid) -> bool: if self.x > 0: if grid.get_value(self.y, self.x - 1) == 0: self.x -= 1 + self.sync_image() return True return False + def sync_image(self): + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) + #_draw_block(self, self.block_pen) -class Shape: - def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.block: Block = None - #self.reset() - - - def reset_block(self): - self.block = Block() - #self.block.commit(self.grid) - - def commit_block(self): - self.block.commit(self.grid) - - def move_block_down(self) -> bool: - return self.block.move_down(self.grid) - - def move_block_right(self) -> bool: - return self.block.move_right(self.grid) - - def move_block_left(self) -> bool: - return self.block.move_left(self.grid) - - - -_top = 230 -_left = -110 -#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] - - -def _draw(x, y, color_number, pen: LayerTurtle): - screen_x = _left + (x * 20) # each turtle 20x20 pixels - screen_y = _top - (y * 20) - color = _colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - pen.rectangle(18, 18, centered=True) - -def draw_block(block: Block, block_pen: LayerTurtle): - block_pen.clear() - _draw(block.x, block.y, block.color, block_pen) -def draw_grid(grid: Grid, pen: LayerTurtle): - for y in range(24): - for x in range(12): - if not grid.check_reset_need_redraw(y, x): - continue - color_number = grid.get_value(y, x) - _draw(x, y, color_number, pen) -def check_grid(shape: Shape, score: LayerTurtle) -> bool: +def _check_grid(shape: 'Shape', score: LayerTurtle) -> bool: grid = shape.grid block = shape.block @@ -189,6 +176,39 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: return empty_count == 23 +class Shape: + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): + self.grid = Grid() + self.score_count = 0 + self.block: Block = None + self.pen = pen + self.block_pen = block_pen + + def check_grid(self, score: LayerTurtle) -> bool: + return _check_grid(self, score) + + def reset_block(self): + self.block = Block(self.block_pen) + self.sync_image() + #self.block.commit(self.grid) + + def commit_block(self): + self.block.commit(self.grid) + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + def sync_image(self): + #self.block.sync_image() + _draw_grid(self.grid, self.pen) + + class TetrisOneBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) @@ -284,18 +304,18 @@ def update(self): else: self.endGame(won=False) - def drawBlock(self): - draw_block(block=self.shape.block, block_pen=self.block_pen) + # def drawBlock(self): + # draw_block(block=self.shape.block, block_pen=self.block_pen) - def drawGrid(self): - draw_grid(grid=self.shape.grid, pen=self.pen) + # def drawGrid(self): + # _draw_grid(grid=self.shape.grid, pen=self.pen) def startGame(self): self.score.clear() self.score.write('Score: 0', 'C') self.pen.clear() self.block_pen.clear() - self.shape = Shape() + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) self.resetBlock() @@ -316,38 +336,42 @@ def endGame(self, won: bool): def checkGrid(self) -> bool: - check_result = check_grid(shape=self.shape, score=self.score) - self.drawGrid() # should only redraw if any lines were cleared - return check_result + return self.shape.check_grid(score=self.score) + # check_result = check_grid(shape=self.shape, score=self.score) + # self.drawGrid() # should only redraw if any lines were cleared + # return check_result def resetBlock(self): self.shape.reset_block() - self.drawGrid() - self.drawBlock() + #self.drawGrid() + #self.drawBlock() def moveBlockDown(self) -> bool: - if self.shape.move_block_down(): - self.drawBlock() - return True - return False + return self.shape.move_block_down() + # if self.shape.move_block_down(): + # self.drawBlock() + # return True + # return False def moveBlockLeft(self) -> bool: if self.shape is None: self.startGame() return False - if self.shape.move_block_left(): - self.drawBlock() - return True - return False + return self.shape.move_block_left() + # if self.shape.move_block_left(): + # self.drawBlock() + # return True + # return False def moveBlockRight(self) -> bool: if self.shape is None: self.startGame() return False - if self.shape.move_block_right(): - self.drawBlock() - return True - return False + return self.shape.move_block_right() + # if self.shape.move_block_right(): + # self.drawBlock() + # return True + # return False if __name__ == "__main__": From 35ca9023428791436e9337e542401dde7856b1e5 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 24 Aug 2025 21:09:07 +0800 Subject: [PATCH 51/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index cfb5dba..68cbbf5 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -106,14 +106,19 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): class Block: - def __init__(self, block_pen: LayerTurtle): + def __init__(self, x: int, y: int, block_pen: LayerTurtle): self.x = 5 self.y = 0 + self.x = x + self.y = y self.color = random.randint(1, 7) self.block_pen = block_pen + self.sync_image() def commit(self, grid: Grid): grid.set_value(self.y, self.x, self.color) + if True: + self.block_pen.clear() def move_down(self, grid: Grid) -> bool: if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: @@ -145,12 +150,13 @@ def sync_image(self): -def _check_grid(shape: 'Shape', score: LayerTurtle) -> bool: +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): grid = shape.grid block = shape.block # Check if each row is full: empty_count = 23 + deleted_count = 0 for y in range(0,24): is_full = True is_empty = True @@ -173,7 +179,9 @@ def _check_grid(shape: 'Shape', score: LayerTurtle) -> bool: for x in range(0,12): grid.set_value(y + 1, x, grid.get_value(y, x)) - return empty_count == 23 + deleted_count += 1 + + return (empty_count == 23, deleted_count) class Shape: @@ -183,17 +191,28 @@ def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): self.block: Block = None self.pen = pen self.block_pen = block_pen + if not self.reset_block(): + raise Exception("Failed to create initial block") def check_grid(self, score: LayerTurtle) -> bool: - return _check_grid(self, score) + (all_empty, delete_count) = _check_grid(self, score) + if delete_count > 0: + self.sync_image() + return all_empty - def reset_block(self): - self.block = Block(self.block_pen) + def reset_block(self) -> bool: + x = 5 + y = 0 + if self.grid.get_value(y, x) != 0: + #self.sync_image() + return False + self.block = Block(x, y, self.block_pen) self.sync_image() - #self.block.commit(self.grid) + return True def commit_block(self): self.block.commit(self.grid) + self.sync_image() def move_block_down(self) -> bool: return self.block.move_down(self.grid) @@ -241,6 +260,8 @@ def initializeDD(self): #score.write('Score: 0', 'C') border = LayerTurtle(self.dd, width, height) + if False: + border.rectangle(260, 490, centered=True) border.penSize(10) border.penUp() border.goTo(-130, 240) @@ -298,11 +319,13 @@ def update(self): if won: self.endGame(won=True) elif not moved_down: - if self.shape.block.y > 0: - #self.shape.reset_block() - self.resetBlock() - else: + if not self.resetBlock(): self.endGame(won=False) + # if self.shape.block.y > 0: + # #self.shape.reset_block() + # self.resetBlock() + # else: + # self.endGame(won=False) # def drawBlock(self): # draw_block(block=self.shape.block, block_pen=self.block_pen) @@ -316,7 +339,7 @@ def startGame(self): self.pen.clear() self.block_pen.clear() self.shape = Shape(pen=self.pen, block_pen=self.block_pen) - self.resetBlock() + #self.resetBlock() def endGame(self, won: bool): @@ -341,8 +364,8 @@ def checkGrid(self) -> bool: # self.drawGrid() # should only redraw if any lines were cleared # return check_result - def resetBlock(self): - self.shape.reset_block() + def resetBlock(self) -> bool: + return self.shape.reset_block() #self.drawGrid() #self.drawBlock() From e47a35f120b8cd2970a5b99238f657df3599aa44 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 24 Aug 2025 22:55:32 +0800 Subject: [PATCH 52/76] working on TetrisOneBlockApp --- .../tetris_one_block/tetris_one_block.py | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 68cbbf5..e6aba49 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -11,6 +11,10 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd +_WIN_FASTER = False +_USE_LEVEL_ANCHOR_FOR_BLOCK = True +_INIT_BLOCK_X = 5 + _delay = 0.3 # For time/sleep _grid = [ [0,0,0,0,0,0,0,0,0,0,0,0], @@ -41,14 +45,13 @@ class Grid: def __init__(self): - if True: + if _WIN_FASTER: + self.grid = [[0 for _ in range(12)] for _ in range(24)] + self.grid[23] = _grid[23].copy() + else: self.grid = [] for row in _grid: self.grid.append(row.copy()) - else: - # debug - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] self.grid_dirty = [] for grid_row in self.grid: grid_dirty_row = [] @@ -79,17 +82,25 @@ def set_value(self, row_idx, col_idx, value): _top = 230 _left = -110 +_block_unit_width = 20 #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] +# def _calc_screen_position(x: int, y : int) -> (int, int): +# screen_x = _left + (x * 20) # each turtle 20x20 pixels +# screen_y = _top - (y * 20) +# return (screen_x, screen_y) + + def _draw(x, y, color_number, pen: LayerTurtle): - screen_x = _left + (x * 20) # each turtle 20x20 pixels - screen_y = _top - (y * 20) + screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels + screen_y = _top - (y * _block_unit_width) + # (screen_x, screen_y) = _calc_screen_position(x, y) color = _colors[color_number] pen.penColor(color) pen.goTo(screen_x, screen_y, with_pen=False) - pen.rectangle(18, 18, centered=True) + pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) # def _draw_block(block: 'Block', block_pen: LayerTurtle): # block_pen.clear() @@ -107,12 +118,15 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): class Block: def __init__(self, x: int, y: int, block_pen: LayerTurtle): - self.x = 5 + self.x = _INIT_BLOCK_X self.y = 0 self.x = x self.y = y self.color = random.randint(1, 7) self.block_pen = block_pen + if _USE_LEVEL_ANCHOR_FOR_BLOCK: + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) self.sync_image() def commit(self, grid: Grid): @@ -144,9 +158,14 @@ def move_left(self, grid: Grid) -> bool: return False def sync_image(self): - self.block_pen.clear() - _draw(self.x, self.y, self.color, self.block_pen) - #_draw_block(self, self.block_pen) + if _USE_LEVEL_ANCHOR_FOR_BLOCK: + anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width + anchor_y = self.y * _block_unit_width + #(screen_x, screen_y) = _calc_screen_position(x, y) + self.block_pen.setLevelAnchor(anchor_x, anchor_y) + else: + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) @@ -247,10 +266,11 @@ def initializeDD(self): block_pen = LayerTurtle(self.dd, width, height) block_pen.penFilled() - block_pen.setTextSize(32) + #block_pen.setTextSize(32) pen = LayerTurtle(self.dd, width, height) pen.penFilled() + pen.setTextSize(32) score = LayerTurtle(self.dd, width, height) score.penColor('red') @@ -300,9 +320,11 @@ def initializeDD(self): #self.shape = Shape() #self.resetBlock() + self.last_update_time = time.time() + def updateDD(self): now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + need_update = (now - self.last_update_time) >= _delay if need_update: self.last_update_time = now self.update() @@ -327,12 +349,6 @@ def update(self): # else: # self.endGame(won=False) - # def drawBlock(self): - # draw_block(block=self.shape.block, block_pen=self.block_pen) - - # def drawGrid(self): - # _draw_grid(grid=self.shape.grid, pen=self.pen) - def startGame(self): self.score.clear() self.score.write('Score: 0', 'C') @@ -343,7 +359,7 @@ def startGame(self): def endGame(self, won: bool): - self.block_pen.clear() + #self.block_pen.clear() self.shape = None if won: msg = "🥳 YOU WON 🥳" @@ -351,11 +367,11 @@ def endGame(self, won: bool): else: msg = "GAME OVER 😔" color = "darkgray" - self.block_pen.home(with_pen=False) - self.block_pen.penColor("white") - self.block_pen.oval(300, 100, centered=True) - self.block_pen.penColor(color) - self.block_pen.write(msg, align='C') + self.pen.home(with_pen=False) + self.pen.penColor("white") + self.pen.oval(300, 100, centered=True) + self.pen.penColor(color) + self.pen.write(msg, align='C') def checkGrid(self) -> bool: From 526afcb5961ee8268e5798066733353780746490 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Mon, 25 Aug 2025 21:00:00 +0800 Subject: [PATCH 53/76] working on TetrisOneBlockApp --- dumbdisplay/ddlayer_turtle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dumbdisplay/ddlayer_turtle.py b/dumbdisplay/ddlayer_turtle.py index 5ac8358..d0239e0 100644 --- a/dumbdisplay/ddlayer_turtle.py +++ b/dumbdisplay/ddlayer_turtle.py @@ -136,7 +136,10 @@ def centeredPolygon(self, radius: int, vertex_count: int, inside: bool = False): self.dd._sendCommand(self.layer_id, "cpolyin" if inside else "cpoly", _DD_INT_ARG(radius), _DD_INT_ARG(vertex_count)) def write(self, text: str, align: str = "L"): """write text, with alignment 'L', 'C', or 'R'""" - self._sendCommandTracked("write", str(align), text) + if align == "" or align == "L": + self._sendCommandTracked("write", text) + else: + self._sendCommandTracked("writetext", str(align), text) def drawText(self, text: str, draw: bool = False): """draw the text (honor heading)""" self._sendCommandTracked("drawtext" if draw else "write", text) From 3f04fc1b280d8424b70796ce7907bb940d10b60c Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Mon, 25 Aug 2025 21:07:57 +0800 Subject: [PATCH 54/76] _readFeedback bug fix --- dumbdisplay/ddimpl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index ec2f417..3bdfa8a 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -553,7 +553,10 @@ def _checkForFeedback(self): except: pass def _readFeedback(self) -> str: - (need_reconnect, keep_alive_diff_ms) = self._validateConnection() + validate_res = self._validateConnection() + if validate_res is None: + return None + (need_reconnect, keep_alive_diff_ms) = validate_res if need_reconnect: self.onDetectedDisconnect(keep_alive_diff_ms) if not self._connected_iop.available(): @@ -575,6 +578,8 @@ def _validateConnection(self): else: (need_reconnect, keep_alive_diff_ms) = (None, None) return (need_reconnect, keep_alive_diff_ms) + else: + return None def _setReconnectRCId(self, rc_id: str): if self._connected_iop: self._connected_iop.setReconnectRCId(rc_id) From 3f19fea362de1c9f029dfffdfb02819743b3a45d Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Tue, 26 Aug 2025 22:19:07 +0800 Subject: [PATCH 55/76] updated --- dumbdisplay/ddlayer_graphical.py | 3 ++- .../tetris_one_block/tetris_one_block.py | 10 ++++++---- setup.py | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dumbdisplay/ddlayer_graphical.py b/dumbdisplay/ddlayer_graphical.py index e3b7df5..c808113 100644 --- a/dumbdisplay/ddlayer_graphical.py +++ b/dumbdisplay/ddlayer_graphical.py @@ -151,7 +151,8 @@ def __init__(self, dd: DumbDisplayImpl, width: int, height: int): class DDRootLayer(DDLayerGraphicalBase): """ - it is the root layer of the DumbDisplay; it is basically a graphical layer + it is [the only] root layer of the DumbDisplay, the foundation layer on which all other layers are contained; + it is basically a graphical layer, but without feedback capability """ def __init__(self, dd: DumbDisplayImpl, width: int, height: int, contained_alignment: str = ""): """ diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index e6aba49..b8ba290 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -16,7 +16,7 @@ _INIT_BLOCK_X = 5 _delay = 0.3 # For time/sleep -_grid = [ +_grid = [ # 12x24 [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], @@ -59,6 +59,8 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) + self.n_cols = len(self.grid[0]) + self.n_rows = len(self.grid) def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -107,8 +109,8 @@ def _draw(x, y, color_number, pen: LayerTurtle): # _draw(block.x, block.y, block.color, block_pen) def _draw_grid(grid: Grid, pen: LayerTurtle): - for y in range(24): - for x in range(12): + for y in range(grid.n_rows): + for x in range(grid.n_cols): if not grid.check_reset_need_redraw(y, x): continue color_number = grid.get_value(y, x) @@ -122,7 +124,7 @@ def __init__(self, x: int, y: int, block_pen: LayerTurtle): self.y = 0 self.x = x self.y = y - self.color = random.randint(1, 7) + self.color = random.randint(1, len(_colors) - 1) self.block_pen = block_pen if _USE_LEVEL_ANCHOR_FOR_BLOCK: self.block_pen.clear() diff --git a/setup.py b/setup.py index c907350..ac794de 100644 --- a/setup.py +++ b/setup.py @@ -11,5 +11,5 @@ long_description_content_type="text/markdown", url='https://github.com/trevorwslee/MicroPython-DumbDisplay', license='MIT', -packages=find_packages(include=["dumbdisplay*"]), + packages=find_packages(include=["dumbdisplay*"]), ) From e78d194a7ce2b556b134b80e9ce89db2691d526b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 27 Aug 2025 06:34:59 +0800 Subject: [PATCH 56/76] updated --- .../tetris_one_block/tetris_one_block.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index b8ba290..7e03117 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -15,6 +15,17 @@ _USE_LEVEL_ANCHOR_FOR_BLOCK = True _INIT_BLOCK_X = 5 + + +_width = 400 +_height = 700 +_top = 230 +_left = -110 +_block_unit_width = 20 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] + + _delay = 0.3 # For time/sleep _grid = [ # 12x24 [0,0,0,0,0,0,0,0,0,0,0,0], @@ -82,12 +93,6 @@ def set_value(self, row_idx, col_idx, value): -_top = 230 -_left = -110 -_block_unit_width = 20 -#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] - # def _calc_screen_position(x: int, y : int) -> (int, int): # screen_x = _left + (x * 20) # each turtle 20x20 pixels @@ -259,29 +264,27 @@ def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): self.last_update_time = None def initializeDD(self): - width = 400 - height = 700 - root = DDRootLayer(self.dd, width, height) + root = DDRootLayer(self.dd, _width, _height) root.border(5, "darkred", "round", 1) root.backgroundColor("black") - block_pen = LayerTurtle(self.dd, width, height) + block_pen = LayerTurtle(self.dd, _width, _height) block_pen.penFilled() #block_pen.setTextSize(32) - pen = LayerTurtle(self.dd, width, height) + pen = LayerTurtle(self.dd, _width, _height) pen.penFilled() pen.setTextSize(32) - score = LayerTurtle(self.dd, width, height) + score = LayerTurtle(self.dd, _width, _height) score.penColor('red') score.penUp() score.goTo(60, -300) score.setTextFont("Courier", 24) #score.write('Score: 0', 'C') - border = LayerTurtle(self.dd, width, height) + border = LayerTurtle(self.dd, _width, _height) if False: border.rectangle(260, 490, centered=True) border.penSize(10) From 15f20d8d46c0d5d0674b01e2fe35cc52fea52446 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Wed, 27 Aug 2025 06:35:59 +0800 Subject: [PATCH 57/76] adding more examples --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e5e0108..2422c24 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,7 @@ MIT v0.6.0 - added DDRootLayer - added LayerTurtle +- added more examples - bug fixes v0.5.0 From f27eef37726f3bea8c1db91c1dd5fe7abb4986e5 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 16:06:31 +0800 Subject: [PATCH 58/76] adding more examples --- .../tetris_one_block/tetris_one_block.py | 31 +- ...e_block_OLD_2.py => tetris_one_block_2.py} | 374 ++++++++++-------- .../tetris_one_block_OLD_0.py | 306 -------------- .../tetris_one_block_OLD_1.py | 319 --------------- 4 files changed, 239 insertions(+), 791 deletions(-) rename dumbdisplay_examples/tetris_one_block/{tetris_one_block_OLD_2.py => tetris_one_block_2.py} (50%) delete mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py delete mode 100644 dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py index 7e03117..d5f40d1 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block.py @@ -11,9 +11,9 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_WIN_FASTER = False _USE_LEVEL_ANCHOR_FOR_BLOCK = True _INIT_BLOCK_X = 5 +_RANDOMIZE_ROW_COUNT = 4 @@ -53,16 +53,28 @@ [0,1,1,2,3,0,0,0,4,0,2,0], [2,0,1,2,3,0,6,5,5,5,0,2] ] +_grid_n_cols = len(_grid[0]) # should be 12 +_grid_n_rows = len(_grid) # should be 24 + class Grid: def __init__(self): - if _WIN_FASTER: - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid[23] = _grid[23].copy() + if _RANDOMIZE_ROW_COUNT >= 0: + grid = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid.append(grid_row) + self.grid = grid else: self.grid = [] - for row in _grid: - self.grid.append(row.copy()) + for grid_row in _grid: + self.grid.append(grid_row.copy()) self.grid_dirty = [] for grid_row in self.grid: grid_dirty_row = [] @@ -70,8 +82,8 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - self.n_cols = len(self.grid[0]) - self.n_rows = len(self.grid) + self.n_cols = _grid_n_cols + self.n_rows = _grid_n_rows def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -224,7 +236,7 @@ def check_grid(self, score: LayerTurtle) -> bool: (all_empty, delete_count) = _check_grid(self, score) if delete_count > 0: self.sync_image() - return all_empty + return all_empty and self.block is None def reset_block(self) -> bool: x = 5 @@ -239,6 +251,7 @@ def reset_block(self) -> bool: def commit_block(self): self.block.commit(self.grid) self.sync_image() + self.block = None def move_block_down(self) -> bool: return self.block.move_down(self.grid) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py similarity index 50% rename from dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py rename to dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py index df8bc91..015ba88 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_2.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py @@ -11,8 +11,23 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd +_USE_LEVEL_ANCHOR_FOR_BLOCK = True +_INIT_BLOCK_X = 5 +_RANDOMIZE_ROW_COUNT = 4 + + + +_width = 400 +_height = 700 +_top = 230 +_left = -110 +_block_unit_width = 20 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] + + _delay = 0.3 # For time/sleep -_grid = [ +_grid = [ # 12x24 [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0], @@ -38,33 +53,28 @@ [0,1,1,2,3,0,0,0,4,0,2,0], [2,0,1,2,3,0,6,5,5,5,0,2] ] +_grid_n_cols = len(_grid[0]) # should be 12 +_grid_n_rows = len(_grid) # should be 24 -class GridRow: - def __init__(self, grid, grid_dirty): - self.grid = grid - self.grid_dirty = grid_dirty - self.row_idx = 0 - - def __len__(self): - return len(self.grid[self.row_idx]) - - def __getitem__(self, col_idx): - return self.grid[self.row_idx][col_idx] - - def __setitem__(self, col_idx, value): - self.grid[self.row_idx][col_idx] = value - self.grid_dirty[self.row_idx][col_idx] = True class Grid: def __init__(self): - if True: - self.grid = [] - for row in _grid: - self.grid.append(row.copy()) + if _RANDOMIZE_ROW_COUNT >= 0: + grid = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid.append(grid_row) + self.grid = grid else: - # debug - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] + self.grid = [] + for grid_row in _grid: + self.grid.append(grid_row.copy()) self.grid_dirty = [] for grid_row in self.grid: grid_dirty_row = [] @@ -72,14 +82,8 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow(self.grid, self.grid_dirty) - - def __len__(self): - return len(self.grid) - - def __getitem__(self, row_idx): - self.grid_row.row_idx = row_idx - return self.grid_row + self.n_cols = _grid_n_cols + self.n_rows = _grid_n_rows def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -88,108 +92,115 @@ def check_reset_need_redraw(self, row_idx, col_idx): self.grid_dirty[row_idx][col_idx] = False return True + def get_value(self, row_idx, col_idx): + return self.grid[row_idx][col_idx] + + def set_value(self, row_idx, col_idx, value): + if self.grid[row_idx][col_idx] != value: + self.grid[row_idx][col_idx] = value + self.grid_dirty[row_idx][col_idx] = True + # old_value = self.grid[row_idx][col_idx] + # self.grid[row_idx][col_idx] = value + # self.grid_dirty[row_idx][col_idx] = old_value != value + + + + +# def _calc_screen_position(x: int, y : int) -> (int, int): +# screen_x = _left + (x * 20) # each turtle 20x20 pixels +# screen_y = _top - (y * 20) +# return (screen_x, screen_y) + + +def _draw(x, y, color_number, pen: LayerTurtle): + screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels + screen_y = _top - (y * _block_unit_width) + # (screen_x, screen_y) = _calc_screen_position(x, y) + color = _colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) + +# def _draw_block(block: 'Block', block_pen: LayerTurtle): +# block_pen.clear() +# _draw(block.x, block.y, block.color, block_pen) + +def _draw_grid(grid: Grid, pen: LayerTurtle): + for y in range(grid.n_rows): + for x in range(grid.n_cols): + if not grid.check_reset_need_redraw(y, x): + continue + color_number = grid.get_value(y, x) + _draw(x, y, color_number, pen) + + + class Block: - def __init__(self): - self.x = 5 + def __init__(self, x: int, y: int, block_pen: LayerTurtle): + self.x = _INIT_BLOCK_X self.y = 0 - self.color = random.randint(1, 7) + self.x = x + self.y = y + self.color = random.randint(1, len(_colors) - 1) + self.block_pen = block_pen + if _USE_LEVEL_ANCHOR_FOR_BLOCK: + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) + self.sync_image() - def commit(self, grid): - grid[self.y][self.x] = self.color + def commit(self, grid: Grid): + grid.set_value(self.y, self.x, self.color) + if True: + self.block_pen.clear() - def move_down(self, grid) -> bool: - if self.y < 23 and grid[self.y + 1][self.x] == 0: - #grid[self.y][self.x]=0 + def move_down(self, grid: Grid) -> bool: + if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: self.y += 1 - #grid[self.y][self.x] = self.color + self.sync_image() return True return False - def move_right(self, grid) -> bool: + def move_right(self, grid: Grid) -> bool: if self.x < 11: - if grid[self.y][self.x + 1] == 0: - #grid[self.y][self.x]=0 + if grid.get_value(self.y, self.x + 1) == 0: self.x += 1 - #grid[self.y][self.x] = self.color + self.sync_image() return True return False - def move_left(self, grid) -> bool: + def move_left(self, grid: Grid) -> bool: if self.x > 0: - if grid[self.y][self.x - 1] == 0: - #grid[self.y][self.x]=0 + if grid.get_value(self.y, self.x - 1) == 0: self.x -= 1 - #grid[self.y][self.x] = self.color + self.sync_image() return True return False + def sync_image(self): + if _USE_LEVEL_ANCHOR_FOR_BLOCK: + anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width + anchor_y = self.y * _block_unit_width + #(screen_x, screen_y) = _calc_screen_position(x, y) + self.block_pen.setLevelAnchor(anchor_x, anchor_y) + else: + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) -class Shape: - def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.block: Block = None - #self.reset() - - - def reset_block(self): - self.block = Block() - #self.block.commit(self.grid) - - def commit_block(self): - self.block.commit(self.grid) - - def move_block_down(self) -> bool: - return self.block.move_down(self.grid) - - def move_block_right(self) -> bool: - return self.block.move_right(self.grid) - - def move_block_left(self) -> bool: - return self.block.move_left(self.grid) - - - -_top = 230 -_left = -110 -#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'purple'] - - -def _draw(x, y, color_number, pen: LayerTurtle): - screen_x = _left + (x * 20) # each turtle 20x20 pixels - screen_y = _top - (y * 20) - color = _colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - pen.rectangle(18, 18, centered=True) - -def draw_block(shape: Shape, block_pen: LayerTurtle): - block = shape.block - block_pen.clear() - _draw(block.x, block.y, block.color, block_pen) -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid - for y in range(24): - for x in range(12): - if not grid.check_reset_need_redraw(y, x): - continue - color_number = grid[y][x] - _draw(x, y, color_number, pen) -def check_grid(shape: Shape, score: LayerTurtle) -> bool: +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): grid = shape.grid block = shape.block # Check if each row is full: empty_count = 23 + deleted_count = 0 for y in range(0,24): is_full = True is_empty = True y_erase = y for x in range(0,12): - if grid[y][x] == 0: + if grid.get_value(y, x) == 0: is_full = False else: is_empty = False @@ -204,13 +215,59 @@ def check_grid(shape: Shape, score: LayerTurtle) -> bool: for y in range(y_erase-1, -1, -1): for x in range(0,12): - grid[y + 1][x] = grid[y][x] + grid.set_value(y + 1, x, grid.get_value(y, x)) - return empty_count == 23 + deleted_count += 1 + return (empty_count == 23, deleted_count) -class TetrisOneBlockApp(DDAppBase): +class Shape: + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): + self.grid = Grid() + self.score_count = 0 + self.block: Block = None + self.pen = pen + self.block_pen = block_pen + if not self.reset_block(): + raise Exception("Failed to create initial block") + + def check_grid(self, score: LayerTurtle) -> bool: + (all_empty, delete_count) = _check_grid(self, score) + if delete_count > 0: + self.sync_image() + return all_empty and self.block is None + + def reset_block(self) -> bool: + x = 5 + y = 0 + if self.grid.get_value(y, x) != 0: + #self.sync_image() + return False + self.block = Block(x, y, self.block_pen) + self.sync_image() + return True + + def commit_block(self): + self.block.commit(self.grid) + self.sync_image() + self.block = None + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + def sync_image(self): + #self.block.sync_image() + _draw_grid(self.grid, self.pen) + + +class TetrisOneBlock2App(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) self.score: LayerTurtle = None @@ -220,26 +277,34 @@ def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): self.last_update_time = None def initializeDD(self): - width = 400 - height = 700 - root = DDRootLayer(self.dd, width, height) + root = DDRootLayer(self.dd, _width, _height) root.border(5, "darkred", "round", 1) root.backgroundColor("black") - score = LayerTurtle(self.dd, width, height) + block_pen = LayerTurtle(self.dd, _width, _height) + block_pen.penFilled() + #block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, _width, _height) + pen.penFilled() + pen.setTextSize(32) + + score = LayerTurtle(self.dd, _width, _height) score.penColor('red') score.penUp() score.goTo(60, -300) score.setTextFont("Courier", 24) #score.write('Score: 0', 'C') - border = LayerTurtle(self.dd, width, height) + border = LayerTurtle(self.dd, _width, _height) + if False: + border.rectangle(260, 490, centered=True) border.penSize(10) border.penUp() border.goTo(-130, 240) border.penDown() - border.penColor('white') + border.penColor('linen') border.rightTurn(90) border.forward(490) # Down border.leftTurn(90) @@ -251,13 +316,6 @@ def initializeDD(self): border.setTextFont("Courier", 36) border.write("One-Block TETRIS", "C") - block_pen = LayerTurtle(self.dd, width, height) - block_pen.penFilled() - block_pen.setTextSize(32) - - pen = LayerTurtle(self.dd, width, height) - pen.penFilled() - left_button = LayerLcd(self.dd, 2, 1, char_height=28) left_button.noBackgroundColor() left_button.writeLine("⬅️") @@ -280,9 +338,11 @@ def initializeDD(self): #self.shape = Shape() #self.resetBlock() + self.last_update_time = time.time() + def updateDD(self): now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay + need_update = (now - self.last_update_time) >= _delay if need_update: self.last_update_time = now self.update() @@ -299,29 +359,25 @@ def update(self): if won: self.endGame(won=True) elif not moved_down: - if self.shape.block.y > 0: - #self.shape.reset_block() - self.resetBlock() - else: + if not self.resetBlock(): self.endGame(won=False) - - def drawBlock(self): - draw_block(shape=self.shape, block_pen=self.block_pen) - - def drawGrid(self): - draw_grid(shape=self.shape, pen=self.pen) + # if self.shape.block.y > 0: + # #self.shape.reset_block() + # self.resetBlock() + # else: + # self.endGame(won=False) def startGame(self): self.score.clear() self.score.write('Score: 0', 'C') self.pen.clear() self.block_pen.clear() - self.shape = Shape() - self.resetBlock() + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) + #self.resetBlock() def endGame(self, won: bool): - self.block_pen.clear() + #self.block_pen.clear() self.shape = None if won: msg = "🥳 YOU WON 🥳" @@ -329,49 +385,53 @@ def endGame(self, won: bool): else: msg = "GAME OVER 😔" color = "darkgray" - self.block_pen.home(with_pen=False) - self.block_pen.penColor("white") - self.block_pen.oval(300, 100, centered=True) - self.block_pen.penColor(color) - self.block_pen.write(msg, align='C') + self.pen.home(with_pen=False) + self.pen.penColor("white") + self.pen.oval(300, 100, centered=True) + self.pen.penColor(color) + self.pen.write(msg, align='C') def checkGrid(self) -> bool: - check_result = check_grid(shape=self.shape, score=self.score) - self.drawGrid() # should only redraw if any lines were cleared - return check_result + return self.shape.check_grid(score=self.score) + # check_result = check_grid(shape=self.shape, score=self.score) + # self.drawGrid() # should only redraw if any lines were cleared + # return check_result - def resetBlock(self): - self.shape.reset_block() - self.drawGrid() - self.drawBlock() + def resetBlock(self) -> bool: + return self.shape.reset_block() + #self.drawGrid() + #self.drawBlock() def moveBlockDown(self) -> bool: - if self.shape.move_block_down(): - self.drawBlock() - return True - return False + return self.shape.move_block_down() + # if self.shape.move_block_down(): + # self.drawBlock() + # return True + # return False def moveBlockLeft(self) -> bool: if self.shape is None: self.startGame() return False - if self.shape.move_block_left(): - self.drawBlock() - return True - return False + return self.shape.move_block_left() + # if self.shape.move_block_left(): + # self.drawBlock() + # return True + # return False def moveBlockRight(self) -> bool: if self.shape is None: self.startGame() return False - if self.shape.move_block_right(): - self.drawBlock() - return True - return False + return self.shape.move_block_right() + # if self.shape.move_block_right(): + # self.drawBlock() + # return True + # return False if __name__ == "__main__": from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisOneBlockApp(create_example_wifi_dd()) + app = TetrisOneBlock2App(create_example_wifi_dd()) app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py deleted file mode 100644 index d48a780..0000000 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_0.py +++ /dev/null @@ -1,306 +0,0 @@ -# *** -# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games -# *** - -import time - -from dumbdisplay.core import * -from dumbdisplay.layer_graphical import DDRootLayer -from dumbdisplay.layer_turtle import LayerTurtle -from dumbdisplay.layer_lcd import LayerLcd - -from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd - -_delay = 0.3 # For time/sleep -_grid = [ - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [4,3,3,1,0,1,0,3,3,4,0,1], - [5,1,6,5,3,2,1,0,5,5,0,3], - [1,2,3,4,5,2,0,4,2,3,0,4], - [1,3,1,2,4,2,0,3,1,2,4,3], - [1,1,2,3,0,2,2,2,0,3,0,1], - [0,1,1,2,3,0,0,0,4,0,2,0], - [2,0,1,2,3,0,6,5,5,5,0,2] -] - -class GridRow: - def __init__(self, grid, grid_dirty): - self.grid = grid - self.grid_dirty = grid_dirty - self.row_idx = 0 - def __len__(self): - return len(self.grid[self.row_idx]) - def __getitem__(self, col_idx): - return self.grid[self.row_idx][col_idx] - def __setitem__(self, col_idx, value): - self.grid[self.row_idx][col_idx] = value - self.grid_dirty[self.row_idx][col_idx] = True - -class Grid: - def __init__(self): - if True: - self.grid = [] - for row in _grid: - self.grid.append(row.copy()) - else: - # debug - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] - self.grid_dirty = [] - for grid_row in self.grid: - grid_dirty_row = [] - for cell in grid_row: - dirty = True if cell != 0 else False - grid_dirty_row.append(dirty) - self.grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow(self.grid, self.grid_dirty) - def __len__(self): - return len(self.grid) - def __getitem__(self, row_idx): - self.grid_row.row_idx = row_idx - return self.grid_row - def check_reset_need_redraw(self, row_idx, col_idx): - dirty = self.grid_dirty[row_idx][col_idx] - if not dirty: - return False - self.grid_dirty[row_idx][col_idx] = False - return True - -class Shape: - def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.x = 5 - self.y = 0 - self.color = 4 - self.move = 'go' - - def move_right(self): - if self.x < 11 and self.move == 'go': - if self.grid[self.y][self.x + 1]==0: - self.grid[self.y][self.x]=0 - self.x += 1 - self.grid[self.y][self.x] = self.color - - def move_left(self): - if self.x > 0 and self.move == 'go': - if self.grid[self.y][self.x - 1]==0: - self.grid[self.y][self.x]=0 - self.x -= 1 - self.grid[self.y][self.x] = self.color - - -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid - - #pen.clear() - top = 230 - left = -110 - colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', - 'purple'] - - for y in range(len(grid)): # 24 rows - for x in range(len(grid[0])): # 12 columns - if not grid.check_reset_need_redraw(y, x): - continue - screen_x = left + (x*20) # each turtle 20x20 pixels - screen_y = top - (y*20) - color_number = grid[y][x] - color = colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - #self.pen.stamp() - pen.rectangle(18, 18, centered=True) - - -def check_grid(shape: Shape, score: LayerTurtle) -> bool: - grid = shape.grid - - # Check if each row is full: - for y in range(0,24): - is_full = True - y_erase = y - for x in range(0,12): - if grid[y][x] == 0: - is_full = False - break - # Remove row and shift down - if is_full: - shape.score_count += 1 - score.clear() - score.write(f'Score: {shape.score_count}', align='C') - - for y in range(y_erase-1, -1, -1): - for x in range(0,12): - grid[y + 1][x] = grid[y][x] - - return y_erase == 23 and is_full - - - -class TetrisOneBlockApp(DDAppBase): - def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): - super().__init__(dd) - # self.root: DDRootLayer = None - self.score: LayerTurtle = None - self.pen: LayerTurtle = None - # self.left_button: LayerLcd = None - # self.right_button: LayerLcd = None - self.shape: Shape = None - self.last_update_time = None - - def initializeDD(self): - width = 400 - height = 700 - - root = DDRootLayer(self.dd, width, height) - root.border(5, "darkred", "round", 1) - root.backgroundColor("black") - - score = LayerTurtle(self.dd, width, height) - score.penColor('red') - score.penUp() - #score.hideturtle() - score.goTo(60, -300) - #score.write('Score: 0', align='center', font=('Courier', 24, 'normal')) - score.setTextFont("Courier", 24) - score.write('Score: 0', 'C') - - border = LayerTurtle(self.dd, width, height) - border.penSize(10) - border.penUp() - #border.hideturtle() - border.goTo(-130, 240) - border.penDown() - border.penColor('white') - border.rightTurn(90) - border.forward(490) # Down - border.leftTurn(90) - border.forward(260) # Right - border.leftTurn(90) - border.forward(490) # Up - border.penUp() - border.goTo(0,260) - #border.write("TETRIS", align='center', font=('Courier', 36, 'normal')) - border.setTextFont("Courier", 36) - border.write("TETRIS (one block)", "C") - - pen = LayerTurtle(self.dd, width, height) - #pen.up() - # pen.speed(0) - # pen.shape('square') - # pen.shapesize(0.9, 0.9) - # pen.setundobuffer(None) - pen.penFilled() - - left_button = LayerLcd(self.dd, 2, 1, char_height=28) - left_button.noBackgroundColor() - left_button.writeLine("⬅️") - left_button.enableFeedback("f", lambda *args: self.moveShapeLeft()) - - right_button = LayerLcd(self.dd, 2, 1, char_height=28) - right_button.noBackgroundColor() - right_button.writeLine("➡️") - right_button.enableFeedback("f", lambda *args: self.moveShapeRight()) - - AutoPin('V', - AutoPin('S'), - AutoPin('H', left_button, right_button)).pin(self.dd) - - # self.root = root - self.score = score - self.pen = pen - # self.left_button = left_button - # self.right_button = right_button - - shape = Shape() - shape.grid[shape.y][shape.x] = shape.color - - self.shape = shape - - self.drawGrid() - - def updateDD(self): - #global _dirty - now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay - if need_update: - self.update() - self.last_update_time = now - #_dirty = False - # else: - # if _dirty: - # self.drawGrid() - # #_dirty = False - - def update(self): - if self.shape.move == 'stop': - print("... waiting to restart ...") - return - - # Move shape - # Stop if at the bottom - if self.shape.y == 23: - if self.checkGrid(): - self.shape.move = 'stop' - print("*** YOU WON ***") - else: - self.shape.x = 5 - self.shape.y = 0 - - # Drop down one space if empty below - elif self.shape.grid[self.shape.y + 1][self.shape.x] == 0: - self.shape.grid[self.shape.y][self.shape.x]=0 - self.shape.y += 1 - self.shape.grid[self.shape.y][self.shape.x] = self.shape.color - - # Stop if above another block - else: - self.checkGrid() - if self.shape.y > 0: - self.shape.x = 5 - self.shape.y = 0 - else: - self.shape.move = 'stop' - print("*** GAME OVER ***") - - self.drawGrid() - - def drawGrid(self): - self.dd.freezeDrawing() - draw_grid(shape=self.shape, pen=self.pen) - self.dd.unfreezeDrawing() - - - def checkGrid(self) -> bool: - return check_grid(shape=self.shape, score=self.score) - - def moveShapeLeft(self): - self.shape.move_left() - self.drawGrid() - - def moveShapeRight(self): - self.shape.move_right() - self.drawGrid() - - -if __name__ == "__main__": - from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisOneBlockApp(create_example_wifi_dd()) - app.run() diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py deleted file mode 100644 index 208d0d7..0000000 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_OLD_1.py +++ /dev/null @@ -1,319 +0,0 @@ -# *** -# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games -# *** -import random -import time - -from dumbdisplay.core import * -from dumbdisplay.layer_graphical import DDRootLayer -from dumbdisplay.layer_turtle import LayerTurtle -from dumbdisplay.layer_lcd import LayerLcd - -from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd - -_delay = 0.3 # For time/sleep -_grid = [ - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [4,3,3,1,0,1,0,3,3,4,0,1], - [5,1,6,5,3,2,1,0,5,5,0,3], - [1,2,3,4,5,2,0,4,2,3,0,4], - [1,3,1,2,4,2,0,3,1,2,4,3], - [1,1,2,3,0,2,2,2,0,3,0,1], - [0,1,1,2,3,0,0,0,4,0,2,0], - [2,0,1,2,3,0,6,5,5,5,0,2] -] - -class GridRow: - def __init__(self, grid, grid_dirty): - self.grid = grid - self.grid_dirty = grid_dirty - self.row_idx = 0 - def __len__(self): - return len(self.grid[self.row_idx]) - def __getitem__(self, col_idx): - return self.grid[self.row_idx][col_idx] - def __setitem__(self, col_idx, value): - self.grid[self.row_idx][col_idx] = value - self.grid_dirty[self.row_idx][col_idx] = True - -class Grid: - def __init__(self): - if True: - self.grid = [] - for row in _grid: - self.grid.append(row.copy()) - else: - # debug - self.grid = [[0 for _ in range(12)] for _ in range(24)] - self.grid[23] = [2,0,1,2,3,0,6,5,5,5,0,2] - self.grid_dirty = [] - for grid_row in self.grid: - grid_dirty_row = [] - for cell in grid_row: - dirty = True if cell != 0 else False - grid_dirty_row.append(dirty) - self.grid_dirty.append(grid_dirty_row) - self.grid_row = GridRow(self.grid, self.grid_dirty) - def __len__(self): - return len(self.grid) - def __getitem__(self, row_idx): - self.grid_row.row_idx = row_idx - return self.grid_row - def check_reset_need_redraw(self, row_idx, col_idx): - dirty = self.grid_dirty[row_idx][col_idx] - if not dirty: - return False - self.grid_dirty[row_idx][col_idx] = False - return True - -class Block: - def __init__(self): - self.x = 5 - self.y = 0 - self.color = random.randint(1, 7) - - def commit(self, grid): - grid[self.y][self.x] = self.color - - def move_down(self, grid) -> bool: - if self.y < 23 and grid[self.y + 1][self.x] == 0: - grid[self.y][self.x]=0 - self.y += 1 - grid[self.y][self.x] = self.color - return True - return False - - def move_right(self, grid) -> bool: - if self.x < 11: - if grid[self.y][self.x + 1]==0: - grid[self.y][self.x]=0 - self.x += 1 - grid[self.y][self.x] = self.color - return True - return False - - def move_left(self, grid) -> bool: - if self.x > 0: - if grid[self.y][self.x - 1]==0: - grid[self.y][self.x]=0 - self.x -= 1 - grid[self.y][self.x] = self.color - return True - return False - - -class Shape: - def __init__(self): - self.grid = Grid() - self.score_count = 0 - self.block: Block = None - #self.reset() - - def reset(self): - self.block = Block() - self.block.commit(self.grid) - - def move_down(self) -> bool: - return self.block.move_down(self.grid) - - def move_right(self) -> bool: - return self.block.move_right(self.grid) - - def move_left(self) -> bool: - return self.block.move_left(self.grid) - - -def draw_grid(shape: Shape, pen: LayerTurtle): - grid = shape.grid - - #pen.clear() - top = 230 - left = -110 - colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', - 'purple'] - - for y in range(len(grid)): # 24 rows - for x in range(len(grid[0])): # 12 columns - if not grid.check_reset_need_redraw(y, x): - continue - screen_x = left + (x*20) # each turtle 20x20 pixels - screen_y = top - (y*20) - color_number = grid[y][x] - color = colors[color_number] - pen.penColor(color) - pen.goTo(screen_x, screen_y, with_pen=False) - #self.pen.stamp() - pen.rectangle(18, 18, centered=True) - - -def check_grid(shape: Shape, score: LayerTurtle) -> bool: - grid = shape.grid - - # Check if each row is full: - empty_count = 23 - for y in range(0,24): - is_full = True - is_empty = True - y_erase = y - for x in range(0,12): - if grid[y][x] == 0: - is_full = False - else: - is_empty = False - if not is_empty and not is_full: - empty_count -= 1 - break - # Remove row and shift down - if is_full: - shape.score_count += 1 - score.clear() - score.write(f'Score: {shape.score_count}', align='C') - - for y in range(y_erase-1, -1, -1): - for x in range(0,12): - grid[y + 1][x] = grid[y][x] - - return empty_count == 23 - - - -class TetrisOneBlockApp(DDAppBase): - def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): - super().__init__(dd) - self.score: LayerTurtle = None - self.pen: LayerTurtle = None - self.shape: Shape = None - self.last_update_time = None - - def initializeDD(self): - width = 400 - height = 700 - - root = DDRootLayer(self.dd, width, height) - root.border(5, "darkred", "round", 1) - root.backgroundColor("black") - - score = LayerTurtle(self.dd, width, height) - score.penColor('red') - score.penUp() - score.goTo(60, -300) - score.setTextFont("Courier", 24) - score.write('Score: 0', 'C') - - border = LayerTurtle(self.dd, width, height) - border.penSize(10) - border.penUp() - border.goTo(-130, 240) - border.penDown() - border.penColor('white') - border.rightTurn(90) - border.forward(490) # Down - border.leftTurn(90) - border.forward(260) # Right - border.leftTurn(90) - border.forward(490) # Up - border.penUp() - border.goTo(0,260) - border.setTextFont("Courier", 36) - border.write("TETRIS (one block)", "C") - - pen = LayerTurtle(self.dd, width, height) - pen.penFilled() - - left_button = LayerLcd(self.dd, 2, 1, char_height=28) - left_button.noBackgroundColor() - left_button.writeLine("⬅️") - left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) - - right_button = LayerLcd(self.dd, 2, 1, char_height=28) - right_button.noBackgroundColor() - right_button.writeLine("➡️") - right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) - - AutoPin('V', - AutoPin('S'), - AutoPin('H', left_button, right_button)).pin(self.dd) - - self.score = score - self.pen = pen - - self.shape = Shape() - self.resetBlock() - - def updateDD(self): - now = time.time() - need_update = self.last_update_time is None or (now - self.last_update_time) >= _delay - if need_update: - self.last_update_time = now - self.update() - - def update(self): - if self.shape is None: - print("... waiting to restart ...") - return - - moved_down = self.moveBlockDown() - won = self.checkGrid() - if won: - self.shape = None - print("*** YOU WON ***") - elif not moved_down: - if self.shape.block.y > 0: - self.shape.reset() - else: - self.shape = None - print("*** GAME OVER ***") - - def drawGrid(self): - draw_grid(shape=self.shape, pen=self.pen) - - - def checkGrid(self) -> bool: - check_result = check_grid(shape=self.shape, score=self.score) - self.drawGrid() # should only redraw if any lines were cleared - return check_result - - def resetBlock(self): - self.shape.reset() - self.drawGrid() - - - def moveBlockDown(self) -> bool: - if self.shape.move_down(): - self.drawGrid() - return True - return False - - def moveBlockLeft(self) -> bool: - if self.shape.move_left(): - self.drawGrid() - return True - return False - - def moveBlockRight(self) -> bool: - if self.shape.move_right(): - self.drawGrid() - return True - return False - - -if __name__ == "__main__": - from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisOneBlockApp(create_example_wifi_dd()) - app.run() From 76bdd1d0967595de4e0d074ceac93caa367566c9 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 17:05:24 +0800 Subject: [PATCH 59/76] adding more examples --- .../tetris_one_block/tetris_one_block_2.py | 142 +++++++----------- 1 file changed, 58 insertions(+), 84 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py index 015ba88..ad6a5bb 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py @@ -1,6 +1,7 @@ # *** # *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games # *** + import random import time @@ -11,12 +12,10 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_USE_LEVEL_ANCHOR_FOR_BLOCK = True -_INIT_BLOCK_X = 5 +#_INIT_BLOCK_X = 5 _RANDOMIZE_ROW_COUNT = 4 - _width = 400 _height = 700 _top = 230 @@ -27,54 +26,12 @@ _delay = 0.3 # For time/sleep -_grid = [ # 12x24 - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [4,3,3,1,0,1,0,3,3,4,0,1], - [5,1,6,5,3,2,1,0,5,5,0,3], - [1,2,3,4,5,2,0,4,2,3,0,4], - [1,3,1,2,4,2,0,3,1,2,4,3], - [1,1,2,3,0,2,2,2,0,3,0,1], - [0,1,1,2,3,0,0,0,4,0,2,0], - [2,0,1,2,3,0,6,5,5,5,0,2] -] -_grid_n_cols = len(_grid[0]) # should be 12 -_grid_n_rows = len(_grid) # should be 24 - +_grid_n_cols = 12 +_grid_n_rows = 24 class Grid: - def __init__(self): - if _RANDOMIZE_ROW_COUNT >= 0: - grid = [] - for y in range(_grid_n_rows): - grid_row = [] - for x in range(_grid_n_cols): - if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: - color = random.randint(1, len(_colors) - 1) - else: - color = 0 - grid_row.append(color) - grid.append(grid_row) - self.grid = grid - else: - self.grid = [] - for grid_row in _grid: - self.grid.append(grid_row.copy()) + def __init__(self, grid): + self.grid = grid self.grid_dirty = [] for grid_row in self.grid: grid_dirty_row = [] @@ -82,8 +39,8 @@ def __init__(self): dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - self.n_cols = _grid_n_cols - self.n_rows = _grid_n_rows + self.n_cols = len(self.grid[0]) + self.n_rows = len(self.grid) def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -99,18 +56,43 @@ def set_value(self, row_idx, col_idx, value): if self.grid[row_idx][col_idx] != value: self.grid[row_idx][col_idx] = value self.grid_dirty[row_idx][col_idx] = True - # old_value = self.grid[row_idx][col_idx] - # self.grid[row_idx][col_idx] = value - # self.grid_dirty[row_idx][col_idx] = old_value != value - - - -# def _calc_screen_position(x: int, y : int) -> (int, int): -# screen_x = _left + (x * 20) # each turtle 20x20 pixels -# screen_y = _top - (y * 20) -# return (screen_x, screen_y) +# def _draw(x, y, color_number, pen: LayerTurtle): +# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels +# screen_y = _top - (y * _block_unit_width) +# # (screen_x, screen_y) = _calc_screen_position(x, y) +# color = _colors[color_number] +# pen.penColor(color) +# pen.goTo(screen_x, screen_y, with_pen=False) +# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) + + +def _randomize_block_grid() -> Grid: + color = random.randint(1, len(_colors) - 1) + block_grid = [[color]] + return Grid(grid=block_grid) + +def _randomize_grid() -> Grid: + grid = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid.append(grid_row) + return Grid(grid=grid) + +def _check_block_grid_overlap(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + if block_grid.get_value(y, x) != 0: + if grid.get_value(y + block_grid_y_offset, x + block_grid_x_off) != 0: + return True + return False def _draw(x, y, color_number, pen: LayerTurtle): screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels @@ -121,10 +103,6 @@ def _draw(x, y, color_number, pen: LayerTurtle): pen.goTo(screen_x, screen_y, with_pen=False) pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) -# def _draw_block(block: 'Block', block_pen: LayerTurtle): -# block_pen.clear() -# _draw(block.x, block.y, block.color, block_pen) - def _draw_grid(grid: Grid, pen: LayerTurtle): for y in range(grid.n_rows): for x in range(grid.n_cols): @@ -136,17 +114,19 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): class Block: - def __init__(self, x: int, y: int, block_pen: LayerTurtle): - self.x = _INIT_BLOCK_X - self.y = 0 + def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): + # self.x = _INIT_BLOCK_X + # self.y = 0 self.x = x self.y = y - self.color = random.randint(1, len(_colors) - 1) + self.block_grid = block_grid + self.color = block_grid.get_value(0, 0) # assume a single cell + #self.color = random.randint(1, len(_colors) - 1) self.block_pen = block_pen - if _USE_LEVEL_ANCHOR_FOR_BLOCK: - self.block_pen.clear() - _draw(self.x, self.y, self.color, self.block_pen) + self.block_pen.clear() self.sync_image() + #_draw_grid(block_grid, block_pen) + #_draw(self.x, self.y, self.color, self.block_pen) def commit(self, grid: Grid): grid.set_value(self.y, self.x, self.color) @@ -177,14 +157,8 @@ def move_left(self, grid: Grid) -> bool: return False def sync_image(self): - if _USE_LEVEL_ANCHOR_FOR_BLOCK: - anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width - anchor_y = self.y * _block_unit_width - #(screen_x, screen_y) = _calc_screen_position(x, y) - self.block_pen.setLevelAnchor(anchor_x, anchor_y) - else: - self.block_pen.clear() - _draw(self.x, self.y, self.color, self.block_pen) + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) @@ -224,7 +198,7 @@ def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): class Shape: def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): - self.grid = Grid() + self.grid = _randomize_grid() self.score_count = 0 self.block: Block = None self.pen = pen @@ -241,10 +215,10 @@ def check_grid(self, score: LayerTurtle) -> bool: def reset_block(self) -> bool: x = 5 y = 0 - if self.grid.get_value(y, x) != 0: - #self.sync_image() + block_grid = _randomize_block_grid() + if _check_block_grid_overlap(block_grid, x, y, grid=self.grid): return False - self.block = Block(x, y, self.block_pen) + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) self.sync_image() return True From 76590ef289d255db2e7bffab6d589c6ca59fd122 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 17:36:41 +0800 Subject: [PATCH 60/76] adding more examples --- .../tetris_one_block/tetris_one_block_2.py | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py index ad6a5bb..42c56b2 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py @@ -12,8 +12,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -#_INIT_BLOCK_X = 5 -_RANDOMIZE_ROW_COUNT = 4 +_RANDOMIZE_ROW_COUNT = 21 _width = 400 @@ -23,24 +22,24 @@ _block_unit_width = 20 #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] +_grid_n_cols = 12 +_grid_n_rows = 24 _delay = 0.3 # For time/sleep -_grid_n_cols = 12 -_grid_n_rows = 24 class Grid: - def __init__(self, grid): - self.grid = grid + def __init__(self, grid_cells): + self.grid_cells = grid_cells self.grid_dirty = [] - for grid_row in self.grid: + for grid_row in self.grid_cells: grid_dirty_row = [] for cell in grid_row: dirty = True if cell != 0 else False grid_dirty_row.append(dirty) self.grid_dirty.append(grid_dirty_row) - self.n_cols = len(self.grid[0]) - self.n_rows = len(self.grid) + self.n_cols = len(self.grid_cells[0]) + self.n_rows = len(self.grid_cells) def check_reset_need_redraw(self, row_idx, col_idx): dirty = self.grid_dirty[row_idx][col_idx] @@ -50,11 +49,11 @@ def check_reset_need_redraw(self, row_idx, col_idx): return True def get_value(self, row_idx, col_idx): - return self.grid[row_idx][col_idx] + return self.grid_cells[row_idx][col_idx] def set_value(self, row_idx, col_idx, value): - if self.grid[row_idx][col_idx] != value: - self.grid[row_idx][col_idx] = value + if self.grid_cells[row_idx][col_idx] != value: + self.grid_cells[row_idx][col_idx] = value self.grid_dirty[row_idx][col_idx] = True @@ -71,10 +70,10 @@ def set_value(self, row_idx, col_idx, value): def _randomize_block_grid() -> Grid: color = random.randint(1, len(_colors) - 1) block_grid = [[color]] - return Grid(grid=block_grid) + return Grid(grid_cells=block_grid) def _randomize_grid() -> Grid: - grid = [] + grid_cells = [] for y in range(_grid_n_rows): grid_row = [] for x in range(_grid_n_cols): @@ -83,8 +82,8 @@ def _randomize_grid() -> Grid: else: color = 0 grid_row.append(color) - grid.append(grid_row) - return Grid(grid=grid) + grid_cells.append(grid_row) + return Grid(grid_cells=grid_cells) def _check_block_grid_overlap(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: for y in range(block_grid.n_rows): @@ -94,6 +93,15 @@ def _check_block_grid_overlap(block_grid: Grid, block_grid_x_off: int, block_gri return True return False + +def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + color = block_grid.get_value(y, x) + if color != 0: + grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) + + def _draw(x, y, color_number, pen: LayerTurtle): screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels screen_y = _top - (y * _block_unit_width) @@ -115,18 +123,14 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): class Block: def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): - # self.x = _INIT_BLOCK_X - # self.y = 0 self.x = x self.y = y self.block_grid = block_grid self.color = block_grid.get_value(0, 0) # assume a single cell - #self.color = random.randint(1, len(_colors) - 1) self.block_pen = block_pen - self.block_pen.clear() + block_pen.clear() self.sync_image() - #_draw_grid(block_grid, block_pen) - #_draw(self.x, self.y, self.color, self.block_pen) + _draw_grid(block_grid, block_pen) def commit(self, grid: Grid): grid.set_value(self.y, self.x, self.color) @@ -157,8 +161,10 @@ def move_left(self, grid: Grid) -> bool: return False def sync_image(self): - self.block_pen.clear() - _draw(self.x, self.y, self.color, self.block_pen) + #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width + anchor_x = self.x * _block_unit_width + anchor_y = self.y * _block_unit_width + self.block_pen.setLevelAnchor(anchor_x, anchor_y) @@ -223,7 +229,8 @@ def reset_block(self) -> bool: return True def commit_block(self): - self.block.commit(self.grid) + _commit_block_grid(self.block.block_grid, self.block.x, self.block.y, self.grid) + #self.block.commit(self.grid) self.sync_image() self.block = None From a40004280a8b192dac6c692fbeceee0b234a5a4d Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 18:41:31 +0800 Subject: [PATCH 61/76] adding TetrisOneBlock2App --- .../tetris_one_block/tetris_one_block_2.py | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py index 42c56b2..7bb5302 100644 --- a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py +++ b/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py @@ -12,7 +12,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -_RANDOMIZE_ROW_COUNT = 21 +_RANDOMIZE_ROW_COUNT = 2 _width = 400 @@ -69,7 +69,17 @@ def set_value(self, row_idx, col_idx, value): def _randomize_block_grid() -> Grid: color = random.randint(1, len(_colors) - 1) - block_grid = [[color]] + if True: + n_rows = random.randint(1, 2) + n_cols = random.randint(1, 2) + block_grid = [] + for y in range(n_rows): + block_grid_row = [] + for x in range(n_cols): + block_grid_row.append(color) + block_grid.append(block_grid_row) + else: + block_grid = [[color, color]] return Grid(grid_cells=block_grid) def _randomize_grid() -> Grid: @@ -85,11 +95,17 @@ def _randomize_grid() -> Grid: grid_cells.append(grid_row) return Grid(grid_cells=grid_cells) -def _check_block_grid_overlap(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: +def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: for y in range(block_grid.n_rows): for x in range(block_grid.n_cols): if block_grid.get_value(y, x) != 0: - if grid.get_value(y + block_grid_y_offset, x + block_grid_x_off) != 0: + row_idx = y + block_grid_y_offset + col_idx = x + block_grid_x_off + if row_idx < 0 or row_idx >= grid.n_rows: + return True + if col_idx < 0 or col_idx >= grid.n_cols: + return True + if grid.get_value(row_idx, col_idx) != 0: return True return False @@ -126,39 +142,31 @@ def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): self.x = x self.y = y self.block_grid = block_grid - self.color = block_grid.get_value(0, 0) # assume a single cell self.block_pen = block_pen block_pen.clear() self.sync_image() _draw_grid(block_grid, block_pen) - def commit(self, grid: Grid): - grid.set_value(self.y, self.x, self.color) - if True: - self.block_pen.clear() - def move_down(self, grid: Grid) -> bool: - if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: - self.y += 1 - self.sync_image() - return True - return False + if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): + return False + self.y += 1 + self.sync_image() + return True def move_right(self, grid: Grid) -> bool: - if self.x < 11: - if grid.get_value(self.y, self.x + 1) == 0: - self.x += 1 - self.sync_image() - return True - return False + if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): + return False + self.x += 1 + self.sync_image() + return True def move_left(self, grid: Grid) -> bool: - if self.x > 0: - if grid.get_value(self.y, self.x - 1) == 0: - self.x -= 1 - self.sync_image() - return True - return False + if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): + return False + self.x -= 1 + self.sync_image() + return True def sync_image(self): #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width @@ -222,7 +230,9 @@ def reset_block(self) -> bool: x = 5 y = 0 block_grid = _randomize_block_grid() - if _check_block_grid_overlap(block_grid, x, y, grid=self.grid): + x -= 1 - block_grid.n_cols + y -= 1 - block_grid.n_rows + if _check_can_place_block_grid(block_grid, x, y, grid=self.grid): return False self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) self.sync_image() From 2f67a870f7bc4a5b4265f7f586ebf27fa845f5ad Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 19:58:58 +0800 Subject: [PATCH 62/76] adding more examples --- dumbdisplay_examples/tetris/tetris_common.py | 66 +++ .../tetris/tetris_one_block.py | 453 ++++++++++++++++++ .../tetris/tetris_two_block.py | 430 +++++++++++++++++ 3 files changed, 949 insertions(+) create mode 100644 dumbdisplay_examples/tetris/tetris_common.py create mode 100644 dumbdisplay_examples/tetris/tetris_one_block.py create mode 100644 dumbdisplay_examples/tetris/tetris_two_block.py diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py new file mode 100644 index 0000000..04e49e7 --- /dev/null +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -0,0 +1,66 @@ +from dumbdisplay.layer_turtle import LayerTurtle + + +_width = 400 +_height = 700 +_top = 230 +_left = -110 +_block_unit_width = 20 +#_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +_colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] + +_grid_n_cols = 12 +_grid_n_rows = 24 + + +class Grid: + def __init__(self, grid_cells): + self.grid_cells = grid_cells + self.grid_dirty = [] + for grid_row in self.grid_cells: + grid_dirty_row = [] + for cell in grid_row: + dirty = True if cell != 0 else False + grid_dirty_row.append(dirty) + self.grid_dirty.append(grid_dirty_row) + self.n_cols = len(self.grid_cells[0]) + self.n_rows = len(self.grid_cells) + + def check_reset_need_redraw(self, row_idx, col_idx): + dirty = self.grid_dirty[row_idx][col_idx] + if not dirty: + return False + self.grid_dirty[row_idx][col_idx] = False + return True + + def get_value(self, row_idx, col_idx): + return self.grid_cells[row_idx][col_idx] + + def set_value(self, row_idx, col_idx, value): + if self.grid_cells[row_idx][col_idx] != value: + self.grid_cells[row_idx][col_idx] = value + self.grid_dirty[row_idx][col_idx] = True + + +def _draw(x, y, color_number, pen: LayerTurtle): + screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels + screen_y = _top - (y * _block_unit_width) + # (screen_x, screen_y) = _calc_screen_position(x, y) + color = _colors[color_number] + pen.penColor(color) + pen.goTo(screen_x, screen_y, with_pen=False) + pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) + +# def _draw_block(block: 'Block', block_pen: LayerTurtle): +# block_pen.clear() +# _draw(block.x, block.y, block.color, block_pen) + +def _draw_grid(grid: Grid, pen: LayerTurtle): + for y in range(grid.n_rows): + for x in range(grid.n_cols): + if not grid.check_reset_need_redraw(y, x): + continue + color_number = grid.get_value(y, x) + _draw(x, y, color_number, pen) + + diff --git a/dumbdisplay_examples/tetris/tetris_one_block.py b/dumbdisplay_examples/tetris/tetris_one_block.py new file mode 100644 index 0000000..aed6708 --- /dev/null +++ b/dumbdisplay_examples/tetris/tetris_one_block.py @@ -0,0 +1,453 @@ +# *** +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games +# *** + +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd +from dumbdisplay_examples.tetris.tetris_common import Grid, _draw, _draw_grid, _width, _height, _colors, \ + _block_unit_width, _grid_n_rows, _grid_n_cols + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +#_USE_LEVEL_ANCHOR_FOR_BLOCK = True +#_INIT_BLOCK_X = 5 +_RANDOMIZE_ROW_COUNT = 1 + + + +# _width = 400 +# _height = 700 +# _top = 230 +# _left = -110 +# _block_unit_width = 20 +# #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +# _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] + + +_delay = 0.3 # For time/sleep +_grid = [ # 12x24 + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [4,3,3,1,0,1,0,3,3,4,0,1], + [5,1,6,5,3,2,1,0,5,5,0,3], + [1,2,3,4,5,2,0,4,2,3,0,4], + [1,3,1,2,4,2,0,3,1,2,4,3], + [1,1,2,3,0,2,2,2,0,3,0,1], + [0,1,1,2,3,0,0,0,4,0,2,0], + [2,0,1,2,3,0,6,5,5,5,0,2] +] +# _grid_n_cols = len(_grid[0]) # should be 12 +# _grid_n_rows = len(_grid) # should be 24 + + +# class Grid: +# def __init__(self): +# if _RANDOMIZE_ROW_COUNT >= 0: +# grid = [] +# for y in range(_grid_n_rows): +# grid_row = [] +# for x in range(_grid_n_cols): +# if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: +# color = random.randint(1, len(_colors) - 1) +# else: +# color = 0 +# grid_row.append(color) +# grid.append(grid_row) +# self.grid = grid +# else: +# self.grid = [] +# for grid_row in _grid: +# self.grid.append(grid_row.copy()) +# self.grid_dirty = [] +# for grid_row in self.grid: +# grid_dirty_row = [] +# for cell in grid_row: +# dirty = True if cell != 0 else False +# grid_dirty_row.append(dirty) +# self.grid_dirty.append(grid_dirty_row) +# self.n_cols = _grid_n_cols +# self.n_rows = _grid_n_rows +# +# def check_reset_need_redraw(self, row_idx, col_idx): +# dirty = self.grid_dirty[row_idx][col_idx] +# if not dirty: +# return False +# self.grid_dirty[row_idx][col_idx] = False +# return True +# +# def get_value(self, row_idx, col_idx): +# return self.grid[row_idx][col_idx] +# +# def set_value(self, row_idx, col_idx, value): +# if self.grid[row_idx][col_idx] != value: +# self.grid[row_idx][col_idx] = value +# self.grid_dirty[row_idx][col_idx] = True +# # old_value = self.grid[row_idx][col_idx] +# # self.grid[row_idx][col_idx] = value +# # self.grid_dirty[row_idx][col_idx] = old_value != value +# +# + + +# def _calc_screen_position(x: int, y : int) -> (int, int): +# screen_x = _left + (x * 20) # each turtle 20x20 pixels +# screen_y = _top - (y * 20) +# return (screen_x, screen_y) + + +def _create_grid() -> Grid: + if _RANDOMIZE_ROW_COUNT >= 0: + grid = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid.append(grid_row) + else: + grid = [] + for grid_row in _grid: + grid.append(grid_row.copy()) + return Grid(grid) + + +# def _draw(x, y, color_number, pen: LayerTurtle): +# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels +# screen_y = _top - (y * _block_unit_width) +# # (screen_x, screen_y) = _calc_screen_position(x, y) +# color = _colors[color_number] +# pen.penColor(color) +# pen.goTo(screen_x, screen_y, with_pen=False) +# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) +# +# # def _draw_block(block: 'Block', block_pen: LayerTurtle): +# # block_pen.clear() +# # _draw(block.x, block.y, block.color, block_pen) +# +# def _draw_grid(grid: Grid, pen: LayerTurtle): +# for y in range(grid.n_rows): +# for x in range(grid.n_cols): +# if not grid.check_reset_need_redraw(y, x): +# continue +# color_number = grid.get_value(y, x) +# _draw(x, y, color_number, pen) + + + +class Block: + def __init__(self, x: int, y: int, block_pen: LayerTurtle): + # self.x = _INIT_BLOCK_X + # self.y = 0 + self.x = x + self.y = y + self.color = random.randint(1, len(_colors) - 1) + self.block_pen = block_pen + # if _USE_LEVEL_ANCHOR_FOR_BLOCK: + # self.block_pen.clear() + # _draw(self.x, self.y, self.color, self.block_pen) + self.sync_image() + + def commit(self, grid: Grid): + grid.set_value(self.y, self.x, self.color) + if True: + self.block_pen.clear() + + def move_down(self, grid: Grid) -> bool: + if self.y < 23 and grid.get_value(self.y + 1, self.x) == 0: + self.y += 1 + self.sync_image() + return True + return False + + def move_right(self, grid: Grid) -> bool: + if self.x < 11: + if grid.get_value(self.y, self.x + 1) == 0: + self.x += 1 + self.sync_image() + return True + return False + + def move_left(self, grid: Grid) -> bool: + if self.x > 0: + if grid.get_value(self.y, self.x - 1) == 0: + self.x -= 1 + self.sync_image() + return True + return False + + def sync_image(self): + self.block_pen.clear() + _draw(self.x, self.y, self.color, self.block_pen) + + + +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): + grid = shape.grid + block = shape.block + + # Check if each row is full: + empty_count = 23 + deleted_count = 0 + for y in range(0,24): + is_full = True + is_empty = True + y_erase = y + for x in range(0,12): + if grid.get_value(y, x) == 0: + is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid.set_value(y + 1, x, grid.get_value(y, x)) + + deleted_count += 1 + + return (empty_count == 23, deleted_count) + + +class Shape: + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): + self.grid = _create_grid() + self.score_count = 0 + self.block: Block = None + self.pen = pen + self.block_pen = block_pen + if not self.reset_block(): + raise Exception("Failed to create initial block") + + def check_grid(self, score: LayerTurtle) -> bool: + (all_empty, delete_count) = _check_grid(self, score) + if delete_count > 0: + self.sync_image() + return all_empty and self.block is None + + def reset_block(self) -> bool: + x = 5 + y = 0 + if self.grid.get_value(y, x) != 0: + #self.sync_image() + return False + self.block = Block(x, y, self.block_pen) + self.sync_image() + return True + + def commit_block(self): + self.block.commit(self.grid) + self.sync_image() + self.block = None + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + def sync_image(self): + #self.block.sync_image() + _draw_grid(self.grid, self.pen) + + +class TetrisOneBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.score: LayerTurtle = None + self.block_pen: LayerTurtle = None + self.pen: LayerTurtle = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + + root = DDRootLayer(self.dd, _width, _height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + block_pen = LayerTurtle(self.dd, _width, _height) + block_pen.penFilled() + #block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, _width, _height) + pen.penFilled() + pen.setTextSize(32) + + score = LayerTurtle(self.dd, _width, _height) + score.penColor('red') + score.penUp() + score.goTo(60, -300) + score.setTextFont("Courier", 24) + #score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, _width, _height) + if False: + border.rectangle(260, 490, centered=True) + border.penSize(10) + border.penUp() + border.goTo(-130, 240) + border.penDown() + border.penColor('linen') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + border.setTextFont("Courier", 36) + border.write("One-Block TETRIS", "C") + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.score = score + self.block_pen = block_pen + self.pen = pen + + self.startGame() + #self.shape = Shape() + #self.resetBlock() + + self.last_update_time = time.time() + + def updateDD(self): + now = time.time() + need_update = (now - self.last_update_time) >= _delay + if need_update: + self.last_update_time = now + self.update() + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + moved_down = self.moveBlockDown() + if not moved_down: + self.shape.commit_block() + won = self.checkGrid() + if won: + self.endGame(won=True) + elif not moved_down: + if not self.resetBlock(): + self.endGame(won=False) + # if self.shape.block.y > 0: + # #self.shape.reset_block() + # self.resetBlock() + # else: + # self.endGame(won=False) + + def startGame(self): + self.score.clear() + self.score.write('Score: 0', 'C') + self.pen.clear() + self.block_pen.clear() + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) + #self.resetBlock() + + + def endGame(self, won: bool): + #self.block_pen.clear() + self.shape = None + if won: + msg = "🥳 YOU WON 🥳" + color = "purple" + else: + msg = "GAME OVER 😔" + color = "darkgray" + self.pen.home(with_pen=False) + self.pen.penColor("white") + self.pen.oval(300, 100, centered=True) + self.pen.penColor(color) + self.pen.write(msg, align='C') + + + def checkGrid(self) -> bool: + return self.shape.check_grid(score=self.score) + # check_result = check_grid(shape=self.shape, score=self.score) + # self.drawGrid() # should only redraw if any lines were cleared + # return check_result + + def resetBlock(self) -> bool: + return self.shape.reset_block() + #self.drawGrid() + #self.drawBlock() + + def moveBlockDown(self) -> bool: + return self.shape.move_block_down() + # if self.shape.move_block_down(): + # self.drawBlock() + # return True + # return False + + def moveBlockLeft(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_left() + # if self.shape.move_block_left(): + # self.drawBlock() + # return True + # return False + + def moveBlockRight(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_right() + # if self.shape.move_block_right(): + # self.drawBlock() + # return True + # return False + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisOneBlockApp(create_example_wifi_dd()) + app.run() diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py new file mode 100644 index 0000000..5f9f5ed --- /dev/null +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -0,0 +1,430 @@ +# *** +# *** Adapted from TETRIS ONE BLOCK\tetris_one_block.py of https://github.com/DimaGutierrez/Python-Games +# *** + +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd +from dumbdisplay_examples.tetris.tetris_common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ + _width, _height, _left, _top, _draw_grid + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_RANDOMIZE_ROW_COUNT = 2 + + +# _width = 400 +# _height = 700 +# _top = 230 +# _left = -110 +# _block_unit_width = 20 +# #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] +# _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] +# _grid_n_cols = 12 +# _grid_n_rows = 24 + + +_delay = 0.3 # For time/sleep + +# class Grid: +# def __init__(self, grid_cells): +# self.grid_cells = grid_cells +# self.grid_dirty = [] +# for grid_row in self.grid_cells: +# grid_dirty_row = [] +# for cell in grid_row: +# dirty = True if cell != 0 else False +# grid_dirty_row.append(dirty) +# self.grid_dirty.append(grid_dirty_row) +# self.n_cols = len(self.grid_cells[0]) +# self.n_rows = len(self.grid_cells) +# +# def check_reset_need_redraw(self, row_idx, col_idx): +# dirty = self.grid_dirty[row_idx][col_idx] +# if not dirty: +# return False +# self.grid_dirty[row_idx][col_idx] = False +# return True +# +# def get_value(self, row_idx, col_idx): +# return self.grid_cells[row_idx][col_idx] +# +# def set_value(self, row_idx, col_idx, value): +# if self.grid_cells[row_idx][col_idx] != value: +# self.grid_cells[row_idx][col_idx] = value +# self.grid_dirty[row_idx][col_idx] = True + + +# def _draw(x, y, color_number, pen: LayerTurtle): +# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels +# screen_y = _top - (y * _block_unit_width) +# # (screen_x, screen_y) = _calc_screen_position(x, y) +# color = _colors[color_number] +# pen.penColor(color) +# pen.goTo(screen_x, screen_y, with_pen=False) +# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) + + +def _randomize_block_grid() -> Grid: + color = random.randint(1, len(_colors) - 1) + if True: + n_rows = random.randint(1, 2) + n_cols = random.randint(1, 2) + block_grid = [] + for y in range(n_rows): + block_grid_row = [] + for x in range(n_cols): + block_grid_row.append(color) + block_grid.append(block_grid_row) + else: + block_grid = [[color, color]] + return Grid(grid_cells=block_grid) + +def _randomize_grid() -> Grid: + grid_cells = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid_cells.append(grid_row) + return Grid(grid_cells=grid_cells) + +def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + if block_grid.get_value(y, x) != 0: + row_idx = y + block_grid_y_offset + col_idx = x + block_grid_x_off + if row_idx < 0 or row_idx >= grid.n_rows: + return True + if col_idx < 0 or col_idx >= grid.n_cols: + return True + if grid.get_value(row_idx, col_idx) != 0: + return True + return False + + +def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + color = block_grid.get_value(y, x) + if color != 0: + grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) + + +# def _draw(x, y, color_number, pen: LayerTurtle): +# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels +# screen_y = _top - (y * _block_unit_width) +# # (screen_x, screen_y) = _calc_screen_position(x, y) +# color = _colors[color_number] +# pen.penColor(color) +# pen.goTo(screen_x, screen_y, with_pen=False) +# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) +# +# def _draw_grid(grid: Grid, pen: LayerTurtle): +# for y in range(grid.n_rows): +# for x in range(grid.n_cols): +# if not grid.check_reset_need_redraw(y, x): +# continue +# color_number = grid.get_value(y, x) +# _draw(x, y, color_number, pen) +# +# + +class Block: + def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): + self.x = x + self.y = y + self.block_grid = block_grid + self.block_pen = block_pen + block_pen.clear() + self.sync_image() + _draw_grid(block_grid, block_pen) + + def move_down(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): + return False + self.y += 1 + self.sync_image() + return True + + def move_right(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): + return False + self.x += 1 + self.sync_image() + return True + + def move_left(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): + return False + self.x -= 1 + self.sync_image() + return True + + def sync_image(self): + #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width + anchor_x = self.x * _block_unit_width + anchor_y = self.y * _block_unit_width + self.block_pen.setLevelAnchor(anchor_x, anchor_y) + + + +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): + grid = shape.grid + block = shape.block + + # Check if each row is full: + empty_count = 23 + deleted_count = 0 + for y in range(0,24): + is_full = True + is_empty = True + y_erase = y + for x in range(0,12): + if grid.get_value(y, x) == 0: + is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid.set_value(y + 1, x, grid.get_value(y, x)) + + deleted_count += 1 + + return (empty_count == 23, deleted_count) + + +class Shape: + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): + self.grid = _randomize_grid() + self.score_count = 0 + self.block: Block = None + self.pen = pen + self.block_pen = block_pen + if not self.reset_block(): + raise Exception("Failed to create initial block") + + def check_grid(self, score: LayerTurtle) -> bool: + (all_empty, delete_count) = _check_grid(self, score) + if delete_count > 0: + self.sync_image() + return all_empty and self.block is None + + def reset_block(self) -> bool: + x = 5 + y = 0 + block_grid = _randomize_block_grid() + x -= 1 - block_grid.n_cols + y -= 1 - block_grid.n_rows + if _check_can_place_block_grid(block_grid, x, y, grid=self.grid): + return False + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) + self.sync_image() + return True + + def commit_block(self): + _commit_block_grid(self.block.block_grid, self.block.x, self.block.y, self.grid) + #self.block.commit(self.grid) + self.sync_image() + self.block = None + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + def sync_image(self): + #self.block.sync_image() + _draw_grid(self.grid, self.pen) + + +class TetrisOneBlock2App(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.score: LayerTurtle = None + self.block_pen: LayerTurtle = None + self.pen: LayerTurtle = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + + root = DDRootLayer(self.dd, _width, _height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + block_pen = LayerTurtle(self.dd, _width, _height) + block_pen.penFilled() + #block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, _width, _height) + pen.penFilled() + pen.setTextSize(32) + + score = LayerTurtle(self.dd, _width, _height) + score.penColor('red') + score.penUp() + score.goTo(60, -300) + score.setTextFont("Courier", 24) + #score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, _width, _height) + if False: + border.rectangle(260, 490, centered=True) + border.penSize(10) + border.penUp() + border.goTo(-130, 240) + border.penDown() + border.penColor('linen') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + border.setTextFont("Courier", 36) + border.write("Two-Block TETRIS", "C") + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.score = score + self.block_pen = block_pen + self.pen = pen + + self.startGame() + #self.shape = Shape() + #self.resetBlock() + + self.last_update_time = time.time() + + def updateDD(self): + now = time.time() + need_update = (now - self.last_update_time) >= _delay + if need_update: + self.last_update_time = now + self.update() + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + moved_down = self.moveBlockDown() + if not moved_down: + self.shape.commit_block() + won = self.checkGrid() + if won: + self.endGame(won=True) + elif not moved_down: + if not self.resetBlock(): + self.endGame(won=False) + # if self.shape.block.y > 0: + # #self.shape.reset_block() + # self.resetBlock() + # else: + # self.endGame(won=False) + + def startGame(self): + self.score.clear() + self.score.write('Score: 0', 'C') + self.pen.clear() + self.block_pen.clear() + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) + #self.resetBlock() + + + def endGame(self, won: bool): + #self.block_pen.clear() + self.shape = None + if won: + msg = "🥳 YOU WON 🥳" + color = "purple" + else: + msg = "GAME OVER 😔" + color = "darkgray" + self.pen.home(with_pen=False) + self.pen.penColor("white") + self.pen.oval(300, 100, centered=True) + self.pen.penColor(color) + self.pen.write(msg, align='C') + + + def checkGrid(self) -> bool: + return self.shape.check_grid(score=self.score) + # check_result = check_grid(shape=self.shape, score=self.score) + # self.drawGrid() # should only redraw if any lines were cleared + # return check_result + + def resetBlock(self) -> bool: + return self.shape.reset_block() + #self.drawGrid() + #self.drawBlock() + + def moveBlockDown(self) -> bool: + return self.shape.move_block_down() + # if self.shape.move_block_down(): + # self.drawBlock() + # return True + # return False + + def moveBlockLeft(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_left() + # if self.shape.move_block_left(): + # self.drawBlock() + # return True + # return False + + def moveBlockRight(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_right() + # if self.shape.move_block_right(): + # self.drawBlock() + # return True + # return False + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisOneBlock2App(create_example_wifi_dd()) + app.run() From 903731f3259438708ab1c72b8fe18663a6447765 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 20:49:16 +0800 Subject: [PATCH 63/76] adding TetrisTwoBlockApp --- dumbdisplay_examples/tetris/tetris_common.py | 21 ++++++++ .../tetris/tetris_one_block.py | 6 +-- .../tetris/tetris_two_block.py | 48 +++++++++---------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py index 04e49e7..3782eac 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -63,4 +63,25 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): color_number = grid.get_value(y, x) _draw(x, y, color_number, pen) +def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + if block_grid.get_value(y, x) != 0: + row_idx = y + block_grid_y_offset + col_idx = x + block_grid_x_off + if row_idx < 0 or row_idx >= grid.n_rows: + return True + if col_idx < 0 or col_idx >= grid.n_cols: + return True + if grid.get_value(row_idx, col_idx) != 0: + return True + return False + + +def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + color = block_grid.get_value(y, x) + if color != 0: + grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) diff --git a/dumbdisplay_examples/tetris/tetris_one_block.py b/dumbdisplay_examples/tetris/tetris_one_block.py index aed6708..85d1ca5 100644 --- a/dumbdisplay_examples/tetris/tetris_one_block.py +++ b/dumbdisplay_examples/tetris/tetris_one_block.py @@ -157,7 +157,7 @@ def _create_grid() -> Grid: -class Block: +class OneBlock: def __init__(self, x: int, y: int, block_pen: LayerTurtle): # self.x = _INIT_BLOCK_X # self.y = 0 @@ -242,7 +242,7 @@ class Shape: def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): self.grid = _create_grid() self.score_count = 0 - self.block: Block = None + self.block: OneBlock = None self.pen = pen self.block_pen = block_pen if not self.reset_block(): @@ -260,7 +260,7 @@ def reset_block(self) -> bool: if self.grid.get_value(y, x) != 0: #self.sync_image() return False - self.block = Block(x, y, self.block_pen) + self.block = OneBlock(x, y, self.block_pen) self.sync_image() return True diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 5f9f5ed..3131090 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -10,7 +10,7 @@ from dumbdisplay.layer_turtle import LayerTurtle from dumbdisplay.layer_lcd import LayerLcd from dumbdisplay_examples.tetris.tetris_common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ - _width, _height, _left, _top, _draw_grid + _width, _height, _left, _top, _draw_grid, _check_can_place_block_grid, _commit_block_grid from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd @@ -97,27 +97,27 @@ def _randomize_grid() -> Grid: grid_cells.append(grid_row) return Grid(grid_cells=grid_cells) -def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: - for y in range(block_grid.n_rows): - for x in range(block_grid.n_cols): - if block_grid.get_value(y, x) != 0: - row_idx = y + block_grid_y_offset - col_idx = x + block_grid_x_off - if row_idx < 0 or row_idx >= grid.n_rows: - return True - if col_idx < 0 or col_idx >= grid.n_cols: - return True - if grid.get_value(row_idx, col_idx) != 0: - return True - return False - - -def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): - for y in range(block_grid.n_rows): - for x in range(block_grid.n_cols): - color = block_grid.get_value(y, x) - if color != 0: - grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) +# def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: +# for y in range(block_grid.n_rows): +# for x in range(block_grid.n_cols): +# if block_grid.get_value(y, x) != 0: +# row_idx = y + block_grid_y_offset +# col_idx = x + block_grid_x_off +# if row_idx < 0 or row_idx >= grid.n_rows: +# return True +# if col_idx < 0 or col_idx >= grid.n_cols: +# return True +# if grid.get_value(row_idx, col_idx) != 0: +# return True +# return False +# +# +# def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): +# for y in range(block_grid.n_rows): +# for x in range(block_grid.n_cols): +# color = block_grid.get_value(y, x) +# if color != 0: +# grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) # def _draw(x, y, color_number, pen: LayerTurtle): @@ -260,7 +260,7 @@ def sync_image(self): _draw_grid(self.grid, self.pen) -class TetrisOneBlock2App(DDAppBase): +class TetrisTwoBlockApp(DDAppBase): def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): super().__init__(dd) self.score: LayerTurtle = None @@ -426,5 +426,5 @@ def moveBlockRight(self) -> bool: if __name__ == "__main__": from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase - app = TetrisOneBlock2App(create_example_wifi_dd()) + app = TetrisTwoBlockApp(create_example_wifi_dd()) app.run() From b7d199a455bb2a386443a259400884db3a3547c3 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 20:58:35 +0800 Subject: [PATCH 64/76] adding TetrisTwoBlockApp --- dumbdisplay_examples/tetris/tetris_common.py | 38 ++++++++++ .../tetris/tetris_two_block.py | 74 +++++++++---------- 2 files changed, 75 insertions(+), 37 deletions(-) diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py index 3782eac..160e9bd 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -42,6 +42,44 @@ def set_value(self, row_idx, col_idx, value): self.grid_dirty[row_idx][col_idx] = True +class Block: + def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): + self.x = x + self.y = y + self.block_grid = block_grid + self.block_pen = block_pen + block_pen.clear() + self.sync_image() + _draw_grid(block_grid, block_pen) + + def move_down(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): + return False + self.y += 1 + self.sync_image() + return True + + def move_right(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): + return False + self.x += 1 + self.sync_image() + return True + + def move_left(self, grid: Grid) -> bool: + if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): + return False + self.x -= 1 + self.sync_image() + return True + + def sync_image(self): + #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width + anchor_x = self.x * _block_unit_width + anchor_y = self.y * _block_unit_width + self.block_pen.setLevelAnchor(anchor_x, anchor_y) + + def _draw(x, y, color_number, pen: LayerTurtle): screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels screen_y = _top - (y * _block_unit_width) diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 3131090..fa3cfb1 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -10,7 +10,7 @@ from dumbdisplay.layer_turtle import LayerTurtle from dumbdisplay.layer_lcd import LayerLcd from dumbdisplay_examples.tetris.tetris_common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ - _width, _height, _left, _top, _draw_grid, _check_can_place_block_grid, _commit_block_grid + _width, _height, _left, _top, _draw_grid, _check_can_place_block_grid, _commit_block_grid, Block from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd @@ -139,42 +139,42 @@ def _randomize_grid() -> Grid: # # -class Block: - def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): - self.x = x - self.y = y - self.block_grid = block_grid - self.block_pen = block_pen - block_pen.clear() - self.sync_image() - _draw_grid(block_grid, block_pen) - - def move_down(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): - return False - self.y += 1 - self.sync_image() - return True - - def move_right(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): - return False - self.x += 1 - self.sync_image() - return True - - def move_left(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): - return False - self.x -= 1 - self.sync_image() - return True - - def sync_image(self): - #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width - anchor_x = self.x * _block_unit_width - anchor_y = self.y * _block_unit_width - self.block_pen.setLevelAnchor(anchor_x, anchor_y) +# class Block: +# def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): +# self.x = x +# self.y = y +# self.block_grid = block_grid +# self.block_pen = block_pen +# block_pen.clear() +# self.sync_image() +# _draw_grid(block_grid, block_pen) +# +# def move_down(self, grid: Grid) -> bool: +# if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): +# return False +# self.y += 1 +# self.sync_image() +# return True +# +# def move_right(self, grid: Grid) -> bool: +# if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): +# return False +# self.x += 1 +# self.sync_image() +# return True +# +# def move_left(self, grid: Grid) -> bool: +# if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): +# return False +# self.x -= 1 +# self.sync_image() +# return True +# +# def sync_image(self): +# #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width +# anchor_x = self.x * _block_unit_width +# anchor_y = self.y * _block_unit_width +# self.block_pen.setLevelAnchor(anchor_x, anchor_y) From f64448ee7158fca6f37e2f82a16675f26eca17c4 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 29 Aug 2025 21:08:29 +0800 Subject: [PATCH 65/76] adding TetrisTwoBlockApp --- .../tetris/tetris_one_block.py | 124 +----------------- .../tetris/tetris_two_block.py | 3 +- 2 files changed, 5 insertions(+), 122 deletions(-) diff --git a/dumbdisplay_examples/tetris/tetris_one_block.py b/dumbdisplay_examples/tetris/tetris_one_block.py index 85d1ca5..b6882bb 100644 --- a/dumbdisplay_examples/tetris/tetris_one_block.py +++ b/dumbdisplay_examples/tetris/tetris_one_block.py @@ -14,21 +14,10 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd -#_USE_LEVEL_ANCHOR_FOR_BLOCK = True -#_INIT_BLOCK_X = 5 -_RANDOMIZE_ROW_COUNT = 1 +_RANDOMIZE_ROW_COUNT = 4 -# _width = 400 -# _height = 700 -# _top = 230 -# _left = -110 -# _block_unit_width = 20 -# #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -# _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] - - _delay = 0.3 # For time/sleep _grid = [ # 12x24 [0,0,0,0,0,0,0,0,0,0,0,0], @@ -56,63 +45,6 @@ [0,1,1,2,3,0,0,0,4,0,2,0], [2,0,1,2,3,0,6,5,5,5,0,2] ] -# _grid_n_cols = len(_grid[0]) # should be 12 -# _grid_n_rows = len(_grid) # should be 24 - - -# class Grid: -# def __init__(self): -# if _RANDOMIZE_ROW_COUNT >= 0: -# grid = [] -# for y in range(_grid_n_rows): -# grid_row = [] -# for x in range(_grid_n_cols): -# if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: -# color = random.randint(1, len(_colors) - 1) -# else: -# color = 0 -# grid_row.append(color) -# grid.append(grid_row) -# self.grid = grid -# else: -# self.grid = [] -# for grid_row in _grid: -# self.grid.append(grid_row.copy()) -# self.grid_dirty = [] -# for grid_row in self.grid: -# grid_dirty_row = [] -# for cell in grid_row: -# dirty = True if cell != 0 else False -# grid_dirty_row.append(dirty) -# self.grid_dirty.append(grid_dirty_row) -# self.n_cols = _grid_n_cols -# self.n_rows = _grid_n_rows -# -# def check_reset_need_redraw(self, row_idx, col_idx): -# dirty = self.grid_dirty[row_idx][col_idx] -# if not dirty: -# return False -# self.grid_dirty[row_idx][col_idx] = False -# return True -# -# def get_value(self, row_idx, col_idx): -# return self.grid[row_idx][col_idx] -# -# def set_value(self, row_idx, col_idx, value): -# if self.grid[row_idx][col_idx] != value: -# self.grid[row_idx][col_idx] = value -# self.grid_dirty[row_idx][col_idx] = True -# # old_value = self.grid[row_idx][col_idx] -# # self.grid[row_idx][col_idx] = value -# # self.grid_dirty[row_idx][col_idx] = old_value != value -# -# - - -# def _calc_screen_position(x: int, y : int) -> (int, int): -# screen_x = _left + (x * 20) # each turtle 20x20 pixels -# screen_y = _top - (y * 20) -# return (screen_x, screen_y) def _create_grid() -> Grid: @@ -134,40 +66,12 @@ def _create_grid() -> Grid: return Grid(grid) -# def _draw(x, y, color_number, pen: LayerTurtle): -# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels -# screen_y = _top - (y * _block_unit_width) -# # (screen_x, screen_y) = _calc_screen_position(x, y) -# color = _colors[color_number] -# pen.penColor(color) -# pen.goTo(screen_x, screen_y, with_pen=False) -# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) -# -# # def _draw_block(block: 'Block', block_pen: LayerTurtle): -# # block_pen.clear() -# # _draw(block.x, block.y, block.color, block_pen) -# -# def _draw_grid(grid: Grid, pen: LayerTurtle): -# for y in range(grid.n_rows): -# for x in range(grid.n_cols): -# if not grid.check_reset_need_redraw(y, x): -# continue -# color_number = grid.get_value(y, x) -# _draw(x, y, color_number, pen) - - - class OneBlock: def __init__(self, x: int, y: int, block_pen: LayerTurtle): - # self.x = _INIT_BLOCK_X - # self.y = 0 self.x = x self.y = y self.color = random.randint(1, len(_colors) - 1) self.block_pen = block_pen - # if _USE_LEVEL_ANCHOR_FOR_BLOCK: - # self.block_pen.clear() - # _draw(self.x, self.y, self.color, self.block_pen) self.sync_image() def commit(self, grid: Grid): @@ -377,11 +281,6 @@ def update(self): elif not moved_down: if not self.resetBlock(): self.endGame(won=False) - # if self.shape.block.y > 0: - # #self.shape.reset_block() - # self.resetBlock() - # else: - # self.endGame(won=False) def startGame(self): self.score.clear() @@ -389,11 +288,10 @@ def startGame(self): self.pen.clear() self.block_pen.clear() self.shape = Shape(pen=self.pen, block_pen=self.block_pen) - #self.resetBlock() + print("... started game") def endGame(self, won: bool): - #self.block_pen.clear() self.shape = None if won: msg = "🥳 YOU WON 🥳" @@ -406,45 +304,29 @@ def endGame(self, won: bool): self.pen.oval(300, 100, centered=True) self.pen.penColor(color) self.pen.write(msg, align='C') + print("... ended game") def checkGrid(self) -> bool: return self.shape.check_grid(score=self.score) - # check_result = check_grid(shape=self.shape, score=self.score) - # self.drawGrid() # should only redraw if any lines were cleared - # return check_result def resetBlock(self) -> bool: return self.shape.reset_block() - #self.drawGrid() - #self.drawBlock() def moveBlockDown(self) -> bool: return self.shape.move_block_down() - # if self.shape.move_block_down(): - # self.drawBlock() - # return True - # return False def moveBlockLeft(self) -> bool: if self.shape is None: self.startGame() return False return self.shape.move_block_left() - # if self.shape.move_block_left(): - # self.drawBlock() - # return True - # return False def moveBlockRight(self) -> bool: if self.shape is None: self.startGame() return False return self.shape.move_block_right() - # if self.shape.move_block_right(): - # self.drawBlock() - # return True - # return False if __name__ == "__main__": diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index fa3cfb1..8c0076a 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -366,7 +366,7 @@ def startGame(self): self.pen.clear() self.block_pen.clear() self.shape = Shape(pen=self.pen, block_pen=self.block_pen) - #self.resetBlock() + print("... started game") def endGame(self, won: bool): @@ -383,6 +383,7 @@ def endGame(self, won: bool): self.pen.oval(300, 100, centered=True) self.pen.penColor(color) self.pen.write(msg, align='C') + print("... ended game") def checkGrid(self) -> bool: From 6f436097a3b655a98c476f9708cbfbe1f669fb3d Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 30 Aug 2025 00:18:32 +0800 Subject: [PATCH 66/76] adding TetrisTwoBlockApp --- dumbdisplay_examples/tetris/tetris_common.py | 12 +- .../tetris/tetris_two_block.py | 138 +----------------- 2 files changed, 13 insertions(+), 137 deletions(-) diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py index 160e9bd..7b72415 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -53,21 +53,21 @@ def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): _draw_grid(block_grid, block_pen) def move_down(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): + if _check_block_grid_placement(self.block_grid, self.x, self.y + 1, grid=grid): return False self.y += 1 self.sync_image() return True def move_right(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): + if _check_block_grid_placement(self.block_grid, self.x + 1, self.y, grid=grid): return False self.x += 1 self.sync_image() return True def move_left(self, grid: Grid) -> bool: - if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): + if _check_block_grid_placement(self.block_grid, self.x - 1, self.y, grid=grid): return False self.x -= 1 self.sync_image() @@ -101,15 +101,19 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): color_number = grid.get_value(y, x) _draw(x, y, color_number, pen) -def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: +def _check_block_grid_placement(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid, check_boundary: bool = True) -> bool: for y in range(block_grid.n_rows): for x in range(block_grid.n_cols): if block_grid.get_value(y, x) != 0: row_idx = y + block_grid_y_offset col_idx = x + block_grid_x_off if row_idx < 0 or row_idx >= grid.n_rows: + if not check_boundary: + continue return True if col_idx < 0 or col_idx >= grid.n_cols: + if not check_boundary: + continue return True if grid.get_value(row_idx, col_idx) != 0: return True diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 8c0076a..4988a2b 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -10,64 +10,16 @@ from dumbdisplay.layer_turtle import LayerTurtle from dumbdisplay.layer_lcd import LayerLcd from dumbdisplay_examples.tetris.tetris_common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ - _width, _height, _left, _top, _draw_grid, _check_can_place_block_grid, _commit_block_grid, Block + _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ + _check_block_grid_placement from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd _RANDOMIZE_ROW_COUNT = 2 -# _width = 400 -# _height = 700 -# _top = 230 -# _left = -110 -# _block_unit_width = 20 -# #_colors = ['black', 'red', 'lightblue', 'blue', 'orange', 'yellow', 'green', 'purple'] -# _colors = ['black', 'crimson', 'cyan', 'ivory', 'coral', 'gold', 'lime', 'magenta'] -# _grid_n_cols = 12 -# _grid_n_rows = 24 - - _delay = 0.3 # For time/sleep -# class Grid: -# def __init__(self, grid_cells): -# self.grid_cells = grid_cells -# self.grid_dirty = [] -# for grid_row in self.grid_cells: -# grid_dirty_row = [] -# for cell in grid_row: -# dirty = True if cell != 0 else False -# grid_dirty_row.append(dirty) -# self.grid_dirty.append(grid_dirty_row) -# self.n_cols = len(self.grid_cells[0]) -# self.n_rows = len(self.grid_cells) -# -# def check_reset_need_redraw(self, row_idx, col_idx): -# dirty = self.grid_dirty[row_idx][col_idx] -# if not dirty: -# return False -# self.grid_dirty[row_idx][col_idx] = False -# return True -# -# def get_value(self, row_idx, col_idx): -# return self.grid_cells[row_idx][col_idx] -# -# def set_value(self, row_idx, col_idx, value): -# if self.grid_cells[row_idx][col_idx] != value: -# self.grid_cells[row_idx][col_idx] = value -# self.grid_dirty[row_idx][col_idx] = True - - -# def _draw(x, y, color_number, pen: LayerTurtle): -# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels -# screen_y = _top - (y * _block_unit_width) -# # (screen_x, screen_y) = _calc_screen_position(x, y) -# color = _colors[color_number] -# pen.penColor(color) -# pen.goTo(screen_x, screen_y, with_pen=False) -# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) - def _randomize_block_grid() -> Grid: color = random.randint(1, len(_colors) - 1) @@ -97,86 +49,6 @@ def _randomize_grid() -> Grid: grid_cells.append(grid_row) return Grid(grid_cells=grid_cells) -# def _check_can_place_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> bool: -# for y in range(block_grid.n_rows): -# for x in range(block_grid.n_cols): -# if block_grid.get_value(y, x) != 0: -# row_idx = y + block_grid_y_offset -# col_idx = x + block_grid_x_off -# if row_idx < 0 or row_idx >= grid.n_rows: -# return True -# if col_idx < 0 or col_idx >= grid.n_cols: -# return True -# if grid.get_value(row_idx, col_idx) != 0: -# return True -# return False -# -# -# def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): -# for y in range(block_grid.n_rows): -# for x in range(block_grid.n_cols): -# color = block_grid.get_value(y, x) -# if color != 0: -# grid.set_value(y + block_grid_y_offset, x + block_grid_x_off, color) - - -# def _draw(x, y, color_number, pen: LayerTurtle): -# screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels -# screen_y = _top - (y * _block_unit_width) -# # (screen_x, screen_y) = _calc_screen_position(x, y) -# color = _colors[color_number] -# pen.penColor(color) -# pen.goTo(screen_x, screen_y, with_pen=False) -# pen.rectangle(_block_unit_width - 2, _block_unit_width - 2, centered=True) -# -# def _draw_grid(grid: Grid, pen: LayerTurtle): -# for y in range(grid.n_rows): -# for x in range(grid.n_cols): -# if not grid.check_reset_need_redraw(y, x): -# continue -# color_number = grid.get_value(y, x) -# _draw(x, y, color_number, pen) -# -# - -# class Block: -# def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): -# self.x = x -# self.y = y -# self.block_grid = block_grid -# self.block_pen = block_pen -# block_pen.clear() -# self.sync_image() -# _draw_grid(block_grid, block_pen) -# -# def move_down(self, grid: Grid) -> bool: -# if _check_can_place_block_grid(self.block_grid, self.x, self.y + 1, grid=grid): -# return False -# self.y += 1 -# self.sync_image() -# return True -# -# def move_right(self, grid: Grid) -> bool: -# if _check_can_place_block_grid(self.block_grid, self.x + 1, self.y, grid=grid): -# return False -# self.x += 1 -# self.sync_image() -# return True -# -# def move_left(self, grid: Grid) -> bool: -# if _check_can_place_block_grid(self.block_grid, self.x - 1, self.y, grid=grid): -# return False -# self.x -= 1 -# self.sync_image() -# return True -# -# def sync_image(self): -# #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width -# anchor_x = self.x * _block_unit_width -# anchor_y = self.y * _block_unit_width -# self.block_pen.setLevelAnchor(anchor_x, anchor_y) - - def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): grid = shape.grid @@ -232,9 +104,9 @@ def reset_block(self) -> bool: x = 5 y = 0 block_grid = _randomize_block_grid() - x -= 1 - block_grid.n_cols - y -= 1 - block_grid.n_rows - if _check_can_place_block_grid(block_grid, x, y, grid=self.grid): + x -= int((block_grid.n_cols - 1) / 2) + y += 1 - block_grid.n_rows + if _check_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): return False self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) self.sync_image() From ac3213fcb80d8474154a81cc3c2b1211694e7a7b Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 30 Aug 2025 00:23:40 +0800 Subject: [PATCH 67/76] fixed parse FB bug --- dumbdisplay/ddimpl.py | 18 +++++++++++------- dumbdisplay/ddlayer_multilevel.py | 10 ++++++---- dumbdisplay_examples/tetris/tetris_common.py | 1 + .../tetris/tetris_two_block.py | 1 + 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py index 3bdfa8a..d48a8a3 100644 --- a/dumbdisplay/ddimpl.py +++ b/dumbdisplay/ddimpl.py @@ -83,7 +83,9 @@ def available(self): done = '\n' in s return done def read(self) -> str: - #print(self.data) + # if True: # TODO: disable printing of received data + # data = self.data[:-1] + # print(data) idx = self.data.index('\n') s = self.data[0:idx] self.data = self.data[idx + 1:] @@ -461,7 +463,7 @@ def _checkForFeedback(self): pass #self._onFeedbackKeepAlive() else: - #print(feedback)#### + #print(feedback) # TODO: disable debug if feedback.startswith(' str: validate_res = self._validateConnection() if validate_res is None: diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 4ee1176..7e99cc1 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -71,18 +71,20 @@ def setLevelAnchor(self, x: float, y: float, reach_in_millis: int = 0): """ set the anchor of the level; note that level anchor is the top-left corner of the level "opening" """ + command = "setlevelanchor" if self.dd._compatibility < 15 else "SLA" if reach_in_millis > 0: - self.dd._sendCommand(self.layer_id, "setlevelanchor", _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y), _DD_INT_ARG(reach_in_millis)) + self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y), _DD_INT_ARG(reach_in_millis)) else: - self.dd._sendCommand(self.layer_id, "setlevelanchor", _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y)) + self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y)) def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): """ move the level anchor """ + command = "movelevelanchorby" if self.dd._compatibility < 15 else "MLAB" if reach_in_millis > 0: - self.dd._sendCommand(self.layer_id, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); + self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); else: - self.dd._sendCommand(self.layer_id, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); + self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); def registerLevelBackground(self, background_id: str, background_image_name: str, draw_background_options: str = ""): """ register an image for setting as level's background diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py index 7b72415..49c73d9 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -70,6 +70,7 @@ def move_left(self, grid: Grid) -> bool: if _check_block_grid_placement(self.block_grid, self.x - 1, self.y, grid=grid): return False self.x -= 1 + #print(f"* left ==> x={self.x}") self.sync_image() return True diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 4988a2b..0563f48 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -277,6 +277,7 @@ def moveBlockDown(self) -> bool: # return False def moveBlockLeft(self) -> bool: + #print("$ move left") if self.shape is None: self.startGame() return False From 5182045a2a3ceb1b7b57b48e26e40342b67c696a Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 30 Aug 2025 08:53:30 +0800 Subject: [PATCH 68/76] updated --- dd_demo.py | 4 +++- dumbdisplay/ddcmds.py | 2 ++ dumbdisplay/ddlayer_multilevel.py | 13 +++++++------ .../{ => OLD}/tetris_classic/tetris_classic.py | 0 .../tetris_classic/tetris_classic_OLD_0.py | 0 .../{ => OLD}/tetris_one_block/main.py | 0 .../{ => OLD}/tetris_one_block/tetris_one_block.py | 0 .../tetris_one_block/tetris_one_block_2.py | 0 8 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 dumbdisplay/ddcmds.py rename dumbdisplay_examples/{ => OLD}/tetris_classic/tetris_classic.py (100%) rename dumbdisplay_examples/{ => OLD}/tetris_classic/tetris_classic_OLD_0.py (100%) rename dumbdisplay_examples/{ => OLD}/tetris_one_block/main.py (100%) rename dumbdisplay_examples/{ => OLD}/tetris_one_block/tetris_one_block.py (100%) rename dumbdisplay_examples/{ => OLD}/tetris_one_block/tetris_one_block_2.py (100%) diff --git a/dd_demo.py b/dd_demo.py index 9115e97..8fad7e0 100644 --- a/dd_demo.py +++ b/dd_demo.py @@ -315,7 +315,9 @@ def run_mnist_app(): if __name__ == "__main__": - demo_AutoPin() + #run_sliding_puzzle_app() + #run_mnist_app() + #demo_AutoPin() demo_LayerTurtle() if True: diff --git a/dumbdisplay/ddcmds.py b/dumbdisplay/ddcmds.py new file mode 100644 index 0000000..c6aa960 --- /dev/null +++ b/dumbdisplay/ddcmds.py @@ -0,0 +1,2 @@ +DDC_setlevelanchor = "#3a" +DDC_movelevelanchorby = "#3b" diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 7e99cc1..4cdf2f4 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -1,3 +1,4 @@ +from dumbdisplay.ddcmds import DDC_setlevelanchor, DDC_movelevelanchorby from dumbdisplay.ddimpl import DumbDisplayImpl from dumbdisplay.ddlayer import DDLayer, _DD_BOOL_ARG, _DD_FLOAT_ARG, _DD_INT_ARG, _DD_FLOAT_IS_ZERO @@ -71,20 +72,20 @@ def setLevelAnchor(self, x: float, y: float, reach_in_millis: int = 0): """ set the anchor of the level; note that level anchor is the top-left corner of the level "opening" """ - command = "setlevelanchor" if self.dd._compatibility < 15 else "SLA" + #command = "setlevelanchor" if self.dd._compatibility < 15 else "SLA" if reach_in_millis > 0: - self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y), _DD_INT_ARG(reach_in_millis)) + self.dd._sendCommand(self.layer_id, DDC_setlevelanchor, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y), _DD_INT_ARG(reach_in_millis)) else: - self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y)) + self.dd._sendCommand(self.layer_id, DDC_setlevelanchor, _DD_FLOAT_ARG(x), _DD_FLOAT_ARG(y)) def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): """ move the level anchor """ - command = "movelevelanchorby" if self.dd._compatibility < 15 else "MLAB" + #command = "movelevelanchorby" if self.dd._compatibility < 15 else "MLAB" if reach_in_millis > 0: - self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); + self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); else: - self.dd._sendCommand(self.layer_id, command, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); + self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); def registerLevelBackground(self, background_id: str, background_image_name: str, draw_background_options: str = ""): """ register an image for setting as level's background diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic.py b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic.py similarity index 100% rename from dumbdisplay_examples/tetris_classic/tetris_classic.py rename to dumbdisplay_examples/OLD/tetris_classic/tetris_classic.py diff --git a/dumbdisplay_examples/tetris_classic/tetris_classic_OLD_0.py b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic_OLD_0.py similarity index 100% rename from dumbdisplay_examples/tetris_classic/tetris_classic_OLD_0.py rename to dumbdisplay_examples/OLD/tetris_classic/tetris_classic_OLD_0.py diff --git a/dumbdisplay_examples/tetris_one_block/main.py b/dumbdisplay_examples/OLD/tetris_one_block/main.py similarity index 100% rename from dumbdisplay_examples/tetris_one_block/main.py rename to dumbdisplay_examples/OLD/tetris_one_block/main.py diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block.py similarity index 100% rename from dumbdisplay_examples/tetris_one_block/tetris_one_block.py rename to dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block.py diff --git a/dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block_2.py similarity index 100% rename from dumbdisplay_examples/tetris_one_block/tetris_one_block_2.py rename to dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block_2.py From af232445c737d9952f156566e218e95b5e196ad4 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 30 Aug 2025 16:01:35 +0800 Subject: [PATCH 69/76] added setLevelRotation --- dumbdisplay/ddcmds.py | 1 + dumbdisplay/ddlayer_multilevel.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dumbdisplay/ddcmds.py b/dumbdisplay/ddcmds.py index c6aa960..34f489f 100644 --- a/dumbdisplay/ddcmds.py +++ b/dumbdisplay/ddcmds.py @@ -1,2 +1,3 @@ DDC_setlevelanchor = "#3a" DDC_movelevelanchorby = "#3b" +DDC_setlevelrotate = "#4e" diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 4cdf2f4..65a98b6 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -1,4 +1,4 @@ -from dumbdisplay.ddcmds import DDC_setlevelanchor, DDC_movelevelanchorby +from dumbdisplay.ddcmds import DDC_setlevelanchor, DDC_movelevelanchorby, DDC_setlevelrotate from dumbdisplay.ddimpl import DumbDisplayImpl from dumbdisplay.ddlayer import DDLayer, _DD_BOOL_ARG, _DD_FLOAT_ARG, _DD_INT_ARG, _DD_FLOAT_IS_ZERO @@ -85,7 +85,14 @@ def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): if reach_in_millis > 0: self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); else: - self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)); + self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)) + def setLevelRotation(self, angle: float, pivotX: float = 0, pivotY: float = 0): + if pivotX == 0 and pivotY == 0: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle)) + elif pivotY == 0: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivotX)) + else: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivotX), _DD_FLOAT_ARG(pivotY)) def registerLevelBackground(self, background_id: str, background_image_name: str, draw_background_options: str = ""): """ register an image for setting as level's background From 62d6a634f0171fa57d93dd2db0d947a78832b919 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sun, 31 Aug 2025 14:26:33 +0800 Subject: [PATCH 70/76] updated --- dumbdisplay/ddlayer_multilevel.py | 4 +++- dumbdisplay_examples/tetris/tetris_common.py | 4 +++- dumbdisplay_examples/tetris/tetris_two_block.py | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index 65a98b6..ee2795c 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -87,7 +87,9 @@ def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): else: self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)) def setLevelRotation(self, angle: float, pivotX: float = 0, pivotY: float = 0): - if pivotX == 0 and pivotY == 0: + if angle == 0 and pivotX == 0 and pivotY == 0: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate) + elif pivotX == 0 and pivotY == 0: self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle)) elif pivotY == 0: self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivotX)) diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/tetris_common.py index 49c73d9..ebec955 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/tetris_common.py @@ -49,6 +49,9 @@ def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): self.block_grid = block_grid self.block_pen = block_pen block_pen.clear() + if True: + # make the block tiled a bit + block_pen.setLevelRotation(2, 90, 120) # calculated from _left and _top self.sync_image() _draw_grid(block_grid, block_pen) @@ -75,7 +78,6 @@ def move_left(self, grid: Grid) -> bool: return True def sync_image(self): - #anchor_x = (self.x - _INIT_BLOCK_X) * _block_unit_width anchor_x = self.x * _block_unit_width anchor_y = self.y * _block_unit_width self.block_pen.setLevelAnchor(anchor_x, anchor_y) diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 0563f48..8e5e121 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -26,6 +26,9 @@ def _randomize_block_grid() -> Grid: if True: n_rows = random.randint(1, 2) n_cols = random.randint(1, 2) + if False: # TODO: disable after debug + n_rows = 1 + n_cols = 2 block_grid = [] for y in range(n_rows): block_grid_row = [] From 4cdcf64d2eda78be25f3778e8bfd9e23f8bcafe1 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Mon, 1 Sep 2025 20:17:12 +0800 Subject: [PATCH 71/76] updated --- dumbdisplay/ddlayer_multilevel.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/dumbdisplay/ddlayer_multilevel.py b/dumbdisplay/ddlayer_multilevel.py index ee2795c..064c929 100644 --- a/dumbdisplay/ddlayer_multilevel.py +++ b/dumbdisplay/ddlayer_multilevel.py @@ -86,15 +86,26 @@ def moveLevelAnchorBy(self, by_x: float, by_y: float, reach_in_millis: int = 0): self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis)); else: self.dd._sendCommand(self.layer_id, DDC_movelevelanchorby, _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y)) - def setLevelRotation(self, angle: float, pivotX: float = 0, pivotY: float = 0): - if angle == 0 and pivotX == 0 and pivotY == 0: - self.dd._sendCommand(self.layer_id, DDC_setlevelrotate) - elif pivotX == 0 and pivotY == 0: - self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle)) - elif pivotY == 0: - self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivotX)) + def setLevelRotation(self, angle: float, pivot_x: float = 0, pivot_y: float = 0, reach_in_millis: int = 0): + """ + :param angle: rotation angle in degree; positive is clockwise + :param pivot_x: x coordinate of the pivot point (relative to the level anchor) + :param pivot_y: y coordinate of the pivot point (relative to the level anchor) + """ + if reach_in_millis > 0: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivot_x), _DD_FLOAT_ARG(pivot_y), _DD_INT_ARG(reach_in_millis)) else: - self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivotX), _DD_FLOAT_ARG(pivotY)) + angle_is_zero = _DD_FLOAT_IS_ZERO(angle) + pivot_x_is_zero = _DD_FLOAT_IS_ZERO(pivot_x) + pivot_y_is_zero = _DD_FLOAT_IS_ZERO(pivot_y) + if angle_is_zero: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate) + elif pivot_x_is_zero and pivot_y_is_zero: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle)) + elif pivot_y_is_zero: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivot_x)) + else: + self.dd._sendCommand(self.layer_id, DDC_setlevelrotate, _DD_FLOAT_ARG(angle), _DD_FLOAT_ARG(pivot_x), _DD_FLOAT_ARG(pivot_y)) def registerLevelBackground(self, background_id: str, background_image_name: str, draw_background_options: str = ""): """ register an image for setting as level's background From 84c9f8e059b403154b1d9c1527c0f262b5f680fc Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 5 Sep 2025 10:21:15 +0800 Subject: [PATCH 72/76] working on TetrisTwoBlockApp --- .../tetris/{tetris_common.py => _common.py} | 18 ++ dumbdisplay_examples/tetris/_shapes.py | 40 +++ dumbdisplay_examples/tetris/tetris_classic.py | 276 ++++++++++++++++++ .../tetris/tetris_one_block.py | 2 +- .../tetris/tetris_two_block.py | 30 +- 5 files changed, 350 insertions(+), 16 deletions(-) rename dumbdisplay_examples/tetris/{tetris_common.py => _common.py} (89%) create mode 100644 dumbdisplay_examples/tetris/_shapes.py create mode 100644 dumbdisplay_examples/tetris/tetris_classic.py diff --git a/dumbdisplay_examples/tetris/tetris_common.py b/dumbdisplay_examples/tetris/_common.py similarity index 89% rename from dumbdisplay_examples/tetris/tetris_common.py rename to dumbdisplay_examples/tetris/_common.py index ebec955..49d669e 100644 --- a/dumbdisplay_examples/tetris/tetris_common.py +++ b/dumbdisplay_examples/tetris/_common.py @@ -1,3 +1,5 @@ +import random + from dumbdisplay.layer_turtle import LayerTurtle @@ -83,6 +85,20 @@ def sync_image(self): self.block_pen.setLevelAnchor(anchor_x, anchor_y) +def _randomize_grid(randomize_row_count: int) -> Grid: + grid_cells = [] + for y in range(_grid_n_rows): + grid_row = [] + for x in range(_grid_n_cols): + if y >= (_grid_n_rows - randomize_row_count) and random.random() < 0.7: + color = random.randint(1, len(_colors) - 1) + else: + color = 0 + grid_row.append(color) + grid_cells.append(grid_row) + return Grid(grid_cells=grid_cells) + + def _draw(x, y, color_number, pen: LayerTurtle): screen_x = _left + (x * _block_unit_width) # each turtle 20x20 pixels screen_y = _top - (y * _block_unit_width) @@ -110,6 +126,8 @@ def _check_block_grid_placement(block_grid: Grid, block_grid_x_off: int, block_g if block_grid.get_value(y, x) != 0: row_idx = y + block_grid_y_offset col_idx = x + block_grid_x_off + if row_idx < 0: + continue # never mind above the grid if row_idx < 0 or row_idx >= grid.n_rows: if not check_boundary: continue diff --git a/dumbdisplay_examples/tetris/_shapes.py b/dumbdisplay_examples/tetris/_shapes.py new file mode 100644 index 0000000..5c59cf5 --- /dev/null +++ b/dumbdisplay_examples/tetris/_shapes.py @@ -0,0 +1,40 @@ +import random + +from dumbdisplay_examples.tetris._common import Grid, _colors + +_square = [[1,1], + [1,1]] + +_horizontal_line = [[1,1,1,1]] + +_vertical_line = [[1], + [1], + [1], + [1]] + +_left_l = [[1,0,0,0], + [1,1,1,1]] + +_right_l = [[0,0,0,1], + [1,1,1,1]] + +_left_s = [[1,1,0], + [0,1,1]] + +_right_s = [[0,1,1], + [1,1,0]] + +_t = [[0,1,0], + [1,1,1]] + +_shapes = [_square, _horizontal_line, _vertical_line, _left_l, _right_l, _left_s, _right_s, _t] + + +def _randomize_block_grid() -> Grid: + block_grid = random.choice(_shapes) + if False: + block_grid = _vertical_line + color = random.randint(1, len(_colors) - 1) + block_grid = [[color if cell != 0 else 0 for cell in row] for row in block_grid] + return Grid(grid_cells=block_grid) + diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py new file mode 100644 index 0000000..a525c47 --- /dev/null +++ b/dumbdisplay_examples/tetris/tetris_classic.py @@ -0,0 +1,276 @@ +# *** +# *** Adapted from TETRIS CLASSIC\tetris_turtle.py of https://github.com/DimaGutierrez/Python-Games +# *** - modified from dumbdisplay_examples/tetris/tetris_two_block.py +# *** + +import random +import time + +from dumbdisplay.core import * +from dumbdisplay.layer_graphical import DDRootLayer +from dumbdisplay.layer_turtle import LayerTurtle +from dumbdisplay.layer_lcd import LayerLcd +from dumbdisplay_examples.tetris._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ + _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ + _check_block_grid_placement, _randomize_grid +from dumbdisplay_examples.tetris._shapes import _randomize_block_grid + +from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd + +_RANDOMIZE_ROW_COUNT = 2 + + +_delay = 0.3 # For time/sleep + +def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): + grid = shape.grid + block = shape.block + + # Check if each row is full: + empty_count = 23 + deleted_count = 0 + for y in range(0,24): + is_full = True + is_empty = True + y_erase = y + for x in range(0,12): + if grid.get_value(y, x) == 0: + is_full = False + else: + is_empty = False + if not is_empty and not is_full: + empty_count -= 1 + break + # Remove row and shift down + if is_full: + shape.score_count += 1 + score.clear() + score.write(f'Score: {shape.score_count}', align='C') + + for y in range(y_erase-1, -1, -1): + for x in range(0,12): + grid.set_value(y + 1, x, grid.get_value(y, x)) + + deleted_count += 1 + + return (empty_count == 23, deleted_count) + + +class Shape: + def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): + self.grid = _randomize_grid(_RANDOMIZE_ROW_COUNT) + self.score_count = 0 + self.block: Block = None + self.pen = pen + self.block_pen = block_pen + if not self.reset_block(): + raise Exception("Failed to create initial block") + + def check_grid(self, score: LayerTurtle) -> bool: + (all_empty, delete_count) = _check_grid(self, score) + if delete_count > 0: + self.sync_image() + return all_empty and self.block is None + + def reset_block(self) -> bool: + x = 5 + y = 0 + block_grid = _randomize_block_grid() + x -= int((block_grid.n_cols - 1) / 2) + y += 1 - block_grid.n_rows + if _check_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): + return False + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) + self.sync_image() + return True + + def commit_block(self): + _commit_block_grid(self.block.block_grid, self.block.x, self.block.y, self.grid) + #self.block.commit(self.grid) + self.sync_image() + self.block = None + + def move_block_down(self) -> bool: + return self.block.move_down(self.grid) + + def move_block_right(self) -> bool: + return self.block.move_right(self.grid) + + def move_block_left(self) -> bool: + return self.block.move_left(self.grid) + + def sync_image(self): + #self.block.sync_image() + _draw_grid(self.grid, self.pen) + + +class TetrisTwoBlockApp(DDAppBase): + def __init__(self, dd: DumbDisplay = create_example_wifi_dd()): + super().__init__(dd) + self.score: LayerTurtle = None + self.block_pen: LayerTurtle = None + self.pen: LayerTurtle = None + self.shape: Shape = None + self.last_update_time = None + + def initializeDD(self): + + root = DDRootLayer(self.dd, _width, _height) + root.border(5, "darkred", "round", 1) + root.backgroundColor("black") + + block_pen = LayerTurtle(self.dd, _width, _height) + block_pen.penFilled() + #block_pen.setTextSize(32) + + pen = LayerTurtle(self.dd, _width, _height) + pen.penFilled() + pen.setTextSize(32) + + score = LayerTurtle(self.dd, _width, _height) + score.penColor('red') + score.penUp() + score.goTo(60, -300) + score.setTextFont("Courier", 24) + #score.write('Score: 0', 'C') + + border = LayerTurtle(self.dd, _width, _height) + if False: + border.rectangle(260, 490, centered=True) + border.penSize(10) + border.penUp() + border.goTo(-130, 240) + border.penDown() + border.penColor('linen') + border.rightTurn(90) + border.forward(490) # Down + border.leftTurn(90) + border.forward(260) # Right + border.leftTurn(90) + border.forward(490) # Up + border.penUp() + border.goTo(0,260) + border.setTextFont("Courier", 36) + border.write("TETRIS", "C") + + left_button = LayerLcd(self.dd, 2, 1, char_height=28) + left_button.noBackgroundColor() + left_button.writeLine("⬅️") + left_button.enableFeedback("f", lambda *args: self.moveBlockLeft()) + + right_button = LayerLcd(self.dd, 2, 1, char_height=28) + right_button.noBackgroundColor() + right_button.writeLine("➡️") + right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + + AutoPin('V', + AutoPin('S'), + AutoPin('H', left_button, right_button)).pin(self.dd) + + self.score = score + self.block_pen = block_pen + self.pen = pen + + self.startGame() + #self.shape = Shape() + #self.resetBlock() + + self.last_update_time = time.time() + + def updateDD(self): + now = time.time() + need_update = (now - self.last_update_time) >= _delay + if need_update: + self.last_update_time = now + self.update() + + def update(self): + if self.shape is None: + print("... waiting to restart ...") + return + + moved_down = self.moveBlockDown() + if not moved_down: + self.shape.commit_block() + won = self.checkGrid() + if won: + self.endGame(won=True) + elif not moved_down: + if not self.resetBlock(): + self.endGame(won=False) + # if self.shape.block.y > 0: + # #self.shape.reset_block() + # self.resetBlock() + # else: + # self.endGame(won=False) + + def startGame(self): + self.score.clear() + self.score.write('Score: 0', 'C') + self.pen.clear() + self.block_pen.clear() + self.shape = Shape(pen=self.pen, block_pen=self.block_pen) + print("... started game") + + + def endGame(self, won: bool): + #self.block_pen.clear() + self.shape = None + if won: + msg = "🥳 YOU WON 🥳" + color = "purple" + else: + msg = "GAME OVER 😔" + color = "darkgray" + self.pen.home(with_pen=False) + self.pen.penColor("white") + self.pen.oval(300, 100, centered=True) + self.pen.penColor(color) + self.pen.write(msg, align='C') + print("... ended game") + + + def checkGrid(self) -> bool: + return self.shape.check_grid(score=self.score) + # check_result = check_grid(shape=self.shape, score=self.score) + # self.drawGrid() # should only redraw if any lines were cleared + # return check_result + + def resetBlock(self) -> bool: + return self.shape.reset_block() + #self.drawGrid() + #self.drawBlock() + + def moveBlockDown(self) -> bool: + return self.shape.move_block_down() + # if self.shape.move_block_down(): + # self.drawBlock() + # return True + # return False + + def moveBlockLeft(self) -> bool: + #print("$ move left") + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_left() + # if self.shape.move_block_left(): + # self.drawBlock() + # return True + # return False + + def moveBlockRight(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.move_block_right() + # if self.shape.move_block_right(): + # self.drawBlock() + # return True + # return False + + +if __name__ == "__main__": + from dumbdisplay_examples.utils import create_example_wifi_dd, DDAppBase + app = TetrisTwoBlockApp(create_example_wifi_dd()) + app.run() diff --git a/dumbdisplay_examples/tetris/tetris_one_block.py b/dumbdisplay_examples/tetris/tetris_one_block.py index b6882bb..caa866b 100644 --- a/dumbdisplay_examples/tetris/tetris_one_block.py +++ b/dumbdisplay_examples/tetris/tetris_one_block.py @@ -9,7 +9,7 @@ from dumbdisplay.layer_graphical import DDRootLayer from dumbdisplay.layer_turtle import LayerTurtle from dumbdisplay.layer_lcd import LayerLcd -from dumbdisplay_examples.tetris.tetris_common import Grid, _draw, _draw_grid, _width, _height, _colors, \ +from dumbdisplay_examples.tetris._common import Grid, _draw, _draw_grid, _width, _height, _colors, \ _block_unit_width, _grid_n_rows, _grid_n_cols from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 8e5e121..795fec0 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -9,9 +9,9 @@ from dumbdisplay.layer_graphical import DDRootLayer from dumbdisplay.layer_turtle import LayerTurtle from dumbdisplay.layer_lcd import LayerLcd -from dumbdisplay_examples.tetris.tetris_common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ +from dumbdisplay_examples.tetris._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ - _check_block_grid_placement + _check_block_grid_placement, _randomize_grid from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd @@ -39,18 +39,18 @@ def _randomize_block_grid() -> Grid: block_grid = [[color, color]] return Grid(grid_cells=block_grid) -def _randomize_grid() -> Grid: - grid_cells = [] - for y in range(_grid_n_rows): - grid_row = [] - for x in range(_grid_n_cols): - if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: - color = random.randint(1, len(_colors) - 1) - else: - color = 0 - grid_row.append(color) - grid_cells.append(grid_row) - return Grid(grid_cells=grid_cells) +# def _randomize_grid() -> Grid: +# grid_cells = [] +# for y in range(_grid_n_rows): +# grid_row = [] +# for x in range(_grid_n_cols): +# if y >= (_grid_n_rows - _RANDOMIZE_ROW_COUNT) and random.random() < 0.7: +# color = random.randint(1, len(_colors) - 1) +# else: +# color = 0 +# grid_row.append(color) +# grid_cells.append(grid_row) +# return Grid(grid_cells=grid_cells) def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): @@ -89,7 +89,7 @@ def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): class Shape: def __init__(self, pen: LayerTurtle, block_pen: LayerTurtle): - self.grid = _randomize_grid() + self.grid = _randomize_grid(_RANDOMIZE_ROW_COUNT) self.score_count = 0 self.block: Block = None self.pen = pen From 464a2a87bd54b6928cd528b993587080e6f5f151 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 5 Sep 2025 13:38:47 +0800 Subject: [PATCH 73/76] working on TetrisTwoBlockApp --- dumbdisplay_examples/tetris/_common.py | 102 ++++++++++++++---- dumbdisplay_examples/tetris/tetris_classic.py | 28 +++-- .../tetris/tetris_two_block.py | 4 +- 3 files changed, 99 insertions(+), 35 deletions(-) diff --git a/dumbdisplay_examples/tetris/_common.py b/dumbdisplay_examples/tetris/_common.py index 49d669e..dcc57ea 100644 --- a/dumbdisplay_examples/tetris/_common.py +++ b/dumbdisplay_examples/tetris/_common.py @@ -58,33 +58,78 @@ def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): _draw_grid(block_grid, block_pen) def move_down(self, grid: Grid) -> bool: - if _check_block_grid_placement(self.block_grid, self.x, self.y + 1, grid=grid): + if _check_bad_block_grid_placement(self.block_grid, self.x, self.y + 1, grid=grid): return False self.y += 1 self.sync_image() return True def move_right(self, grid: Grid) -> bool: - if _check_block_grid_placement(self.block_grid, self.x + 1, self.y, grid=grid): + if _check_bad_block_grid_placement(self.block_grid, self.x + 1, self.y, grid=grid): return False self.x += 1 self.sync_image() return True def move_left(self, grid: Grid) -> bool: - if _check_block_grid_placement(self.block_grid, self.x - 1, self.y, grid=grid): + if _check_bad_block_grid_placement(self.block_grid, self.x - 1, self.y, grid=grid): return False self.x -= 1 #print(f"* left ==> x={self.x}") self.sync_image() return True + def rotate(self, grid: Grid) -> bool: + (rotated_block_grid, y_offset) = _rotate_block_grid_if_possible(self.block_grid, self.x, self.y, grid=grid) + if rotated_block_grid is None: + return False + self.block_grid = rotated_block_grid + self.y += y_offset + self.block_pen.clear() + _draw_grid(self.block_grid, self.block_pen) + return True + + def sync_image(self): anchor_x = self.x * _block_unit_width anchor_y = self.y * _block_unit_width self.block_pen.setLevelAnchor(anchor_x, anchor_y) + +def _check_bad_block_grid_cells_placement(grid_cells: list[list[int]], block_grid_x_off: int, block_grid_y_offset: int, grid: Grid, check_boundary: bool = True) -> bool: + n_rows = len(grid_cells) + n_cols = len(grid_cells[0]) if n_rows > 0 else 0 + for y in range(n_rows): + for x in range(n_cols): + if grid_cells[y][x] != 0: + row_idx = y + block_grid_y_offset + col_idx = x + block_grid_x_off + if row_idx < 0: + continue # never mind above the grid + if row_idx < 0 or row_idx >= grid.n_rows: + if not check_boundary: + continue + return True + if col_idx < 0 or col_idx >= grid.n_cols: + if not check_boundary: + continue + return True + if grid.get_value(row_idx, col_idx) != 0: + return True + return False + + +def _rotate_block_grid_cells(grid_cells: list[list[int]]) -> list[list[int]]: + rotated = [] + for x in range(len(grid_cells[0])): + new_row = [] + for y in range(len(grid_cells) -1, -1, -1): + new_row.append(grid_cells[y][x]) + rotated.append(new_row) + return rotated + + def _randomize_grid(randomize_row_count: int) -> Grid: grid_cells = [] for y in range(_grid_n_rows): @@ -120,25 +165,38 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): color_number = grid.get_value(y, x) _draw(x, y, color_number, pen) -def _check_block_grid_placement(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid, check_boundary: bool = True) -> bool: - for y in range(block_grid.n_rows): - for x in range(block_grid.n_cols): - if block_grid.get_value(y, x) != 0: - row_idx = y + block_grid_y_offset - col_idx = x + block_grid_x_off - if row_idx < 0: - continue # never mind above the grid - if row_idx < 0 or row_idx >= grid.n_rows: - if not check_boundary: - continue - return True - if col_idx < 0 or col_idx >= grid.n_cols: - if not check_boundary: - continue - return True - if grid.get_value(row_idx, col_idx) != 0: - return True - return False +def _check_bad_block_grid_placement(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid, check_boundary: bool = True) -> bool: + if True: + return _check_bad_block_grid_cells_placement(block_grid.grid_cells, block_grid_x_off, block_grid_y_offset, grid, check_boundary) + else: + for y in range(block_grid.n_rows): + for x in range(block_grid.n_cols): + if block_grid.get_value(y, x) != 0: + row_idx = y + block_grid_y_offset + col_idx = x + block_grid_x_off + if row_idx < 0: + continue # never mind above the grid + if row_idx < 0 or row_idx >= grid.n_rows: + if not check_boundary: + continue + return True + if col_idx < 0 or col_idx >= grid.n_cols: + if not check_boundary: + continue + return True + if grid.get_value(row_idx, col_idx) != 0: + return True + return False + + +def _rotate_block_grid_if_possible(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> (Grid, int): + block_grid_cells = block_grid.grid_cells + rotated_cells = _rotate_block_grid_cells(block_grid_cells) + y_offset = len(rotated_cells) - len(block_grid_cells) + if _check_bad_block_grid_cells_placement(rotated_cells, block_grid_x_off, block_grid_y_offset + y_offset, grid, check_boundary=True): + return (None, None) + rotated_block_grid = Grid(grid_cells=rotated_cells) + return (rotated_block_grid, y_offset) def _commit_block_grid(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid): diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py index a525c47..d65f8ad 100644 --- a/dumbdisplay_examples/tetris/tetris_classic.py +++ b/dumbdisplay_examples/tetris/tetris_classic.py @@ -12,7 +12,7 @@ from dumbdisplay.layer_lcd import LayerLcd from dumbdisplay_examples.tetris._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ - _check_block_grid_placement, _randomize_grid + _check_bad_block_grid_placement, _randomize_grid, _rotate_block_grid_if_possible from dumbdisplay_examples.tetris._shapes import _randomize_block_grid from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd @@ -78,7 +78,7 @@ def reset_block(self) -> bool: block_grid = _randomize_block_grid() x -= int((block_grid.n_cols - 1) / 2) y += 1 - block_grid.n_rows - if _check_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): + if _check_bad_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): return False self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) self.sync_image() @@ -96,6 +96,9 @@ def move_block_down(self) -> bool: def move_block_right(self) -> bool: return self.block.move_right(self.grid) + def rotate_block(self) -> bool: + return self.block.rotate(self.grid) + def move_block_left(self) -> bool: return self.block.move_left(self.grid) @@ -163,9 +166,14 @@ def initializeDD(self): right_button.writeLine("➡️") right_button.enableFeedback("f", lambda *args: self.moveBlockRight()) + rotate_button = LayerLcd(self.dd, 2, 1, char_height=28) + rotate_button.noBackgroundColor() + rotate_button.writeLine("🔄") + rotate_button.enableFeedback("f", lambda *args: self.rotateBlock()) + AutoPin('V', AutoPin('S'), - AutoPin('H', left_button, right_button)).pin(self.dd) + AutoPin('H', left_button, rotate_button, right_button)).pin(self.dd) self.score = score self.block_pen = block_pen @@ -254,20 +262,18 @@ def moveBlockLeft(self) -> bool: self.startGame() return False return self.shape.move_block_left() - # if self.shape.move_block_left(): - # self.drawBlock() - # return True - # return False def moveBlockRight(self) -> bool: if self.shape is None: self.startGame() return False return self.shape.move_block_right() - # if self.shape.move_block_right(): - # self.drawBlock() - # return True - # return False + + def rotateBlock(self) -> bool: + if self.shape is None: + self.startGame() + return False + return self.shape.rotate_block() if __name__ == "__main__": diff --git a/dumbdisplay_examples/tetris/tetris_two_block.py b/dumbdisplay_examples/tetris/tetris_two_block.py index 795fec0..b05a0f5 100644 --- a/dumbdisplay_examples/tetris/tetris_two_block.py +++ b/dumbdisplay_examples/tetris/tetris_two_block.py @@ -11,7 +11,7 @@ from dumbdisplay.layer_lcd import LayerLcd from dumbdisplay_examples.tetris._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \ _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \ - _check_block_grid_placement, _randomize_grid + _check_bad_block_grid_placement, _randomize_grid from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd @@ -109,7 +109,7 @@ def reset_block(self) -> bool: block_grid = _randomize_block_grid() x -= int((block_grid.n_cols - 1) / 2) y += 1 - block_grid.n_rows - if _check_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): + if _check_bad_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): return False self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) self.sync_image() From 97744632cef5ed0ce8a483d94eac4cebe6b175ce Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Fri, 5 Sep 2025 21:57:31 +0800 Subject: [PATCH 74/76] working on TetrisTwoBlockApp --- dumbdisplay_examples/tetris/_common.py | 30 ++++++++++++------- dumbdisplay_examples/tetris/tetris_classic.py | 3 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/dumbdisplay_examples/tetris/_common.py b/dumbdisplay_examples/tetris/_common.py index dcc57ea..867d2e2 100644 --- a/dumbdisplay_examples/tetris/_common.py +++ b/dumbdisplay_examples/tetris/_common.py @@ -3,6 +3,8 @@ from dumbdisplay.layer_turtle import LayerTurtle + + _width = 400 _height = 700 _top = 230 @@ -45,13 +47,16 @@ def set_value(self, row_idx, col_idx, value): class Block: - def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle): + def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle, rotate_with_level: bool = False): self.x = x self.y = y + self.rotation = 0 self.block_grid = block_grid self.block_pen = block_pen + self.rotate_with_level = rotate_with_level + self.move_reach_in_millis = 50 block_pen.clear() - if True: + if not rotate_with_level: # make the block tiled a bit block_pen.setLevelRotation(2, 90, 120) # calculated from _left and _top self.sync_image() @@ -61,22 +66,21 @@ def move_down(self, grid: Grid) -> bool: if _check_bad_block_grid_placement(self.block_grid, self.x, self.y + 1, grid=grid): return False self.y += 1 - self.sync_image() + self.sync_image(self.move_reach_in_millis) return True def move_right(self, grid: Grid) -> bool: if _check_bad_block_grid_placement(self.block_grid, self.x + 1, self.y, grid=grid): return False self.x += 1 - self.sync_image() + self.sync_image(self.move_reach_in_millis) return True def move_left(self, grid: Grid) -> bool: if _check_bad_block_grid_placement(self.block_grid, self.x - 1, self.y, grid=grid): return False self.x -= 1 - #print(f"* left ==> x={self.x}") - self.sync_image() + self.sync_image(self.move_reach_in_millis) return True def rotate(self, grid: Grid) -> bool: @@ -85,15 +89,21 @@ def rotate(self, grid: Grid) -> bool: return False self.block_grid = rotated_block_grid self.y += y_offset - self.block_pen.clear() - _draw_grid(self.block_grid, self.block_pen) + self.rotation = (self.rotation + 90) % 360 + if self.rotate_with_level: + self.sync_image(self.move_reach_in_millis) + else: + self.block_pen.clear() + _draw_grid(self.block_grid, self.block_pen) return True - def sync_image(self): + def sync_image(self, reach_in_millis: int = 0): anchor_x = self.x * _block_unit_width anchor_y = self.y * _block_unit_width - self.block_pen.setLevelAnchor(anchor_x, anchor_y) + self.block_pen.setLevelAnchor(anchor_x, anchor_y, reach_in_millis) + if self.rotate_with_level: + self.block_pen.setLevelRotation(self.rotation + 2, 90 + 10, 120 + 10, reach_in_millis) # calculated from _left and _top diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py index d65f8ad..e36f4ac 100644 --- a/dumbdisplay_examples/tetris/tetris_classic.py +++ b/dumbdisplay_examples/tetris/tetris_classic.py @@ -18,6 +18,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd _RANDOMIZE_ROW_COUNT = 2 +_ROTATE_WITH_LEVEL = True _delay = 0.3 # For time/sleep @@ -80,7 +81,7 @@ def reset_block(self) -> bool: y += 1 - block_grid.n_rows if _check_bad_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): return False - self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen) + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen, rotate_with_level=_ROTATE_WITH_LEVEL) self.sync_image() return True From e4ef3a890f30052bbc71b71b3f16146aa60f9f59 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 6 Sep 2025 10:25:25 +0800 Subject: [PATCH 75/76] working on TetrisTwoBlockApp --- dumbdisplay_examples/tetris/_common.py | 23 ++++++++++++++----- dumbdisplay_examples/tetris/tetris_classic.py | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dumbdisplay_examples/tetris/_common.py b/dumbdisplay_examples/tetris/_common.py index 867d2e2..532deeb 100644 --- a/dumbdisplay_examples/tetris/_common.py +++ b/dumbdisplay_examples/tetris/_common.py @@ -89,21 +89,31 @@ def rotate(self, grid: Grid) -> bool: return False self.block_grid = rotated_block_grid self.y += y_offset - self.rotation = (self.rotation + 90) % 360 + self.rotation = (self.rotation + 1) % 4 if self.rotate_with_level: self.sync_image(self.move_reach_in_millis) else: self.block_pen.clear() + #self.sync_image(self.move_reach_in_millis) _draw_grid(self.block_grid, self.block_pen) return True def sync_image(self, reach_in_millis: int = 0): - anchor_x = self.x * _block_unit_width - anchor_y = self.y * _block_unit_width - self.block_pen.setLevelAnchor(anchor_x, anchor_y, reach_in_millis) if self.rotate_with_level: - self.block_pen.setLevelRotation(self.rotation + 2, 90 + 10, 120 + 10, reach_in_millis) # calculated from _left and _top + angle = 90 * self.rotation + 2 + pivot_x = 90 + 10 + pivot_y = 120 + 10 + anchor_x = self.x * _block_unit_width + anchor_y = self.y * _block_unit_width + # if self.rotation == 2: + # anchor_x += _block_unit_width + self.block_pen.setLevelRotation(angle, pivot_x, pivot_y, reach_in_millis) # calculated from _left and _top + self.block_pen.setLevelAnchor(anchor_x, anchor_y, reach_in_millis) + else: + anchor_x = self.x * _block_unit_width + anchor_y = self.y * _block_unit_width + self.block_pen.setLevelAnchor(anchor_x, anchor_y, reach_in_millis) @@ -202,7 +212,8 @@ def _check_bad_block_grid_placement(block_grid: Grid, block_grid_x_off: int, blo def _rotate_block_grid_if_possible(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> (Grid, int): block_grid_cells = block_grid.grid_cells rotated_cells = _rotate_block_grid_cells(block_grid_cells) - y_offset = len(rotated_cells) - len(block_grid_cells) + #y_offset = len(rotated_cells) - len(block_grid_cells) + y_offset = 0 if _check_bad_block_grid_cells_placement(rotated_cells, block_grid_x_off, block_grid_y_offset + y_offset, grid, check_boundary=True): return (None, None) rotated_block_grid = Grid(grid_cells=rotated_cells) diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py index e36f4ac..ee8fa7b 100644 --- a/dumbdisplay_examples/tetris/tetris_classic.py +++ b/dumbdisplay_examples/tetris/tetris_classic.py @@ -18,7 +18,7 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd _RANDOMIZE_ROW_COUNT = 2 -_ROTATE_WITH_LEVEL = True +_ROTATE_WITH_LEVEL = False _delay = 0.3 # For time/sleep From 479c40fa512946c976d2343ff380dee6862522a8 Mon Sep 17 00:00:00 2001 From: Trevor Lee Date: Sat, 6 Sep 2025 17:40:38 +0800 Subject: [PATCH 76/76] working on TetrisTwoBlockApp --- dumbdisplay_examples/tetris/_common.py | 81 ++++++++++++------- dumbdisplay_examples/tetris/_shapes.py | 7 +- dumbdisplay_examples/tetris/tetris_classic.py | 8 +- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/dumbdisplay_examples/tetris/_common.py b/dumbdisplay_examples/tetris/_common.py index 532deeb..607575c 100644 --- a/dumbdisplay_examples/tetris/_common.py +++ b/dumbdisplay_examples/tetris/_common.py @@ -18,8 +18,9 @@ class Grid: - def __init__(self, grid_cells): + def __init__(self, grid_cells: list[list[int]], grid_cell_type = None): self.grid_cells = grid_cells + self.grid_cell_type = grid_cell_type self.grid_dirty = [] for grid_row in self.grid_cells: grid_dirty_row = [] @@ -47,14 +48,14 @@ def set_value(self, row_idx, col_idx, value): class Block: - def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle, rotate_with_level: bool = False): + def __init__(self, x: int, y: int, block_grid: Grid, block_pen: LayerTurtle, move_reach_in_millis: int = 50, rotate_with_level: bool = False): self.x = x self.y = y self.rotation = 0 self.block_grid = block_grid self.block_pen = block_pen self.rotate_with_level = rotate_with_level - self.move_reach_in_millis = 50 + self.move_reach_in_millis = move_reach_in_millis block_pen.clear() if not rotate_with_level: # make the block tiled a bit @@ -100,14 +101,54 @@ def rotate(self, grid: Grid) -> bool: def sync_image(self, reach_in_millis: int = 0): + from dumbdisplay_examples.tetris._shapes import _vertical_line, _horizontal_line, _left_l, _right_l, _left_s, _right_s, _t if self.rotate_with_level: angle = 90 * self.rotation + 2 pivot_x = 90 + 10 pivot_y = 120 + 10 - anchor_x = self.x * _block_unit_width - anchor_y = self.y * _block_unit_width - # if self.rotation == 2: - # anchor_x += _block_unit_width + x = self.x + y = self.y + # n_rows = self.block_grid.n_rows + # n_cols = self.block_grid.n_cols + rotation = self.rotation + block_type = self.block_grid.grid_cell_type + # if rotation == 1: + # print("rotated 90") + # if True: # square + # pass + if block_type is _vertical_line: # _vertical_line + if rotation == 1: + x += 2 + elif rotation == 2: + x -= 1 + elif rotation == 3: + y -= 1 + elif block_type is _horizontal_line: # _horizontal_line + if rotation == 1: + x -= 1 + elif rotation == 2: + x += 2 + y -= 1 + # elif self.rotation == 3: + # pass + elif block_type is _left_l or block_type is _right_l: # _left_l and _right_l + # if self.rotation == 1: + # pass + if rotation == 2: + x += 2 + y -= 1 + # elif self.rotation == 3: + # pass + elif block_type is _left_s or block_type is _right_s or block_type is _t: # _left_s and _right_s and _t + # if self.rotation == 1: + # pass + if rotation == 2: + x += 1 + y -= 1 + # elif self.rotation == 3: + # pass + anchor_x = x * _block_unit_width + anchor_y = y * _block_unit_width self.block_pen.setLevelRotation(angle, pivot_x, pivot_y, reach_in_millis) # calculated from _left and _top self.block_pen.setLevelAnchor(anchor_x, anchor_y, reach_in_millis) else: @@ -186,28 +227,7 @@ def _draw_grid(grid: Grid, pen: LayerTurtle): _draw(x, y, color_number, pen) def _check_bad_block_grid_placement(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid, check_boundary: bool = True) -> bool: - if True: - return _check_bad_block_grid_cells_placement(block_grid.grid_cells, block_grid_x_off, block_grid_y_offset, grid, check_boundary) - else: - for y in range(block_grid.n_rows): - for x in range(block_grid.n_cols): - if block_grid.get_value(y, x) != 0: - row_idx = y + block_grid_y_offset - col_idx = x + block_grid_x_off - if row_idx < 0: - continue # never mind above the grid - if row_idx < 0 or row_idx >= grid.n_rows: - if not check_boundary: - continue - return True - if col_idx < 0 or col_idx >= grid.n_cols: - if not check_boundary: - continue - return True - if grid.get_value(row_idx, col_idx) != 0: - return True - return False - + return _check_bad_block_grid_cells_placement(block_grid.grid_cells, block_grid_x_off, block_grid_y_offset, grid, check_boundary) def _rotate_block_grid_if_possible(block_grid: Grid, block_grid_x_off: int, block_grid_y_offset: int, grid: Grid) -> (Grid, int): block_grid_cells = block_grid.grid_cells @@ -216,7 +236,8 @@ def _rotate_block_grid_if_possible(block_grid: Grid, block_grid_x_off: int, bloc y_offset = 0 if _check_bad_block_grid_cells_placement(rotated_cells, block_grid_x_off, block_grid_y_offset + y_offset, grid, check_boundary=True): return (None, None) - rotated_block_grid = Grid(grid_cells=rotated_cells) + grid_cell_type = block_grid.grid_cell_type + rotated_block_grid = Grid(grid_cells=rotated_cells, grid_cell_type=grid_cell_type) return (rotated_block_grid, y_offset) diff --git a/dumbdisplay_examples/tetris/_shapes.py b/dumbdisplay_examples/tetris/_shapes.py index 5c59cf5..610c1d9 100644 --- a/dumbdisplay_examples/tetris/_shapes.py +++ b/dumbdisplay_examples/tetris/_shapes.py @@ -32,9 +32,10 @@ def _randomize_block_grid() -> Grid: block_grid = random.choice(_shapes) - if False: - block_grid = _vertical_line + # if True: + # block_grid = _t color = random.randint(1, len(_colors) - 1) + block_grid_cell_type = block_grid block_grid = [[color if cell != 0 else 0 for cell in row] for row in block_grid] - return Grid(grid_cells=block_grid) + return Grid(grid_cells=block_grid, grid_cell_type=block_grid_cell_type) diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py index ee8fa7b..d0ad308 100644 --- a/dumbdisplay_examples/tetris/tetris_classic.py +++ b/dumbdisplay_examples/tetris/tetris_classic.py @@ -18,10 +18,14 @@ from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd _RANDOMIZE_ROW_COUNT = 2 -_ROTATE_WITH_LEVEL = False +_ROTATE_WITH_LEVEL = True _delay = 0.3 # For time/sleep +_level_animation_millis = 50 + +# _delay = 5000 # TODO: reset after debug +# _level_animation_millis = 5000 def _check_grid(shape: 'Shape', score: LayerTurtle) -> (bool, int): grid = shape.grid @@ -81,7 +85,7 @@ def reset_block(self) -> bool: y += 1 - block_grid.n_rows if _check_bad_block_grid_placement(block_grid, x, y, grid=self.grid, check_boundary=False): return False - self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen, rotate_with_level=_ROTATE_WITH_LEVEL) + self.block = Block(x, y, block_grid=block_grid, block_pen=self.block_pen, move_reach_in_millis=_level_animation_millis, rotate_with_level=_ROTATE_WITH_LEVEL) self.sync_image() return True