diff --git a/.gitignore b/.gitignore
index ac605c4..89ca057 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
/.vscode
/venv
+__pycache__
+
/MicroPythonDumbDisplay.iml
/MicroPythonDumbDisplay.code-workspace
@@ -17,3 +19,4 @@
uDumbDisplay.code-workspace
/OLD.md
+/setup_OLD.py
diff --git a/README.md b/README.md
index 80274eb..2422c24 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# DumbDisplay MicroPython Library (v0.5.0)
+# 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)
@@ -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
```
@@ -213,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
@@ -334,6 +349,12 @@ MIT
# Change History
+v0.6.0
+- added DDRootLayer
+- added LayerTurtle
+- added more examples
+- bug fixes
+
v0.5.0
- ported "level options" for LayerGraphical
- ported LayerSelection
diff --git a/_dev_test.py b/_dev_test.py
index fbd9afa..6b9d8a0 100644
--- a/_dev_test.py
+++ b/_dev_test.py
@@ -1,8 +1,5 @@
import random
-import time
-import math
-
-
+from dumbdisplay.full import *
from dumbdisplay_examples.utils import create_example_wifi_dd
@@ -25,6 +22,179 @@ def run_melody():
import samples.melody.main
+def test_turtleTracked():
+ from dumbdisplay.layer_turtle import LayerTurtleTracked
+ dd = create_example_wifi_dd()
+ l = LayerTurtleTracked(dd, 2000, 2000)
+ l.backgroundColor("ivory")
+ l.border(3, "blue")
+ if True:
+ 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(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:
+ dd.sleep(0.1)
+ 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}")
+ 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)
+ if True:
+ 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, r2, centered=centered)
+ elif shape == 1:
+ l.centeredPolygon(r, 5, inside=centered)
+ elif shape == 2:
+ l.polygon(r, 5)
+ elif shape == 3:
+ l.arc(r, r2, a, a2, centered=centered)
+ elif shape == 4:
+ 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)
+ l.penColor("blue")
+ 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)
+ dd = create_example_wifi_dd()
+ 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.freezeDrawing()
+ distance = 1
+ i = 0
+ else:
+ if reconnecting:
+ dd.masterReset()
+ l = None
+ 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:
+ dd.unfreezeDrawing(refreeze_after_draw=True)
+ print(f" --- unfreeze")
+ else:
+ if freeze_for_steps > 0:
+ dd.unfreezeDrawing()
+ print(f" --- FINAL[{i}] unfreeze")
+ else:
+ coor = l.pos(sync=sync)
+ 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
+
+
+
+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)
+ dd.pinLayer(status_layer, 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_margin():
from dumbdisplay.layer_ledgrid import LayerLedGrid
dd = create_example_wifi_dd()
@@ -51,22 +221,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 +235,20 @@ 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_passive_turtleTracked(sync=True)
- #test_margin()
- #run_debugBlepriority("MyBLEDevice")
+ #test_auto_pin_remaining()
- #test_read_readme()
- #test_find_packages()
+ # 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 a2422c9..8fad7e0 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
@@ -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")
@@ -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")
@@ -171,6 +171,30 @@ 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 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
+ 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
+
+ while True:
+ dd.timeslice()
+
def demo_AutoPin():
from dumbdisplay.full import LayerLedGrid, LayerLcd, LayerGraphical, Layer7SegmentRow, LayerSelection, AutoPin
@@ -291,7 +315,10 @@ def run_mnist_app():
if __name__ == "__main__":
- demo_Feedback()
+ #run_sliding_puzzle_app()
+ #run_mnist_app()
+ #demo_AutoPin()
+ demo_LayerTurtle()
if True:
demo_LayerLedGrid(2, 2)
@@ -301,6 +328,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/_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/ddcmds.py b/dumbdisplay/ddcmds.py
new file mode 100644
index 0000000..34f489f
--- /dev/null
+++ b/dumbdisplay/ddcmds.py
@@ -0,0 +1,3 @@
+DDC_setlevelanchor = "#3a"
+DDC_movelevelanchorby = "#3b"
+DDC_setlevelrotate = "#4e"
diff --git a/dumbdisplay/ddimpl.py b/dumbdisplay/ddimpl.py
index 4e1adf9..d48a8a3 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):
@@ -12,10 +12,14 @@
#_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_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"
+
+_ROOT_LAYER_ID = "00" # hardcoded
_DBG_TNL = False
@@ -26,16 +30,51 @@
_VALIDATE_GAP: int = 1000
_RECONNECTING_VALIDATE_GAP: int = 500
+_INIT_ACK_SEQ = 0
+
+def _NEXT_ACK_SEQ(ack_seq: int) -> int:
+ if True:
+ ack_seq = (ack_seq + 1) % 62
+ else:
+ ack_seq = (ack_seq + 1) % 10
+ return 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)
+
+
+
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():
@@ -44,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:]
@@ -54,7 +95,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()
@@ -170,6 +211,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
@@ -178,9 +220,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):
@@ -201,6 +243,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()
@@ -253,16 +297,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: int, height: int, contained_alignment: str) -> 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:
@@ -364,12 +421,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: int = 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_TO_ACK_STR(ack_seq))
self._io.print('.')
self._io.print(command)
for i in range(0, len(params)):
@@ -386,6 +446,11 @@ def _sendCommand(self, layer_id: str, command: str, *params):
#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()
@@ -398,7 +463,7 @@ def _checkForFeedback(self):
pass
#self._onFeedbackKeepAlive()
else:
- #print(feedback)####
+ #print(feedback) # TODO: disable debug
if feedback.startswith('= "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
- type = "click"
+ text = None
+ 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(',')
- x = int(feedback[0:idx])
- y = int(feedback[idx + 1:])
+ idx2 = feedback.find(',', idx + 1)
+ x_str = feedback[0:idx]
+ if x_str != "":
+ x = int(x_str)
+ else:
+ x = 0
+ if idx2 == -1:
+ y_str = feedback[idx + 1:]
+ text = None
+ else:
+ y_str = feedback[idx + 1: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:
- layer._handleFeedback(type, x, y)
- except:
- pass
+ 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 Exception as e:
+ #print("** EXCEPT: " + feedback)
+ if True:
+ raise e
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():
@@ -491,6 +582,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)
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/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 a2f3bb8..28c6151 100644
--- a/dumbdisplay/ddlayer.py
+++ b/dumbdisplay/ddlayer.py
@@ -1,4 +1,3 @@
-
def DD_RGB_COLOR(r: int, g: int, b: int):
return r * 0x10000 + g * 0x100 + b
@@ -38,16 +37,16 @@ def _DD_COLOR_ARG(c):
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
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
@@ -55,28 +54,28 @@ def __init__(self, dd, 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:
@@ -84,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:
@@ -98,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:
@@ -112,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:
@@ -123,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")
@@ -132,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)
@@ -145,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)
@@ -168,31 +167,33 @@ 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
- 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:
- # if False: # TODO: text parameters
- # print(self._feedback_handler.__code__.co_argcount)
- 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: int, y: int, text: str):
+ pass
+
diff --git a/dumbdisplay/ddlayer_7segrow.py b/dumbdisplay/ddlayer_7segrow.py
index 0e710aa..05be562 100644
--- a/dumbdisplay/ddlayer_7segrow.py
+++ b/dumbdisplay/ddlayer_7segrow.py
@@ -1,48 +1,51 @@
+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):
- '''
+ """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 0af0f1e..c808113 100644
--- a/dumbdisplay/ddlayer_graphical.py
+++ b/dumbdisplay/ddlayer_graphical.py
@@ -1,26 +1,21 @@
#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
-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: 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))
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))
@@ -38,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))
@@ -70,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:
@@ -88,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))
@@ -126,20 +121,45 @@ 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 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 = ""):
+ """
+ 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 b244d1a..2daaf4a 100644
--- a/dumbdisplay/ddlayer_joystick.py
+++ b/dumbdisplay/ddlayer_joystick.py
@@ -1,36 +1,38 @@
+from .ddimpl import DumbDisplayImpl
from .ddlayer import DDLayer
from .ddlayer import _DD_COLOR_ARG, _DD_BOOL_ARG, _DD_INT_ARG, _DD_FLOAT_ARG
+
class DDLayerJoystick(DDLayer):
- '''Virtual joystick'''
- def __init__(self, dd, 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 afc53ad..6f192df 100644
--- a/dumbdisplay/ddlayer_lcd.py
+++ b/dumbdisplay/ddlayer_lcd.py
@@ -1,20 +1,22 @@
+from .ddimpl import DumbDisplayImpl
from .ddlayer import DDLayer
from .ddlayer import _DD_BOOL_ARG
from .ddlayer import _DD_COLOR_ARG
+
class DDLayerLcd(DDLayer):
- '''LCD'''
- def __init__(self, dd, col_count: int = 16, row_count: int = 2, char_height: int = 0, font_name: str = ''):
- '''
+ """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):
+ 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,20 +38,24 @@ 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"):
- '''write text as a line, with alignment "L", "C", or "R"'''
+ 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):
- '''write text as a line, with align "centered"'''
+ 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):
- '''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 ebf5f40..d8ffe2c 100644
--- a/dumbdisplay/ddlayer_ledgrid.py
+++ b/dumbdisplay/ddlayer_ledgrid.py
@@ -1,42 +1,47 @@
+from .ddimpl import DumbDisplayImpl
from .ddlayer import DDLayer
from .ddlayer import _DD_COLOR_ARG
class DDLayerLedGrid(DDLayer):
- '''Grid of LEDs'''
- def __init__(self, dd, col_count = 1, row_count = 1, sub_col_count = 1, sub_row_count = 1):
- '''
+ """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 = 0, y = 0):
- '''turn off LED @ (x, y)'''
+ 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)'''
+ """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):
- '''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):
- '''turn on LEDs to form a vertical "bar"'''
- self.dd._sendCommand(self.layer_id, "ledvertbar", str(count), str(bottomToTop))
+ 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(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(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 7d4fbb3..064c929 100644
--- a/dumbdisplay/ddlayer_multilevel.py
+++ b/dumbdisplay/ddlayer_multilevel.py
@@ -1,17 +1,19 @@
+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
DD_DEF_LAYER_LEVEL_ID = "_"
class DDLayerMultiLevel(DDLayer):
- def __init__(self, dd, layer_id):
+ 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))
@@ -19,135 +21,156 @@ 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
- :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))
+ 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
: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"
- '''
+ """
+ #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, DDC_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))
+ 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"
+ 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))
+ 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, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y), _DD_INT_ARG(reach_in_millis));
+ 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, "movelevelanchorby", _DD_FLOAT_ARG(by_x), _DD_FLOAT_ARG(by_y));
+ 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
: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)
-# TODO: add more
diff --git a/dumbdisplay/ddlayer_plotter.py b/dumbdisplay/ddlayer_plotter.py
index f8f4b06..884e4d6 100644
--- a/dumbdisplay/ddlayer_plotter.py
+++ b/dumbdisplay/ddlayer_plotter.py
@@ -1,26 +1,26 @@
+from .ddimpl import DumbDisplayImpl
from .ddlayer import DDLayer
-from .ddlayer import _DD_BOOL_ARG
-from .ddlayer import _DD_COLOR_ARG
+
class DDLayerPlotter(DDLayer):
- '''Plotter'''
- def __init__(self, dd, width, height, pixels_per_second = 10):
- '''
+ """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 2b09edf..1a45727 100644
--- a/dumbdisplay/ddlayer_selection.py
+++ b/dumbdisplay/ddlayer_selection.py
@@ -1,10 +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
+
class DDLayerSelection(DDLayer):
- '''Selection'''
- def __init__(self, dd,
+ """Selection"""
+ 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 = "",
@@ -15,54 +16,54 @@ def __init__(self, dd,
_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
new file mode 100644
index 0000000..d0239e0
--- /dev/null
+++ b/dumbdisplay/ddlayer_turtle.py
@@ -0,0 +1,186 @@
+#from ._ddlayer 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(DDLayerMultiLevel):
+ """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"""
+ 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, align: str = "L"):
+ """write text, with alignment 'L', 'C', or 'R'"""
+ 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)
+ def _sendCommandTracked(self, command: str, *params):
+ self.dd._sendCommand(self.layer_id, command, *params)
+
+
+class DDLayerTurtleTracked(DDLayerTurtle): # TODO: working on DDLayerTurtleTracked
+ """
+ EXPERIMENTAL: Turtle-like Layer, with position tracking
+ """
+ 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
+ self._next_ack_seq: int = _INIT_ACK_SEQ
+ def pos(self, sync: bool = True) -> (int, int):
+ while self._pending_ack_seq is not None:
+ self.dd.timeslice()
+ if not sync or self.dd._is_reconnecting():
+ break
+ 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 _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
+ self.dd._sendCommand(self.layer_id, command, *params, ack_seq=ack_seq)
+ 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
+ self._y = y
diff --git a/dumbdisplay/dumbdisplay.py b/dumbdisplay/dumbdisplay.py
index 3c97832..ee2bcbc 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
@@ -14,18 +13,18 @@
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):
+ 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:
@@ -49,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})"
@@ -66,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:
@@ -101,29 +100,47 @@ 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()
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 freezeDrawing(self):
+ self._connect()
+ self._sendCommand(None, "FRZ")
+ def unfreezeDrawing(self, refreeze_after_draw: bool = False):
+ self._connect()
+ self._sendCommand(None, "UNFRZ", _DD_BOOL_ARG(refreeze_after_draw))
def recordLayerSetupCommands(self):
self._connect()
self._sendCommand(None, "RECC")
@@ -137,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:
@@ -157,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):
@@ -174,6 +191,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/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_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
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
diff --git a/dumbdisplay_examples/OLD/tetris_classic/tetris_classic.py b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic.py
new file mode 100644
index 0000000..680b45a
--- /dev/null
+++ b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic.py
@@ -0,0 +1,393 @@
+# ***
+# *** 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)
+ self.x = None
+ self.y = None
+ self.color = None
+ 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/OLD/tetris_classic/tetris_classic_OLD_0.py b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic_OLD_0.py
new file mode 100644
index 0000000..59aa8fc
--- /dev/null
+++ b/dumbdisplay_examples/OLD/tetris_classic/tetris_classic_OLD_0.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/OLD/tetris_one_block/main.py b/dumbdisplay_examples/OLD/tetris_one_block/main.py
new file mode 100644
index 0000000..7576fbe
--- /dev/null
+++ b/dumbdisplay_examples/OLD/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/OLD/tetris_one_block/tetris_one_block.py b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block.py
new file mode 100644
index 0000000..d5f40d1
--- /dev/null
+++ b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block.py
@@ -0,0 +1,437 @@
+# ***
+# *** 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
+
+_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 = [ # 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 _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):
+ 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)
+
+
+
+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 = 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/OLD/tetris_one_block/tetris_one_block_2.py b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block_2.py
new file mode 100644
index 0000000..7bb5302
--- /dev/null
+++ b/dumbdisplay_examples/OLD/tetris_one_block/tetris_one_block_2.py
@@ -0,0 +1,428 @@
+# ***
+# *** 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
+
+_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("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 = TetrisOneBlock2App(create_example_wifi_dd())
+ app.run()
diff --git a/dumbdisplay_examples/mnist/main.py b/dumbdisplay_examples/mnist/main.py
new file mode 100644
index 0000000..9ae1b91
--- /dev/null
+++ b/dumbdisplay_examples/mnist/main.py
@@ -0,0 +1,16 @@
+###
+# 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()
+
+
+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
new file mode 100644
index 0000000..3319c53
--- /dev/null
+++ b/dumbdisplay_examples/passive_blink/main.py
@@ -0,0 +1,13 @@
+###
+# 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()
+
+
+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
new file mode 100644
index 0000000..ff3653c
--- /dev/null
+++ b/dumbdisplay_examples/sliding_puzzle/main.py
@@ -0,0 +1,15 @@
+###
+# 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()
+
+
+if __name__ == "__main__":
+ sample_run()
\ No newline at end of file
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/tetris/_common.py b/dumbdisplay_examples/tetris/_common.py
new file mode 100644
index 0000000..607575c
--- /dev/null
+++ b/dumbdisplay_examples/tetris/_common.py
@@ -0,0 +1,250 @@
+import random
+
+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: 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 = []
+ 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
+
+
+class Block:
+ 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 = move_reach_in_millis
+ block_pen.clear()
+ 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()
+ _draw_grid(block_grid, block_pen)
+
+ 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.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.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
+ self.sync_image(self.move_reach_in_millis)
+ 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.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):
+ 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
+ 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:
+ 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)
+
+
+
+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):
+ 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)
+ # (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)
+
+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:
+ 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
+ rotated_cells = _rotate_block_grid_cells(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)
+ 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)
+
+
+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/_shapes.py b/dumbdisplay_examples/tetris/_shapes.py
new file mode 100644
index 0000000..610c1d9
--- /dev/null
+++ b/dumbdisplay_examples/tetris/_shapes.py
@@ -0,0 +1,41 @@
+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 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, grid_cell_type=block_grid_cell_type)
+
diff --git a/dumbdisplay_examples/tetris/tetris_classic.py b/dumbdisplay_examples/tetris/tetris_classic.py
new file mode 100644
index 0000000..d0ad308
--- /dev/null
+++ b/dumbdisplay_examples/tetris/tetris_classic.py
@@ -0,0 +1,287 @@
+# ***
+# *** 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_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
+
+_RANDOMIZE_ROW_COUNT = 2
+_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
+ 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_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, move_reach_in_millis=_level_animation_millis, rotate_with_level=_ROTATE_WITH_LEVEL)
+ 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 rotate_block(self) -> bool:
+ return self.block.rotate(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())
+
+ 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, rotate_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()
+
+ def moveBlockRight(self) -> bool:
+ if self.shape is None:
+ self.startGame()
+ return False
+ return self.shape.move_block_right()
+
+ def rotateBlock(self) -> bool:
+ if self.shape is None:
+ self.startGame()
+ return False
+ return self.shape.rotate_block()
+
+
+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
new file mode 100644
index 0000000..caa866b
--- /dev/null
+++ b/dumbdisplay_examples/tetris/tetris_one_block.py
@@ -0,0 +1,335 @@
+# ***
+# *** 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._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
+
+_RANDOMIZE_ROW_COUNT = 4
+
+
+
+_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]
+]
+
+
+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)
+
+
+class OneBlock:
+ def __init__(self, x: int, y: int, block_pen: LayerTurtle):
+ self.x = x
+ self.y = y
+ self.color = random.randint(1, len(_colors) - 1)
+ 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:
+ 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: OneBlock = 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 = OneBlock(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)
+
+ 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.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)
+
+ def resetBlock(self) -> bool:
+ return self.shape.reset_block()
+
+ def moveBlockDown(self) -> bool:
+ return self.shape.move_block_down()
+
+ def moveBlockLeft(self) -> bool:
+ if self.shape is None:
+ self.startGame()
+ return False
+ return self.shape.move_block_left()
+
+ def moveBlockRight(self) -> bool:
+ if self.shape is None:
+ self.startGame()
+ return False
+ return self.shape.move_block_right()
+
+
+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..b05a0f5
--- /dev/null
+++ b/dumbdisplay_examples/tetris/tetris_two_block.py
@@ -0,0 +1,307 @@
+# ***
+# *** 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._common import Grid, _colors, _grid_n_rows, _grid_n_cols, _block_unit_width, \
+ _width, _height, _left, _top, _draw_grid, _commit_block_grid, Block, \
+ _check_bad_block_grid_placement, _randomize_grid
+
+from dumbdisplay_examples.utils import DDAppBase, create_example_wifi_dd
+
+_RANDOMIZE_ROW_COUNT = 2
+
+
+_delay = 0.3 # For time/sleep
+
+
+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)
+ if False: # TODO: disable after debug
+ n_rows = 1
+ n_cols = 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_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_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()
+ 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("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)
+ 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/utils.py b/dumbdisplay_examples/utils.py
index 1d93896..5065553 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
@@ -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
+
+
+
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 64c8c66..b33521f 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:
@@ -49,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):
@@ -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:
diff --git a/screenshots/layer_turtle.png b/screenshots/layer_turtle.png
new file mode 100644
index 0000000..188a553
Binary files /dev/null and b/screenshots/layer_turtle.png differ
diff --git a/setup.py b/setup.py
index aad2fb4..ac794de 100644
--- a/setup.py
+++ b/setup.py
@@ -2,23 +2,14 @@
from setuptools.config.expand import find_packages
-# 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',
+ version='0.6.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=find_packages(include=["dumbdisplay*"]),
- install_requires=[],
)