omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Boggle App, need help changing labels 3x in a function

    Pythonista
    3
    11
    6731
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • JonB
      JonB last edited by

      With ui, nothing is shown in your ui until your function ends and returns control to the ui thread.

      The ui.in_background is a little confusing because it can only run one thing in the background at a time, including whatever code you have in your main script.

      The trick is to use either ui.delay, or ui.animate, which run on the main(ui) thread. In this case, animate is probably what you want, though it is a little trickier to string together animations, since you have to set the completion animation in the arguments, but a little redursive helper makes it easy to call.

      Here is a simple proof of concept:
      https://gist.github.com/55695af0195c686f580f1d1faa04eaf6

      I was lazy, and made pressing an individual dice roll them all, but you pribably want a separate button for that, and a dufferent action when pressing the dice, but this shows the basic idea.

      For what its worth, this also shows how to avoid having to individually name every label -- the same code works for a 3x3 grid or a 10x10 grid.

      1 Reply Last reply Reply Quote 0
      • fergal
        fergal last edited by

        Wow JonB! I'm blown away by the detailed and awesome response!

        I'm at work now but I will try to dissect and understand this the best I can tonight when I get home! As you can tell by my code, I'm just beginning my python journey and my Pythonista journey as well. This should help a great deal.

        1 Reply Last reply Reply Quote 0
        • JonB
          JonB last edited by

          https://gist.github.com/a0fcfed33d13f3bc0de4051318042bb4
          here is a slightly more animated roll, which includes some translation and rotational jitter.

          JonB 1 Reply Last reply Reply Quote 0
          • JonB
            JonB @JonB last edited by

            @JonB
            https://gist.github.com/541ed070617b52aa3ee3271635036f10
            and now with boggle letters...

            1 Reply Last reply Reply Quote 0
            • fergal
              fergal last edited by

              HOLY COW, ok, that's going to be a lot more to digest! Thanks again, can't wait to dive in.

              1 Reply Last reply Reply Quote 0
              • fergal
                fergal last edited by fergal

                @JonB
                Ok JonB, I've been working on understanding what is going on and how I can implement this awesome work you've showed me.

                For me, the important part is that I understand each piece so I know how it works, so I do that by building the script up slowly and checking that I understand each piece as I do it.

                I started with creating the 'dice' here. You used buttons and were designing for an iphone screen size. I'm starting with an iPad size and moving to iPhone afterwards. So my first question is, can I work on the animations if the 'dice' are labels rather than buttons?

                The only reason that I am looking for labels over animation is that I want to be able to change the text size and weight and I'm not sure how to do that with buttons. BUT I can't change the corner radius with labels, so I'm kinda stuck.
                Here is what I have so far,
                https://gist.github.com/rgregory1/b96079da685fe3b5bca5407930155d7f

                My game plan is:

                • achieve the look I'm after
                • make them roll correctly
                • begin to understand the animation

                So knowing that, do I need to make the 'dice' buttons rather than labels?

                Thanks for any help!

                1 Reply Last reply Reply Quote 0
                • JonB
                  JonB last edited by

                  Buttons do have a font attribute, that works just like label. You use the tint attribute to change text color. labels have the corner radius "bug", but you could also have a View with a Label subview -- which is basically what a button does internally. Either approach can be animated the same way.

                  What might make a lot of sense is to define a custom subclass of View with a custom draw method, which would allow creating a"shaded" die. I'll post an experiment when I get home where the die consists of two half-height rounded rectangles of different shade, then a lighter circle on top for the face. Looks pleasing, and can still be animated.

                  Is your goal to create just the game board, and individual players use paper? Or will this be a solo game, where you want to be able to tap letter sequences to make words? If the latter, then button or custom view will be the way you want to go.

                  Final thought... The roll animation looks very 2d... If you go to some sort of drawn die or image, one could start to imagine a few "tween" images of a cube mid-roll. More complex, but depend on how much sugar you want.

                  1 Reply Last reply Reply Quote 0
                  • JonB
                    JonB last edited by

                    https://gist.github.com/3dbd60b05e13fa32a497122ff9754d7b
                    Here is a completely different take, a little more organized into classes. I tried to draw a more realistic dice image, and added scale (size) into the mix of things getting animated, so it feels more like the dice are moving in and out of the page... sort of. i tried to comment what i was doing.

                    sorry for hijacking your project... it was fun to experiment with.

                    1 Reply Last reply Reply Quote 0
                    • fergal
                      fergal last edited by

                      @JonB
                      No problem on the hijacking! I think it's great that you ran with it, I picked it because it is an achievable project for me, but also because it seems like a fun programming challenge. It was my first CLI challenge I set myself last year!

                      There are a million one player versions out there, my family really enjoys board games, so I want to be able to pull out my phone or ipad at the restaurant while waiting for food and play boggle with my daughters, we'll use pen and napkins for words. Keeping a score tally might be a fun activity to add in afterwards.

                      Your idea for the dice is great, I was going to try rounded corners with a circle in the middle as my final goal, so this is great.

                      Your code looks BRILLIANT, I can't wait to dig into it. Thank you for breaking it up for me to digest. I usually feel that one good example that I can understand will feed me for weeks!

                      Again, I appreciate it!

                      1 Reply Last reply Reply Quote 0
                      • enceladus
                        enceladus last edited by

                        The following word game by Omz was in the examples directory of old version of Pythonista. I have modified it slightly to run on new version (python 3).

                        # coding: utf-8
                        from scene import *
                        import ui
                        import sound
                        import marshal
                        import string
                        import time
                        from itertools import product, chain
                        from random import choice, random
                        from copy import copy
                        import os
                        from math import sqrt
                        import json
                        A = Action
                        
                        game_duration = 90
                        screen_w, screen_h = get_screen_size()
                        min_screen = min(screen_w, screen_h)
                        if max(get_screen_size()) >= 760:
                        	cols, rows = 10, 11
                        else:
                        	cols, rows = 7, 7
                        tile_size = min_screen / (max(cols, rows) + 1)
                        font_size = int(tile_size * 0.6)
                        tile_font = ('AvenirNext-Regular', font_size)
                        score_font = ('AvenirNext-Regular', 50)
                        time_font = ('AvenirNext-Regular', 32)
                        preview_font = ('AvenirNext-Regular', 24)
                        game_over_font = ('AvenirNext-Regular', 72)
                        points_font = ('AvenirNextCondensed-Regular', 32)
                        
                        # Derived from http://en.m.wikipedia.org/wiki/Letter_frequency
                        letter_freq = {'a': 8.2, 'b': 1.5, 'c': 2.8, 'd': 4.3, 'e': 12.7, 'f': 2.3, 'g': 2.0, 'h': 6.1, 'i': 7.0, 'j': 0.2, 'k': 7.7, 'l': 4.0, 'm': 2.4, 'n': 6.7, 'o': 7.5, 'p': 1.9, 'q': 0.1, 'r': 6.0, 's': 6.3, 't': 9.0, 'u': 2.8, 'v': 1.0, 'w': 2.4, 'x': 0.2, 'y': 2.0, 'z': 0.1}
                        letter_bag = list(chain(*[[letter] * int(letter_freq[letter]*10) for letter in letter_freq]))
                        
                        def build_dictionary():
                        	# Generate the word list if it doesn't exist yet.
                        	# It's represented as a set for fast lookup, and saved to disk using the `marshal` module.
                        	if os.path.exists('words.data'):
                        		return
                        	#import urllib
                        	import requests
                        	words = []
                        	#f = urllib.urlopen('https://github.com/atebits/Words/blob/master/Words/en.txt?raw=true')
                        	f = str(requests.get('https://github.com/atebits/Words/blob/master/Words/en.txt?raw=true').text)
                        	for line in f.split():
                        		words.append(line.strip())
                        	with open('words.data', 'w') as out:
                        		#marshal.dump(words, out)
                        		json.dump(words, out)
                        
                        with ui.ImageContext(tile_size, tile_size) as ctx:
                        	ui.set_color('silver')
                        	ui.Path.rounded_rect(2, 2, tile_size-4, tile_size-4, 4).fill()
                        	ui.set_color('white')
                        	ui.Path.rounded_rect(2, 2, tile_size-4, tile_size-6, 4).fill()
                        	tile_texture = Texture(ctx.get_image())
                        
                        class Tile (SpriteNode):
                        	def __init__(self, x, y, letter, color='white', multiplier=1):
                        		SpriteNode.__init__(self, tile_texture)
                        		self.x = x
                        		self.y = y
                        		self.letter = letter
                        		self._selected = False
                        		pos_y = y * tile_size + (tile_size/2 if x % 2 == 0 else 0)
                        		self.position = x * tile_size, pos_y
                        		self.tile_color = color
                        		self.color = color
                        		self.label = LabelNode(letter.upper(), font=tile_font)
                        		self.label.color = 'black'
                        		self.multiplier = multiplier
                        		self.add_child(self.label)
                        	
                        	@property
                        	def selected(self):
                        		return self._selected
                        	
                        	@selected.setter
                        	def selected(self, value):
                        		self._selected = value
                        		self.color = '#fdffce' if value else self.tile_color
                        
                        class Game (Scene):
                        	def setup(self):
                        		self.current_size = None
                        		self.current_word = None
                        		build_dictionary()
                        		with open('words.data') as f:
                        			#self.words = marshal.load(f)
                        			self.words = set(json.load(f))
                        		self.root = Node(parent=self)
                        		self.background_color = '#0f2634'
                        		self.tiles = []
                        		self.selected = []
                        		self.touched_tile = None
                        		self.score_label = LabelNode('0', font=score_font, parent=self)
                        		self.score = 0
                        		self.game_over = False
                        		self.game_over_time = 0
                        		self.word_label = LabelNode(font=preview_font, parent=self)
                        		self.time_label = LabelNode('00:00', font=time_font, parent=self)
                        		self.time_label.anchor_point = (0, 0.5)
                        		self.start_time = time.time()
                        		self.overlay = SpriteNode(color='black', alpha=0, z_position=3, parent=self)
                        		time_up_label = LabelNode('Time Up!', font=game_over_font, parent=self.overlay)
                        		self.did_change_size()
                        		self.new_game()
                        	
                        	def create_tile(self, x, y):
                        		letter = choice(letter_bag)
                        		bonus = random() < 0.07
                        		t = Tile(x, y, letter, '#cef9ff' if bonus else 'white', 2 if bonus else 1)
                        		return t
                        		
                        	def did_change_size(self):
                        		x_margin = (self.size.w - cols * tile_size)/2
                        		y_margin = (self.size.h - rows * tile_size)/2 - tile_size/2
                        		self.root.position = x_margin + tile_size/2, y_margin + tile_size/2
                        		self.overlay.position = self.size/2
                        		self.overlay.size = self.size
                        		if self.size.w < self.size.h:
                        			self.score_label.position = self.size.w/2, self.size.h - 100
                        			self.word_label.position = self.size.w/2, self.size.h - 140
                        			self.time_label.position = 20, self.size.h - 100
                        			self.time_label.anchor_point = (0, 0.5)
                        		else:
                        			self.score_label.position = x_margin/2, self.size.h - 100
                        			self.word_label.position = x_margin/2, self.size.h - 140
                        			self.time_label.position = self.size - (20, 100)
                        			self.time_label.anchor_point = (1, 0.5)
                        		
                        	def update(self):
                        		time_passed = time.time() - self.start_time
                        		t = max(0, int(game_duration - time_passed))
                        		self.time_label.text = '{0}:{1:0>2}'.format(t//60, t%60)
                        		if t == 0 and not self.game_over:
                        			self.end_game()
                        	
                        	def new_game(self, animated=False):
                        		if self.game_over:
                        			self.play_sound('digital:ZapThreeToneUp')
                        		for tile in self.tiles:
                        			tile.remove_from_parent()
                        		self.tiles = []
                        		for x, y in product(range(cols), range(rows)):
                        			s = self.create_tile(x, y)
                        			if animated:
                        				s.scale = 0
                        				s.run_action(Action.scale_to(1, 0.5, TIMING_EASE_OUT_2))
                        			self.tiles.append(s)
                        			self.root.add_child(s)
                        		self.game_over = False
                        		self.start_time = time.time()
                        		self.score = 0
                        		self.score_label.text = '0'
                        		self.overlay.run_action(Action.fade_to(0))
                        		self.word_label.text = ''
                        		self.selected = []
                        		
                        	def end_game(self):
                        		self.play_sound('digital:ZapThreeToneDown')
                        		self.game_over = True
                        		self.game_over_time = time.time()
                        		self.overlay.run_action(Action.fade_to(0.7))
                        	
                        	def get_selected_word(self):
                        		return ''.join([t.letter for t in self.selected]).lower()
                        	
                        	def touch_to_tile(self, location):
                        		touch_x = location[0] - self.root.position[0]
                        		touch_y = location[1] - self.root.position[1]
                        		x = int(touch_x / tile_size)
                        		if x % 2 != 0:
                        			y = int((touch_y - tile_size/2) / tile_size)
                        		else:
                        			y = int(touch_y / tile_size)
                        		return x, y
                        	
                        	def tile_at(self, location):
                        		x = location[0] - self.root.position[0]
                        		y = location[1] - self.root.position[1]
                        		for tile in self.tiles:
                        			if abs(tile.position - (x, y)) < tile_size/2:
                        				if tile.alpha < 1:
                        					continue
                        				return tile
                        		return None
                        	
                        	def is_neighbor(self, tile1, tile2):
                        		if (not tile1 or not tile2 or tile1 == tile2):
                        			return True
                        		x1, y1 = tile1.x, tile1.y
                        		x2, y2 = tile2.x, tile2.y
                        		if x1 == x2:
                        			return abs(y2-y1) <= 1
                        		elif x1 % 2 == 0:
                        			return abs(x2-x1) <= 1 and 0 <= (y2-y1) <= 1
                        		else:
                        			return abs(x2-x1) <= 1 and -1 <= (y2-y1) <= 0
                        	
                        	def select_tile(self, tile):
                        		if not tile or (self.selected and self.selected[-1] == tile):
                        			return
                        		if tile in self.selected:
                        			self.selected = self.selected[:self.selected.index(tile)+1]
                        		else:
                        			if self.selected:
                        				last_selected = self.selected[-1]
                        				if not self.is_neighbor(tile, last_selected):
                        					self.selected = []
                        				self.selected.append(tile)
                        			else:
                        				self.selected = [tile]
                        		for tile in self.tiles:
                        			tile.selected =  (tile in self.selected)
                        		self.play_sound('8ve:8ve-tap-resonant')
                        		self.update_word_label()
                        	
                        	def update_word_label(self):
                        		word = self.get_selected_word()
                        		if word != self.current_word:
                        			self.word_label.color = '#ddffc2' if word in self.words else '#ffcece'
                        			self.word_label.text = word.upper()
                        			self.current_word = word
                        	
                        	def calc_score(self, word_tiles):
                        		n = len(word_tiles)
                        		multiplier = 1
                        		for tile in word_tiles:
                        			multiplier *= tile.multiplier
                        		return int((2 ** (n-2)) * 50 * multiplier)
                        	
                        	def submit_word(self):
                        		word = self.get_selected_word()
                        		if not word:
                        			return
                        		if word in self.words:
                        			added_score = self.calc_score(self.selected)
                        			self.score += added_score
                        			self.score_label.text = str(self.score)
                        			self.play_sound('digital:PowerUp7')
                        		else:
                        			added_score = 0
                        			self.play_sound('digital:PepSound4')
                        			for tile in self.selected:
                        				tile.selected = False
                        			self.selected = []
                        			self.touched_tile = None
                        		for tile in self.selected:
                        			tile.run_action(A.group(A.fade_to(0, 0.25), A.scale_to(0.5, 0.25)))
                        		self.tiles[:] = [t for t in self.tiles if t not in self.selected]
                        		sorted_selection = sorted(self.selected, key=lambda t: t.y, reverse=True)
                        		new_tiles_by_col = [0] * cols
                        		offsets = [0] * len(self.tiles)
                        		for t in sorted_selection:
                        			x, y = t.x, t.y
                        			new_tiles_by_col[x] += 1
                        			for i, tile in enumerate(self.tiles):
                        				if tile.x == x and tile.y > y:
                        					tile.y -= 1
                        					offsets[i] += 1
                        		for i, offset in enumerate(offsets):
                        			if offset > 0:
                        				tile = self.tiles[i]
                        				d = sqrt(offset * tile_size/750.0)
                        				tile.run_action(A.move_by(0, -offset*tile_size, d, TIMING_EASE_IN_2))
                        		for i, n in enumerate(new_tiles_by_col):
                        			for j in range(n):
                        				s = self.create_tile(i, rows-j-1)
                        				to_pos = s.position
                        				from_pos = to_pos[0], (rows + n-j) * tile_size
                        				s.position = from_pos
                        				self.tiles.append(s)
                        				self.root.add_child(s)
                        				s.alpha = 0
                        				s.run_action(Action.fade_to(1, 0.25))
                        				s.run_action(Action.move_to(to_pos[0], to_pos[1], sqrt((from_pos[1] - to_pos[1])/750.0), TIMING_EASE_IN_2))
                        		if added_score > 0:
                        			self.show_points(self.selected[-1].position, added_score)
                        		self.selected = []
                        		self.touched_tile = None
                        		self.update_word_label()
                        		
                        	def show_points(self, position, added_score):
                        		points_bg = ShapeNode(ui.Path.oval(0, 0, 100, 100), '#49b8ff', alpha=0)
                        		points_bg.position = position
                        		points_label = LabelNode('+%i' % (added_score,), font=points_font, parent=points_bg)
                        		points_bg.run_action(A.sequence(
                        			A.fade_to(1, 0.25),
                        			A.wait(0.5),
                        			A.fade_to(0, 0.5, TIMING_EASE_IN),
                        			A.remove()
                        		))
                        		points_bg.run_action(Action.move_by(0, 100, 1.5))
                        		self.root.add_child(points_bg)
                        	
                        	def touch_began(self, touch):
                        		if self.game_over:
                        			return
                        		prev_touched_tile = self.touched_tile
                        		self.touched_tile = self.tile_at(touch.location)
                        		if prev_touched_tile == self.touched_tile:
                        			self.submit_word()
                        		elif self.touched_tile:
                        			self.select_tile(self.touched_tile)
                        	
                        	def touch_moved(self, touch):
                        		if not self.game_over:
                        			self.select_tile(self.tile_at(touch.location))
                        	
                        	def touch_ended(self, touch):
                        		if self.game_over:
                        			if time.time() - self.game_over_time > 2.0:
                        				self.new_game(animated=True)
                        			return
                        		tile = self.tile_at(touch.location)
                        		if tile == self.touched_tile:
                        			self.select_tile(tile)
                        		else:
                        			self.submit_word()
                        	
                        	def play_sound(self, name):
                        		sound.play_effect(name)
                        
                        if __name__ == '__main__':
                        	run(Game(), multi_touch=False)
                        	
                        
                        
                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post
                        Powered by NodeBB Forums | Contributors