From ffff5ae52b9e7d7e6ddf45075e1ee100d73ae75b Mon Sep 17 00:00:00 2001 From: Miguel Rochefort Date: Mon, 4 Jan 2021 22:12:39 -0500 Subject: [PATCH] apps: play2048: Add the 2048 game application 2048 is a popular sliding block puzzle game in which tiles are combined to make the number 2048. It's one of the few games that are enjoyable to play on such a small form factor. This started as a port of a TkInter implementation of the 2048 game. I implemented all of the TkInter APIs used by the game and it worked on wasp-os without any code change in the game. However, the performance was very poor and it consumed too much RAM. I have since reimplemented the whole game from scratch and managed to achieve acceptable performance, although more improvements could still be made. Because names in Python can't start with numbers, I had some trouble naming things. The module is called "ttfe" (two-thousand-forty-eight), the class name is Play2048App, and the software.py entry is "Play 2048". Signed-off-by: Miguel Rochefort [daniel@redfelineninja.org.uk: Renamed the python filename, normalized the screenshot and included the app in the docs] Signed-off-by: Daniel Thompson --- README.rst | 4 + docs/apps.rst | 2 + res/2048App.png | Bin 0 -> 5499 bytes res/2048_icon.png | Bin 0 -> 7768 bytes wasp/apps/play2048.py | 229 ++++++++++++++++++++++++++++++++ wasp/apps/software.py | 9 +- wasp/boards/manifest_240x240.py | 1 + 7 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 res/2048App.png create mode 100644 res/2048_icon.png create mode 100644 wasp/apps/play2048.py diff --git a/README.rst b/README.rst index 5ca32a9..ac6f6f7 100644 --- a/README.rst +++ b/README.rst @@ -189,6 +189,10 @@ using one of the techniques is the Application Writer's guide. :alt: Calculator running in the wasp-os simulator :width: 179 +.. image:: res/2048App.png + :alt: Let's play the 2048 game (in the wasp-os simulator) + :width: 179 + .. image:: res/SnakeApp.png :alt: Snake Game running in the wasp-os simulator :width: 179 diff --git a/docs/apps.rst b/docs/apps.rst index 5cadd94..38f3617 100644 --- a/docs/apps.rst +++ b/docs/apps.rst @@ -32,6 +32,8 @@ Games .. automodule:: apps.gameoflife +.. automodule:: apps.play2048 + .. automodule:: apps.snake Integration diff --git a/res/2048App.png b/res/2048App.png new file mode 100644 index 0000000000000000000000000000000000000000..31e7ee22d57515ea56decc208c2f273945c7a0e0 GIT binary patch literal 5499 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 literal 0 HcmV?d00001 diff --git a/wasp/apps/play2048.py b/wasp/apps/play2048.py new file mode 100644 index 0000000..63dd448 --- /dev/null +++ b/wasp/apps/play2048.py @@ -0,0 +1,229 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Miguel Rochefort +"""Play 2048 +~~~~~~~~~~~~ + +A popular sliding block puzzle game in which tiles are combined to make the +number 2048. + + .. figure:: res/2048App.png + :width: 179 + + Screenshot of the 2048 game application +""" + +import wasp +import icons +import widgets +import random +import fonts +from micropython import const + +SCREEN_SIZE = const(240) + +GRID_PADDING = const(8) +GRID_SIZE = const(4) +CELL_SIZE = const(50) + +GRID_BACKGROUND = const(0x942F) +CELL_BACKGROUND = [0x9CB1, 0xEF3B, 0xEF19, 0xF58F, 0xF4AC, 0xF3EB, 0xF2E7, 0xEE6E, 0xEE6C, 0xEE4A, 0xEE27, 0xEE05] +CELL_FOREGROUND = [0x9CB1, 0x736C, 0x736C, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE] +CELL_LABEL = ['','2','4','8','16','32','64','128','256','512','1K','2K'] # TODO: Display 1024 and 2048 (text-wrapping) + +# 2-bit RLE, generated from res/2048_icon.png, 785 bytes +icon = ( + b'\x02' + b'`@' + b'\x10\xbf\x01 \xbf\x01 \xbf\x01 \x83@\x81M\x82M' + b'\x82M\x82M\x83 \x83M\x82M\x82M\x82M\x83 ' + b'\x83M\x82M\x82M\x82M\x83 \x83M\x82M\x82M' + b'\x82M\x83 \x83M\x82M\x82M\x82M\x83 \x83M' + b'\x82M\x82M\x82M\x83 \x83M\x82M\x82M\x82M' + b'\x83 \x83M\x82M\x82M\x82M\x83 \x83M\x82M' + b'\x82M\x82M\x83 \x83M\x82M\x82M\x82M\x83 ' + b'\x83M\x82M\x82M\x82M\x83 \x83M\x82M\x82M' + b'\x82M\x83 \x83M\x82M\x82M\x82M\x83 \xbf\x01' + b' \xbf\x01 \x83M\x82M\x82M\x82\x80\xfb\x8d\xc0\xdb' + b'\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M' + b'\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 ' + b'\xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M' + b'\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M' + b'\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d' + b'\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M' + b'\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 ' + b'\xc3M\xc2M\xc2M\xc2\x8d\xc3 \xff\x01 \xff\x01 ' + b'\xc3\x8d\xc2M\xc2M\xc2@\xfaM\xc3 \xc3\x8d\xc2\x80' + b'\x81\x8d\xc2\x8d\xc2M\xc3 \xc3\xc0\xfb\xcd@\xdbB\x8d' + b'B\x8dB\x80\xfa\x8dC C\xcdB\xc0\x81\xcdB\xcd' + b'B\x8dC C@\xfbEA\x82AD\x80\xdb\x82\xcd' + b'\x82\xcd\x82\xc0\xfa\xcd\x83 \x83DAAAA\xc1A' + b'C\x82@\x81M\x82M\x82\xcd\x83 \x83\x80\xfb\x88\xc1' + b'\x81\x83\xc0\xdb\xc2M\xc2M\xc2@\xfaM\xc3 \xc3\x87' + b'\x81A\x81\x83\xc2\x80\x81\x8d\xc2\x8d\xc2M\xc3 \xc3\xc0' + b'\xfb\xc7A\xc1\xc4@\xdbB\x8dB\x8dB\x80\xfa\x8dC' + b' C\xc6\x81\xc1\xc5B\xc0\x81\xcdB\xcdB\x8dC ' + b'C@\xfbE\x81AF\x80\xdb\x82\xcd\x82\xcd\x82\xc0\xfa' + b'\xcd\x83 \x83DA\xc4AC\x82@\x81M\x82M\x82' + b'\xcd\x83 \x83\x80\xfb\x8d\xc0\xdb\xc2M\xc2M\xc2@\xfa' + b'M\xc3 \xff\x01 \xff\x01 \xc3\x80\x81\x8d\xc2\xc0\xfb' + b'\xcd@\xdbB\x80\xf6\x8dB\xc0\xc8\xcdC C@\x81' + b'M\x80\xdb\x82\xc0\xfb\xcd\x82@\xf6M\x82\x80\xc8\x8d\xc0' + b'\xdb\xc3 \xc3@\x81M\xc2\x80\xfb\x8d\xc2\xc0\xf6\xcd@' + b'\xdbB\x80\xc8\x8dC C\xc0\x81\xcdB@\xfbM\x80' + b'\xdb\x82\xc0\xf6\xcd\x82@\xc8M\x83 \x83\x80\x81\x8d\xc0' + b'\xdb\xc2@\xfbM\xc2\x80\xf6\x8d\xc2\xc0\xc8\xcd@\xdbC' + b' C\x80\x81\x8dB\xc0\xfb\xcdB@\xf6M\x80\xdb\x82' + b'\xc0\xc8\xcd\x83 \x83@\x81M\x82\x80\xfb\x8d\xc0\xdb\xc2' + b'@\xf6M\xc2\x80\xc8\x8d\xc3 \xc3\xc0\x81\xcd@\xdbB' + b'\x80\xfb\x8dB\xc0\xf6\xcdB@\xc8M\x80\xdb\x83 \x83' + b'\xc0\x81\xcd\x82@\xfbM\x82\x80\xf6\x8d\xc0\xdb\xc2@\xc8' + b'M\xc3 \xc3\x80\x81\x8d\xc2\xc0\xfb\xcd@\xdbB\x80\xf6' + b'\x8dB\xc0\xc8\xcdC C@\x81M\x80\xdb\x82\xc0\xfb' + b'\xcd\x82@\xf6M\x82\x80\xc8\x8d\xc0\xdb\xc3 \xc3@\x81' + b'M\xc2\x80\xfb\x8d\xc2\xc0\xf6\xcd@\xdbB\x80\xc8\x8dC' + b' C\xc0\x81\xcdB@\xfbM\x80\xdb\x82\xc0\xf6\xcd\x82' + b'@\xc8M\x83 \xbf\x01 \xbf\x01 \xbf\x01\x10' +) + +class Play2048App(): + """Let's play the 2048 game.""" + NAME = '2048' + ICON = icon + + def __init__(self): + """Initialize the application.""" + self._board = None + self._state = 0 + self._confirmation_view = None + + def foreground(self): + """Activate the application.""" + wasp.system.request_event(wasp.EventMask.TOUCH | + wasp.EventMask.SWIPE_UPDOWN | + wasp.EventMask.SWIPE_LEFTRIGHT) + + self._state = 0 + + if not self._board: + self._start_game() + + self._draw() + + def touch(self,event): + """Notify the application of a touchscreen touch event.""" + if self._state == 0: + if not self._confirmation_view: + self._confirmation_view = widgets.ConfirmationView() + self._confirmation_view.draw('Restart game?') + self._state = 1 + elif self._state == 1: + if self._confirmation_view.touch(event): + if self._confirmation_view.value: + self._start_game() + self._draw() + self._state = 0 + + def swipe(self, event): + """Notify the application of a touchscreen swipe event.""" + moved = False + + if event[0] == wasp.EventType.UP: + moved = self._shift(1,False) + elif event[0] == wasp.EventType.DOWN: + moved = self._shift(-1,False) + elif event[0] == wasp.EventType.LEFT: + moved = self._shift(1,True) + elif event[0] == wasp.EventType.RIGHT: + moved = self._shift(-1,True) + + if moved: + self._add_tile() + + def _draw(self): + """Draw the display from scratch.""" + board = self._board + draw = wasp.watch.drawable + draw.fill(GRID_BACKGROUND) + draw.set_font(fonts.sans24) + for y in range(GRID_SIZE): + for x in range(GRID_SIZE): + self._update(draw, board[y][x], y, x) + + def _update(self, draw, cell, row, col): + """Update the specified cell of the application display.""" + x = GRID_PADDING + (col * (CELL_SIZE + GRID_PADDING)) + y = GRID_PADDING + (row * (CELL_SIZE + GRID_PADDING)) + draw.set_color(CELL_FOREGROUND[cell], CELL_BACKGROUND[cell]) + draw.fill(CELL_BACKGROUND[cell], x, y, CELL_SIZE, CELL_SIZE) + draw.string(CELL_LABEL[cell], x, y + 16, CELL_SIZE) + + def _start_game(self): + """Start a new game.""" + self._board = self._create_board() + self._add_tile() + self._add_tile() + + def _create_board(self): + """Create an empty 4x4 board.""" + board = [] + for _ in range(GRID_SIZE): + board.append([0] * GRID_SIZE) + return board + + def _add_tile(self): + """Add a new tile to a random empty location on the board.""" + board = self._board + randint = random.randint + y = randint(0, GRID_SIZE-1) + x = randint(0, GRID_SIZE-1) + while board[y][x] != 0: + y = randint(0, GRID_SIZE-1) + x = randint(0, GRID_SIZE-1) + board[y][x] = 1 + self._update(wasp.watch.drawable,1,y,x) + + def _shift(self, direction, orientation): + """Shift and merge the tiles vertically.""" + draw = wasp.watch.drawable + update = self._update + board = self._board + moved = False + + def read(y, x): + if not orientation: + y,x = x,y + return board[y][x] + + def write(y, x, v): + if not orientation: + y,x = x,y + + board[y][x] = v + update(draw, v, y, x) + + if direction > 0: + s = 0 + 1 + e = GRID_SIZE + else: + s = GRID_SIZE - 1 - 1 + e = 0 - 1 + + for y in range(GRID_SIZE): + p = s - direction + for x in range(s,e,direction): + a = read(y,x) + b = read(y,p) + if a != 0: + if a == b: + write(y, p, a + 1) + write(y, x, 0) + moved = True + p += direction + else: + if b != 0: + p += direction + if x != p: + write(y, p, a) + write(y, x, 0) + moved = True + return moved diff --git a/wasp/apps/software.py b/wasp/apps/software.py index b8bea2c..92a2531 100644 --- a/wasp/apps/software.py +++ b/wasp/apps/software.py @@ -19,10 +19,11 @@ class SoftwareApp(): ('fibonacci_clock', wasp.widgets.Checkbox(0, 120, 'Fibonacci Clock')), ('gameoflife', wasp.widgets.Checkbox(0, 160, 'Game Of Life')), ('musicplayer', wasp.widgets.Checkbox(0, 0, 'Music Player')), - ('snake', wasp.widgets.Checkbox(0, 40, 'Snake Game')), - ('flashlight', wasp.widgets.Checkbox(0, 80, 'Torch')), - ('testapp', wasp.widgets.Checkbox(0, 120, 'Test')), - ('timer', wasp.widgets.Checkbox(0, 160, 'Timer')), + ('play2048', wasp.widgets.Checkbox(0, 40, 'Play 2048')), + ('snake', wasp.widgets.Checkbox(0, 80, 'Snake Game')), + ('flashlight', wasp.widgets.Checkbox(0, 120, 'Torch')), + ('testapp', wasp.widgets.Checkbox(0, 160, 'Test')), + ('timer', wasp.widgets.Checkbox(0, 0, 'Timer')), ) self.si = wasp.widgets.ScrollIndicator() self.page = 0 diff --git a/wasp/boards/manifest_240x240.py b/wasp/boards/manifest_240x240.py index ed3a692..d691542 100644 --- a/wasp/boards/manifest_240x240.py +++ b/wasp/boards/manifest_240x240.py @@ -15,6 +15,7 @@ manifest = ( 'apps/musicplayer.py', 'apps/launcher.py', 'apps/pager.py', + 'apps/play2048.py', 'apps/settings.py', 'apps/software.py', 'apps/steps.py',