Skip to content

Instantly share code, notes, and snippets.

@fmaida
Created October 15, 2023 11:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fmaida/b983df6adf3f5d7e1ea02f544d068af1 to your computer and use it in GitHub Desktop.
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)
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%;
}
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