Forum Archive

Struggling with touch and display of images

richwoodham

Hi,

I am pretty new to Python and coding generally, and I have been trying to learn by writing something in Pythonista that might help my daughter with her reading. The idea is that a word appears at the top of the screen (e.g. 'cow') and your score increases if you touch the corresponding picture on a 4 x 4 grid of various images.

I have been 90% there for a while now, but I'm struggling to deal properly with touch values and also to retain the grid of images after the first touch. My most recent efforts seem to have only made it worse (!), so I am turning to the forum in desperation...

I still haven't completely got my head around classes and methods, and I suspect this might be where I am going wrong. If anyone is able to take a look I would be very grateful. My code is as follows:

from scene import *
import Image
import random
import sound
import time

class MyScene (Scene):

        def setup(self):

                # Set up layer1 (where score and word will appear)
                self.root_layer = Layer(self.bounds)    
                self.layer1 = Layer(Rect(0, 200, self.size.w, self.size.h - 200))
                self.root_layer.add_layer(self.layer1)

                # Create black background
                background(0, 0, 0)

                # Setup screen
                self.w, self.h = self.size.w, self.size.h - 200
                self.col_width = self.w / 4
                self.row_height = self.h / 4

                # Create list of images and the word which will appear with each one.
                self.images = [('Ant', 'a n t'), ('Bear_Face', 'b e a r'), ('Bug', 'c a t e r p i l l a r'), ('Chicken', 'c h i c k e n'), ('Cow_Face', 'c o w'), ('Dog_Face', 'd o g'), ('Elephant', 'e l e p h a n t'), ('Fish', 'f i s h'), ('Frog_Face', 'f r o g'), ('Honeybee', 'b e e'), ('Pig_Face', 'p i g'),('Snail', 's n a i l'), ('Snake', 's n a k e'), ('Whale', 'w h a l e'), ('Tiger_Face', 't i g e r'), ('Rabbit_Face', 'r a b b i t')] * 2

                # Draw grid of images
                self.co_ords = []
                self.a = []
                self.rects = []
                for i in range(16):
                        x, y = (i % 4) * self.col_width, (i / 4) * self.row_height
                        self.a.append(x)
                        self.a.append(y)
                        self.co_ords.append(self.a)
                        self.a = []
                        self.rects.append( Rect( x, y, self.col_width, self.row_height ))
                for i in range(len(self.co_ords)):
                        image(self.images[i][0], self.co_ords[i][0], self.co_ords[i][1], self.col_width, self.row_height)

                # Pick random image
                self.random_image()

                # Set self.score to 0
                self.score = 0

                # Display word and score
                self.update_score()
                self.update_answer()

        def update_score(self):
                # display score
                text("Score: " + str(self.score), font_name='ChalkboardSE-Bold', font_size=24.0, x = self.w / 20, y = self.size.h - 100, alignment = 6)

        def update_answer(self):
                # display legible version of answer
                text(self.images[self.current_image][1], font_name='ChalkboardSE-Bold', font_size=72.0, x = self.w * 0.5, y = self.size.h - 100, alignment=5)

        def random_image(self):
                # choose a random image
                self.current_image = random.randint(0, len(self.images) - 1)

        def touch_ended(self, touch):           
                # check touch.location against list of self.rects
                for n, r in enumerate( self.rects ):
                        if touch.location in r:
                                # if touch.location is within correct image:
                                if n == self.current_image:
                                        sound.play_effect('Powerup_1')
                                        self.score += 1
                                        self.update_score()
                                        self.random_image()
                                        self.update_answer()
                                        return
                                # if touch.location is outside correct image:
                                if n != self.current_image:
                                        sound.play_effect('Error')
                                        self.score -= 1
                                        self.update_score()
                                        return

run(MyScene())
ccc
# converts an image name to a lowercase animal name with spaces between each letter
def animal_name(image_name):
    name = image_name.partition('_')[0].lower()
    name = { 'bug': 'caterpillar',
             'honeybee': 'bee' }.get(name, name)
    return ' '.join(name)

self.images = '''Ant Bear_Face Bug Chicken Cow_Face Dog_Face Elephant Fish
        Frog_Face Honeybee Pig_Face Snail Snake Whale Tiger_Face Rabbit_Face'''.split()
richwoodham

Thank you ccc for addressing the line I was most ashamed of!

ccc

I think it is ready for our little beta tester...

# See: http://omz-forums.appspot.com/pythonista/post/5883071282806784

import random
import scene
import sound

font_name = 'ChalkboardSE-Bold'

# convert an image name to a lowercase
# animal name with spaces between each letter
def animal_name(image_name):
    name = image_name.partition('_')[0].lower()
    name = { 'bug': 'caterpillar',
             'honeybee': 'bee' }.get(name, name)
    return ' '.join(name)

