Skip to content

Instantly share code, notes, and snippets.

@zrzka zrzka/tile-game.py
Last active Feb 2, 2018

Embed
What would you like to do?
Tiles
import math
from scene import run, Scene, LabelNode, SpriteNode, Size, Point, Texture, Action
import secrets
from enum import Enum
class Direction(tuple, Enum):
UP = (-1, 0) # 1 row up, no column change
DOWN = (1, 0) # 1 row down, no column change
LEFT = (0, -1) # no row change, one column left
RIGHT = (0, 1) # no row change, one column right
@classmethod
def from_radians(cls, radians):
"""Converts radians angle to `Direction`.
Args:
radians (float): Angle in radians where 0 points up
"""
degrees = math.degrees(radians)
if degrees >= 45 and degrees < 135:
return Direction.RIGHT
elif degrees >= 135 and degrees < 215:
return Direction.DOWN
elif degrees >= 215 and degrees < 305:
return Direction.LEFT
else:
return Direction.UP
class Model:
"""
(0, 0) is top left corner.
Args:
tile_count (int): Number of tiles per row & column
Attributes:
_positions (dict): Map where key is tile index and value is tuple (row, col)
_empty_position (tuple): Tuple holding empty position (row, col)
"""
def __init__(self, tile_count):
self.tile_count = tile_count
available_positions = [
(r, c)
for r in range(tile_count)
for c in range(tile_count)
]
self._positions = {}
for i in range(tile_count * tile_count - 1):
position = secrets.choice(available_positions)
self._positions[i] = position
available_positions.remove(position)
self._empty_position = available_positions[0]
def tile_position(self, index):
return self._positions[index]
def moved_tile_position(self, index, direction):
position = self.tile_position(index)
new_position = (position[0] + direction[0], position[1] + direction[1])
if not new_position == self._empty_position:
raise ValueError(f'Unable to move tile {index} to {direction}')
return new_position
def move_tile(self, index, direction):
new_position = self.moved_tile_position(index, direction)
old_position = self._positions[index]
self._positions[index] = new_position
self._empty_position = old_position
return new_position
class TileNode(SpriteNode):
_TEXTURE = {
True: 'spc:PowerupYellow',
False: 'spc:PowerupBlue'
}
def __init__(self, index, *args, **kwargs):
super().__init__(self._TEXTURE[False], *args, **kwargs)
self.index = index
self.title_node = LabelNode(f'{index+1}', font=('Helvetica-Bold', 24), position=(0, 1), parent=self)
self._highlighted = False
@property
def highlighted(self):
return self._highlighted
@highlighted.setter
def highlighted(self, x):
# We have to save size, because texture change resets it
size = self.size
self.texture = Texture(self._TEXTURE[x])
self.size = size
self._highlighted = x
class GameScene(Scene):
"""
Args:
tile_count (int): Number of tiles per row / column
tile_margin (float): Spacing around each tile
board_size_ratio (float): How big is the board, 1.0 whole width / height, 0.8 = 80%
"""
def __init__(self, tile_count, tile_margin, board_size_ratio, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tile_count = tile_count
self.tile_margin = tile_margin
self.board_size_ratio = board_size_ratio
self.model = Model(tile_count)
# Nodes
self.board_background = None
self.tiles = None
# Cached values for moving tile
self._active_tile_index = None
self._touch_began_location = None
# Cached value, invalidated when did_change_size is called
self._tile_size = None
self._board_background_size = None
self._board_background_position = None
self._top_left_tile_position = None
def setup(self):
self.background_color = 'black'
self.board_background = SpriteNode('spc:BackgroundDarkPurple')
self.add_child(self.board_background)
self.tiles = []
for i in range(self.tile_count * self.tile_count - 1):
node = TileNode(i)
self.add_child(node)
self.tiles.append(node)
self.did_change_size()
def invalidate_cached_values(self):
self._tile_size = None
self._board_background_size = None
self._board_background_position = None
self._top_left_tile_position = None
@property
def board_background_size(self):
if self._board_background_size is None:
min_size = min(self.size.width, self.size.height)
board_size = self.board_size_ratio * min_size
self._board_background_size = Size(board_size, board_size)
return self._board_background_size
@property
def board_background_position(self):
if self._board_background_position is None:
self._board_background_position = self.size / 2
return self._board_background_position
@property
def tile_size(self):
if self._tile_size is None:
size = (self.board_background_size.width - self.tile_margin * (self.tile_count + 1)) / self.tile_count
self._tile_size = Size(size, size)
return self._tile_size
@property
def top_left_tile_position(self):
if self._top_left_tile_position is None:
x = self.board_background_position.x - self.board_background_size.width / 2
x += self.tile_margin + self.tile_size.width / 2
y = self.board_background_position.y + self.board_background_size.height / 2
y -= self.tile_margin + self.tile_size.height / 2
self._top_left_tile_position = Point(x, y)
return self._top_left_tile_position
def tile_position(self, row, col):
top_left = self.top_left_tile_position
size = self.tile_size.width
x = top_left.x + col * (size + self.tile_margin)
y = top_left.y - row * (size + self.tile_margin)
return Point(x, y)
def did_change_size(self):
self.invalidate_cached_values()
self.board_background.size = self.board_background_size
self.board_background.position = self.board_background_position
for i in range(self.tile_count * self.tile_count - 1):
position = self.model.tile_position(i)
tile = self.tiles[i]
tile.size = self.tile_size
tile.position = self.tile_position(*position)
def touch_began(self, touch):
if self._active_tile_index is not None:
return
for tile in self.tiles:
if touch.location in tile.frame:
self._active_tile_index = tile.index
self._touch_began_location = touch.location
tile.highlighted = True
return
def _move_direction(self, touch):
theta = math.atan2(
touch.location.x - self._touch_began_location.x,
touch.location.y - self._touch_began_location.y,
)
if theta < 0.0:
theta += math.pi * 2
return Direction.from_radians(theta)
def touch_moved(self, touch):
if self._active_tile_index is None:
return
if touch.location == self._touch_began_location:
return
direction = self._move_direction(touch)
tile = self.tiles[self._active_tile_index]
try:
new_position = self.model.moved_tile_position(tile.index, direction)
except ValueError:
new_position = self.model.tile_position(tile.index)
tile.run_action(Action.move_to(*self.tile_position(*new_position), 0.2))
def touch_ended(self, touch):
if self._active_tile_index is None:
return
direction = self._move_direction(touch)
tile = self.tiles[self._active_tile_index]
try:
new_position = self.model.move_tile(tile.index, direction)
except ValueError:
new_position = self.model.tile_position(tile.index)
tile.run_action(Action.move_to(*self.tile_position(*new_position), 0.2))
tile.highlighted = False
self._active_tile_index = None
def main():
run(GameScene(4, 20, 0.8))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.