Created
October 15, 2023 11:22
-
-
Save fmaida/b983df6adf3f5d7e1ea02f544d068af1 to your computer and use it in GitHub Desktop.
Fifteen Puzzle game done in python 3 by using the library Textual (https://textual.textualize.io)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Screen { | |
layout: grid; | |
grid-size: 4 4; | |
/* grid-rows: 5% 15% 80%; */ | |
} | |
Tile { | |
width: 9; | |
height: 3; | |
color: #fff; | |
background: #bb2244; | |
border: $secondary tall; | |
content-align: center middle; | |
} | |
.left-margin { | |
margin-left: 1; | |
} | |
.right-margin { | |
margin-right: 1; | |
} | |
.colspan-2 { | |
column-span: 2; | |
} | |
.colspan-3 { | |
column-span: 3; | |
} | |
.text { | |
margin: 1; | |
} | |
.full-width { | |
width: 100%; | |
} | |
.full-height { | |
height: 100%; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from random import randint | |
from textual.app import App, ComposeResult, RenderResult | |
from textual.widget import Widget | |
from textual.widgets import Header, Footer | |
#from textual import log | |
class Tile(Widget): | |
def __init__(self, value) -> None: | |
super().__init__() | |
self.value = value | |
def render(self) -> RenderResult: | |
""" | |
Will show or hide the current tile, depending on its value | |
(16th tile = hidden, otherwise shown) | |
Then it will print the value of the tile inside | |
""" | |
self.visible = True if self.value < 16 else False | |
return " " if self.value == 16 else str(self.value).rjust(3) | |
class FifteenPuzzle(App): | |
CSS_PATH = "fifteen_puzzle.css" | |
BINDINGS = [("left", "left", "Left"), ("right", "right", "Right"), ("up", "up", "Up"), ("down", "down", "Down"), ("d", "toggle_dark", "Toggle dark mode"), ("escape", "quit", "Quit")] | |
def __init__(self) -> None: | |
super().__init__() | |
# self.x and self.y tracks the current | |
# position of the 16th tile | |
self.x = -1 | |
self.y = -1 | |
def compose(self) -> ComposeResult: | |
self.title = "Fifteen Puzzle" | |
yield Header() | |
self.tiles = [] | |
for y in range(4): | |
temp = [] | |
for x in range(4): | |
temp2 = Tile(value=y*4+x+1) | |
temp.append(temp2) | |
yield temp2 | |
self.tiles.append(temp) | |
yield Footer() | |
# Almost ready to start, let's shuffle | |
# the tiles before beginning | |
self.shuffle() | |
def action_toggle_dark(self) -> None: | |
self.dark = not self.dark | |
def action_left(self) -> None: | |
""" | |
Slide the 16th tile to the right (if that's possible) | |
""" | |
if self.x < 3: | |
before = self.x, self.y | |
after = self.x + 1, self.y | |
self.swap(before, after) | |
self.x += 1 | |
def action_right(self) -> None: | |
""" | |
Slide the 16th tile to the left (if that's possible) | |
""" | |
if self.x > 0: | |
before = self.x, self.y | |
after = self.x - 1, self.y | |
self.swap(before, after) | |
self.x -= 1 | |
def action_up(self) -> None: | |
""" | |
Slide the 16th tile to the lower row (if that's possible) | |
""" | |
if self.y < 3: | |
before = self.x, self.y | |
after = self.x, self.y + 1 | |
self.swap(before, after) | |
self.y += 1 | |
def action_down(self) -> None: | |
""" | |
Slide the 16th tile to the upper row (if that's possible) | |
""" | |
if self.y > 0: | |
before = self.x, self.y | |
after = self.x, self.y - 1 | |
self.swap(before, after) | |
self.y -= 1 | |
def shuffle(self) -> None: | |
""" | |
Shuffle the tiles | |
""" | |
# Shuffle a couple of random tiles for 32 times | |
for i in range(32): | |
before = randint(0, 3), randint(0, 3) | |
after = randint(0, 3), randint(0, 3) | |
self.swap(before, after) | |
# Refresh all the displayed tiles on the screen | |
for y in range(4): | |
for x in range(4): | |
temp = self.tiles[y][x] | |
# If this is the 16th tile, | |
# we need to track down its position | |
if temp.value == 16: | |
self.x = x | |
self.y = y | |
temp.render() | |
def swap(self, before: tuple, after: tuple) -> None: | |
""" | |
Swap a tile with another one | |
Args: | |
before (tuple): X and Y coordinates of the first tile to be swapped | |
after (tuple): X and Y coordinates of the second tile to be swapped | |
""" | |
bx, by = before | |
ax, ay = after | |
# swap the two tiles | |
# (actually this operation is a one liner | |
# but splitted on four for the sake of simplicity) | |
self.tiles[by][bx].value, \ | |
self.tiles[ay][ax].value = \ | |
self.tiles[ay][ax].value, \ | |
self.tiles[by][bx].value | |
# The two swapped tiles need to be rendered again | |
self.tiles[by][bx].render() | |
self.tiles[ay][ax].render() | |
# This line is for debugging purposes | |
#self.log.debug(self.tiles[ay][ax].value) | |
def action_quit(self) -> int: | |
""" | |
The user wants to quit | |
Returns: | |
int: Return value | |
""" | |
quit(0) | |
if __name__ == "__main__": | |
FifteenPuzzle().run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment