Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Problem with ability to slide tiles.
-
Hi everyone!
This is my first post here, and the interface is kind of confusing, so I’m sorry if this is formatted strangely. I’ve been trying to teach myself how to code through this game. It’s a tile sliding game, trying to order the tiles into a correct position. Currently, I’m just trying to get movement to work, and the sprites and abilit to start/end/win a game haven’t been implemented. I’ve gotten most of it going, but I can’t seem to get the tiles to move around correctly.
I want one of three things to happen: if the tile tries to move past the boundaries, it can’t move; if the tile tries to move onto another tile, it can’t move; if the tile tries to move to a free space, it moves. I’ve been trying to do this using a for loop (as the names of the tiles are stored under the same variable name, so I have to access them in a way other than by name). However, I can’t get the code to do those three things consistently. I’m pretty sure the problem is with the for loop and if statement combination I have, but I can’t think of another method for doing this.
Sorry that some of the comments haven’t been updated as I’ve been changing the code, but most of it is still correct.I’m using an iPad Pro, version 11.2.2, and Pythonista version 3.2.
Any help would be appreciated. Code starts after this line.
'''Description: A 4x4 board of numbered tiles has one missing space and is randomly set up. To win the game, the player must slide tiles over to put the tiles back in order.
Variants: Instead of numbers, you can have a scrambled picture cut up into 4x4 tiles.'''
from scene import *
import random
import ui
import marshal
import string
import sound
import math
import time
import Image, ImageDrawclass Game(Scene):
MARGIN_OF_ERROR = 33 #Creates background and initial variables that the rest of the game builds off of. def setup(self): self.background_color = '#646464' self.create_tile() self.drag = False self.Tile = None self.touch_location = None self.touched_tile = None #Creates tiles and adds their graphics by randomly selecting a position (was supposed to have numbers but couldn't get that working), adds that position to actual_position list and removes it from position_options lists so no tile is put in the same spot. It then adds all those tiles the game generated into a list that gets accessed later in the game. def create_tile(self): position_options = [(522, 510), (522, 444), (522, 378), (522, 312), (588, 510), (588, 444), (588, 378), (588, 312), (654, 510), (654, 444), (654, 378), (654, 312), (456, 510), (456, 444), (456, 378)] #possible positions actual_position = [] self.tiles = [] t_shape = 'plf:Tile_BoxCrate' #sprite of tile for j in range(15): position = random.choice(position_options) #chooses position actual_position.append(position) #adds position to other list position_options.remove(position) #removes position so it can't be rechosen self.Tile = SpriteNode(t_shape, position) #creates tile self.tiles.append(self.Tile) #adds that tile to self.tiles list self.add_child(self.Tile) #adds tile to gameboard #this function determines where player has touched. It goes through every position option and compares the x and y value of where player touched to intial position option with a margin of error of twenty. It then adds that position to a self.drag variable to be used elsewhere in the class. def touch_began(self, touch): position_options = [(522, 510), (522, 444), (522, 378), (522, 312), (588, 510), (588, 444), (588, 378), (588, 312), (654, 510), (654, 444), (654, 378), (654, 312), (456, 510), (456, 444), (456, 378), (456, 312)] for j in position_options: if touch.location[0] <= j[0] + self.MARGIN_OF_ERROR and touch.location[0] >= j[0] - self.MARGIN_OF_ERROR and touch.location[1] <= j[1] + self.MARGIN_OF_ERROR and touch.location[1] >= j[1] - self.MARGIN_OF_ERROR: #checks if touch.location is within the margin of error to a position option self.drag = j #sets empty variable self.drag to that position #this is the where the function checks if the touch has moved. It first loops through every position of the tiles previously generated and compares that to the self.drag position set earlier. It then sets the self.touched_tile variable to that tile SpriteNode created earlier, which will then allow movement. After that it checks where the player's touch ends and compares that to the inital self.drag position. Then based on that touch movement, it moved the SpriteNode to the next position over in that direction. def touch_moved(self, touch): for i in self.tiles: if i.position == self.drag: self.touched_tile = i #checks to see if there is a tile at that position and sets it to the touched_tile variable if touch.location[0] - self.drag[0] >= 50 and touch.location[1] <= touch.location[1] + self.MARGIN_OF_ERROR and touch.location[1] >= touch.location[1] - self.MARGIN_OF_ERROR: #checks to see if touch movement moves right move_right = Action.move_to(self.drag[0] + 66, self.drag[1], 1, TIMING_SINODIAL) #sets how tile moves for j in self.tiles: if self.drag[0] + 66 > 680: self.drag == False self.touched_tile == None print("Didn't move off gameboard") return self.drag and self.touched_tile elif self.drag[0] + 66 != j.position[0] and self.drag[1] == j.position[1]: self.touched_tile.run_action(move_right) self.drag == False print("I moved right") return self.touched_tile and self.drag else: self.drag == False self.touched_tile == None print("Failed") return self.drag and self.touched_tile elif touch.location[0] - self.drag[0] <= -50 and touch.location[1] <= touch.location[1] + self.MARGIN_OF_ERROR and touch.location[1] >= touch.location[1] - self.MARGIN_OF_ERROR: #checks to see if touch movement moves left move_left = Action.move_to(self.drag[0] - 66, self.drag[1], 1, TIMING_SINODIAL) for j in self.tiles: if self.drag[0] - 66 < 440: self.drag == False self.touched_tile == None print("Didn't move off gameboard") return self.drag and self.touched_tile elif self.drag[0] - 66 != j.position[0] and self.drag[1] == j.position[1]: self.touched_tile.run_action(move_left) self.drag == False print("I moved left") return self.touched_tile and self.drag else: self.drag == False self.touched_tile == None print("Failed") return self.drag and self.touched_tile elif touch.location[1] - self.drag[1] >= 50 and touch.location[0] <= touch.location[0] + self.MARGIN_OF_ERROR and touch.location[0] >= touch.location[0] - self.MARGIN_OF_ERROR: #checks to see if touch movement moves up move_up = Action.move_to(self.drag[0], self.drag[1] + 66, 1, TIMING_SINODIAL) for j in self.tiles: if self.drag[1] + 66 > 530: self.drag == False self.touched_tile == None print("Didn't move off gameboard") return self.drag and self.touched_tile elif self.drag[1] + 66 != j.position[1] and self.drag[0] == j.position[0]: self.touched_tile.run_action(move_up) self.drag == False print("I moved up") return self.touched_tile and self.drag else: self.drag == False self.touched_tile == None print("Failed") return self.drag and self.touched_tile elif touch.location[1] - self.drag[1] <= -50 and touch.location[0] <= touch.location[0] + self.MARGIN_OF_ERROR and touch.location[0] >= touch.location[0] - self.MARGIN_OF_ERROR: #checks to see if touch movement moves down move_down = Action.move_to(self.drag[0], self.drag[1] - 66, 1, TIMING_SINODIAL) for j in self.tiles: if self.drag[1] - 66 < 300: self.drag == False self.touched_tile print("Didn't move off gameboard") return self.drag and self.touched_tile elif self.drag[1] - 66 != j.position[1] and self.drag[0] == j.position[0]: self.touched_tile.run_action(move_down) self.drag == False print("I moved down") return self.touched_tile and self.drag else: self.drag == False self.touched_tile == None print("Failed") return self.drag and self.touched_tile #resets self.drag to intial False value for next touch to reset. def touch_ended(self, touch): if self.drag: self.drag = False return self.drag
#CHECK FOR LOOPS
if name == 'main':
run(Game(), PORTRAIT, show_fps=True)
) -
Sorry that I am not really answering your question, but here’s an alternate design for you to consider:
Have a ”virtual board” that is defined by (for example)
max_x
andmax_y
variables, both 4 in your example, but easily changed in the future if you want to experiment with other sizes in the future.Use the scene size and the variables above to calculate where tile position
(x,y)
really should be on the screen. This provides the additional option of easily experimenting with tile sizes and visual gaps between tiles.Have a variable like
free_spot
tell where there is room.Each tile should know which position it is in. You can keep it as an additional variable in the Node or just calculate it from the Node screen position.
Moving tiles:
touch_began
- Make the tile topmost in the z direction ('bring to front') and store the starting position.touch_moved
- Let the player move the tile anywhere.touch_ended
- Check that the tile has been moved some minimum amount. Then which direction (up, right, left, down) the tile has been moved the most. Then check if the tile could actually move in that direction. E.g. if 'up', is position (x,y-1) equal to thefree_spot
? If yes, animate the tile to that spot and update the free spot. If no, just animate the tile to its original spot.This approach will also let the player to just 'swipe' tiles in the direction they want them to move.
-
... and if you do not like the ’floaty’ behaviour of the design above, you can do the check in
touch_moved
instead oftouch_ended
. -
Hi @ETPH. Couple of generic advices:
- Do not use magic constants in your code (like
position_options
) in multiple places, it's a nightmare when you want to change them. Move them outside ofcreate_tile
,touch_began
and reuse them there. LikeMARGIN_OF_ERROR
. - Keep your functions / methods short. It's good when they can fit one screen (well, depends how the screen is big :), but you know what I mean. Not a hard rule, but it's about readability and crunching all these bugs quickly. It's easier when it's short.
touch_moved
is very long and overly complicated. - You shouldn't hardcode positions based on your device in case you want to run it on iPhone SE for example.
- Split your task into several smaller ones. Replace one huge function with many small ones doing just one thing. Again, readability & easier way how to spot a bug.
I didn't dive into your
touch_moved
method, sorry. But I did quickly hack an example of generic board & moving tiles. You can find it here. - Do not use magic constants in your code (like