1
0
Fork 0
wasp-os/apps/puzzle15.py
Eloi Torrents 92ffa7cd1c Add 15 Puzzle app
Signed-off-by: Eloi Torrents <eloitor@disroot.org>
2023-08-24 21:19:16 +01:00

184 lines
6.7 KiB
Python

# SPDX-License-Identifier: MIT
# Copyright (C) 2023 Eloi Torrents
"""Puzzle 15
~~~~~~~~~~~~
A popular sliding block puzzle game.
.. figure:: res/screenshots/Puzzle15App.png
:width: 179
Screenshot of the 15 puzzle application
"""
import wasp
import widgets
import random
import fonts
from micropython import const
_FONT = fonts.sans24
_GRID_SIZE = const(4)
_GRID_PADDING = const(8)
_GRID_BACKGROUND = const(0x942F)
_EMPTY_BACKGROUND = const(0x9CB1)
_CELL_BACKGROUND = const(0xEF19)
_CELL_FOREGROUND = const(0x736C)
_SCREEN_SIZE = const(240)
_CELL_SIZE = const((_SCREEN_SIZE - (_GRID_PADDING * (_GRID_SIZE + 1))) // _GRID_SIZE)
# 2-bit RLE, 96x64, generated from res/icons/puzzle_15_icon.png, 566 bytes
icon = (
b'\x02'
b'`@'
b'\x10\xbf\x01 \xbf\x01 \xbf\x01 \x83@\xfaM\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\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 \x83M'
b'\x82M\x82M\x82M\x83 \x83M\x82M\x82M\x82M'
b'\x83 \x83M\x82M\x82M\x82M\x83 \x83M\x82M'
b'\x82M\x82M\x83 \xbf\x01 \xbf\x01 \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 \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\x81\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'\xff\x01\x10'
)
class Puzzle15App():
"""Let's solve the 15 puzzle."""
NAME = '15'
ICON = icon
def __init__(self):
"""Initialize the application."""
self._state = 0
self._confirmation_view = widgets.ConfirmationView()
self._start_game()
def foreground(self):
"""Activate the application."""
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.SWIPE_LEFTRIGHT)
self._state = 0
self._draw()
def touch(self,event):
"""Notify the application of a touchscreen touch event."""
if self._state == 0:
self._confirmation_view.draw("{} Moves. Again?".format(self._move_count))
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."""
draw = wasp.watch.drawable
if self._state == 0:
move_x, move_y = 0, 0
if event[0] == wasp.EventType.LEFT and self._empty_y < _GRID_SIZE - 1:
move_y = 1
elif event[0] == wasp.EventType.RIGHT and self._empty_y > 0:
move_y = -1
elif event[0] == wasp.EventType.UP and self._empty_x < _GRID_SIZE - 1:
move_x = 1
elif event[0] == wasp.EventType.DOWN and self._empty_x > 0:
move_x = -1
if move_x != 0 or move_y !=0:
x, y, b = self._empty_x, self._empty_y, self._board
b[x][y], b[x + move_x][y + move_y] = b[x + move_x][y + move_y], b[x][y]
self._empty_x += move_x
self._empty_y += move_y
self._move_count += 1
self._update(draw, x, y)
self._update(draw, self._empty_x, self._empty_y)
def _draw(self):
"""Draw the display from scratch."""
draw = wasp.watch.drawable
draw.fill(_GRID_BACKGROUND)
draw.set_font(_FONT)
for y in range(_GRID_SIZE):
for x in range(_GRID_SIZE):
self._update(draw, y, x)
def _update(self, draw, 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))
if self._board[row][col] != 0:
draw.set_color(_CELL_FOREGROUND, _CELL_BACKGROUND)
draw.fill(_CELL_BACKGROUND, x, y, _CELL_SIZE, _CELL_SIZE)
draw.string(str(self._board[row][col]), x, y + 16, _CELL_SIZE)
else:
draw.fill(_EMPTY_BACKGROUND, x, y, _CELL_SIZE, _CELL_SIZE)
def _start_game(self):
"""Start a new game."""
self._board = self._create_board()
self._empty_x = _GRID_SIZE - 1
self._empty_y = _GRID_SIZE - 1
self._move_count = 0
def _get_invCount(self, board):
"""Count inversion count.
https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/"""
num_list = [num for row in board for num in row]
inv_count = 0
for i, num1 in enumerate(num_list[:-2]):
for num2 in num_list[i+1:-1]:
if num1 > num2:
inv_count += 1
return inv_count
def _getNum(self, v):
"""return the next random number"""
n = len(v)
idx = random.randint(0, n-1)
num, v[idx] = v[idx], v[n-1]
v.pop()
return num
def _create_board(self):
"""Create a new GRID_SIZE x GRID_SIZE board."""
board = [[0] * _GRID_SIZE for _ in range(_GRID_SIZE)]
v = list(range(1, _GRID_SIZE * _GRID_SIZE))
for i in range(_GRID_SIZE):
for j in range(_GRID_SIZE):
if i != _GRID_SIZE - 1 or j != _GRID_SIZE -1:
board[i][j] = self._getNum(v)
# Ensure solvability
if self._get_invCount(board) % 2 == 1:
board[-1][-3], board[-1][-2] = board[-1][-2], board[-1][-3]
return board