class MyScene(scene.Scene):
    def setup(self):
        # Setup screen with top 200 pixels reserved for text
        self.w, self.h = self.size.w, self.size.h - 200
        self.col_width = self.w / 4
        self.row_height = self.h / 4

        # Create a shuffled list of image names
        self.images = '''Ant Bear_Face Bug Chicken Cow_Face
            Dog_Face Elephant Fish Frog_Face Honeybee Pig_Face
            Snail Snake Whale Tiger_Face Rabbit_Face'''.split()
        random.shuffle(self.images)

        # Create a grid of image Layers
        for i in range(16):
            rect = scene.Rect((i % 4) * self.col_width,
                              (i / 4) * self.row_height,
                              self.col_width,
                              self.row_height)
            layer = scene.Layer(rect)
            layer.image = self.images[i]
            self.add_layer(layer)

        # Pick random image
        self.current_image = random.choice(self.images)

        # Set self.score to 0
        self.score = 0

    def different_image(self):
        new_image = random.choice(self.images)
        if new_image == self.current_image:
            return self.different_image()
        return new_image

    def draw(self):
        scene.background(0, 0, 0)
        self.root_layer.update(self.dt)
        self.root_layer.draw()
        self.draw_text()

    def draw_text(self):
        name = animal_name(self.current_image)
        # display legible version of answer
        scene.text(name, font_name=font_name, font_size=72.0,
             x = self.size.w * 0.5, y = self.size.h - 100, alignment = 5)
        # display score
        msg = "Score: %d" % self.score
        scene.text(msg, font_name=font_name, font_size=24.0,
             x = self.size.w / 20, y = self.size.h - 100, alignment = 6)

    def touch_ended(self, touch):
        # check touch.location against list of self.rects
        for layer in self.root_layer.sublayers:
            if touch.location in layer.frame:
                # if touch.location is within correct image:
                if layer.image == self.current_image:
                    sound.play_effect('Powerup_1')
                    self.score += 1
                    self.current_image = self.different_image()
                # if touch.location is outside correct image:
                else:
                    sound.play_effect('Error')
                    self.score -= 1
                return

scene.run(MyScene())
JonB

suggestion:

                    self.current_image = random.choice([im for im in self.images if im is not self.current_image])    

or possibly

                    self.current_image = random.choice(set([self.current_image]).symmetric_difference([self.images])
ccc

DRY... Nice. I added self.different_image() to the code above so you Don't Repeat Yourself.

I did like the symmetric_difference thing but went for readability (I must be spending too much time with golang!).

richwoodham

Fantastic - thank you. While my daughter learns her animal words, I will be studying your code and working out where I went wrong!

Phuket2

Guys, is using scene over the top for this app? I thought just using ui would be a smarter choice in this case. I know, it was asked how to solve a problem with scene. I thought the right response would be to use the ui instead. So much easier as a beginner. To extend the functionality will be so much easier in my opinion. I have written many flash card style learning programs to teach myself thai in the past, not in python. But still same concept.
Ok, this is why there is a forum, discussion. If I am wrong , it's ok. But that is what goes through my mind

ccc

@richwoodham, What is the name of this game? What should we call it?

richwoodham

@ccc My incredibly unimaginative working title was 'Image_ID', but I'll see if I can think of something better. Although you ought to have naming rights, as it was you who got it working! My daughter has been enjoying using it, and it's definitely helping her recognise letters and words, so thank you again for your help with it.

@Phuket2 I have experimented a little with ui in Editorial recently, and in hindsight I agree that using it for this app would have made sense. I think I originally went with scene because, compared to ui, I was able to make sense more easily of the Pythonista example scripts which used it. I realise now that ui would have done a lot of the work for me. I might have learned a little less, though.

Phuket2

@richwoodham, I understand what you mean about the learning curve. I have always leaned towards ui style interface programming. But in the old days on the mac 68000 series processors, I had to get my hands dirty with bitmaps. The computers were too slow! It was hard in the beginning, but it comes to you. Just practice and familiarity. Being so long out of the game, bitmaps scare me i guess, even though they shouldn't. But to be fair, even back in those days the Macintosh toolbox had the best graphic primitives built in.

ccc

I have been thinking about doing .ui based implementation of Image_ID to see if it would be much easier... If I get some hacktime this weekend, I will send you details. Can someone creative please suggest a more compelling name?

donnieh
  • Where's my Pet?
  • Creature Find
  • Friend Match
  • Hidding Friends
  • Zoo Creatures
  • Zoo Find
  • Animal Match
  • Still thinking...
JonB

scene seems like a perfectly valid implementation.... it is after all meant for things like games. perhaps a customized Layer that has the word as an attribute would make keeping track of the word easier.

another improvement idea... have a larger set of pictures/words, and when you get the correct answer, swap out the picture with a different one. my young kids quickly figured out that they only really had to read the first letter, and could guess the animal.

ccc

I tightened up the code and posted AnimalMatchScene.py.

If I get more hacktime, it will take a shot at creating an AnimalMatchView.

a customized Layer that has the word as an attribute would make keeping track of the word easier.

I initially thought the same way and created an AnimalLayer subclass. But then I realized that Layer already gives you that functionality if you can use the image name (Layer.image) as that word or to lookup that word (see: animal_name()). So I got removed AnimalLayer.

ccc

OK... We have two working versions: AnimalMatchScene in 71 lines vs. AnimalMatchView in 105 lines. These are not 100% equivalent implementations yet 34 lines of difference shows that ui apps require more code then scene apps (when not using .pyui files). Creating and positioning ui elements requires several lines of code per element. The counter argument is that ui views are more object oriented, easier to maintain, and more pleasing for users.

Run both and see which you (and your kids) like best. Send pull requests if you see a better approach. "Star" the repo if the code helps you in your Pythonista adventures

Phuket2

I would have thought for a flash card style game ui would be easier than Scene. As pointed out, more code, but simple positioning code. But to make a grid/content class , a Results bar class , maybe a status bar class and then a container class to bring it all together, many games could be derived, ultimately ending up in a lot less code. Example alphabet game, word game, simple puzzle game etc...
But we know what we know. I don't know that much.