Forum Archive

Help building a touch menu

techteej

I wanted to build a menu to execute a script, but I keep getting this error. Problem being, I don't have a line 570 in the program I am trying to launch. The code for the file I am trying to launch can be found here. I didn't modify Pythonista either.

from scene import *
import sys 
import console

class MyScene (Scene):
    def setup(self):
        self.x = self.size.w * 0
        self.y = self.size.h * 0

    def draw(self):
        background(0.40, 0.80, 1.00) # light blue background color

        fill(0.50, 1.00, 0.00) # play button fill color
        self.playpx, self.playpy = (320, 350)
        self.playsx, self.playsy = (358, 100)
        rect(self.playpx, self.playpy, self.playsx, self.playsy) # play button rectangle

        fill(1.00, 0.00, 0.00) # quit game button fill color
        self.quitpx, self.quitpy = (320, 200)
        self.quitsx, self.quitsy = (358, 100)
        rect(320, 200, 358, 100) # quit game button rectangle

        tint(1.00, 1.00, 1.00) # white text color
        text('Play Game', font_name='AvenirNext-Heavy', font_size=60.0, x=500.0, y=400.0)
        text('Quit Game', font_name='AvenirNext-Heavy', font_size=60.0, x=500.0, y=252.0)
        text('Hockey', font_name='Avenir-Medium', font_size=160.0, x=510.0, y=600)

        fill(1.00, 0.80, 0.40) # stick color
        rect(70, 10, 40, 400) # left stick
        rect(70, 10, 100, 40) # left stick

        rect(900, 10, 40, 400) # right stick 
        rect(840, 10, 70, 40) # right stick

        fill(1.00, 0.00, 0.00) # fill color for red stripe details
        rect(70, 360, 40, 10)  # left stick red 1
        rect(70, 320, 40, 10) # left stick red 2
        rect(900, 360, 40, 10) # right stick red 1
        rect(900, 320, 40, 10) # right stick red 2

        fill(1.00, 1.00, 1.00) # fill color for white tripe details
        rect(70, 340, 40, 10) # left stick white
        rect(900, 340, 40, 10)# right stick white

    def check(self, x, y, posx, posy, sizex, sizey):
        if x >= posx and x <= posx + sizex:
            if y >= posy and y <= posy + sizey:
                return True
        return False

    def check_quit(self, x, y):
        return self.check(x, y, self.quitpx, self.quitpy,
                          self.quitsx ,self.quitsy)

    def check_play(self, x, y):
        return self.check(x, y, self.playpx, self.playpy,
                          self.playsx ,self.playsy)

    def touch_ended(self, touch):
        x, y = touch.location
        if self.check_quit(x, y): raise SystemExit()
        if self.check_play(x, y): execfile('air-hockey.py')


run(MyScene())
ccc

[Errno 2] No such file or directory: 'air-hockey.py'

That is the error that I get.

JadedTuna

@ccc, He probably want's to execute air-hockey when user presses on "Play Game", but he doesn't know how to setup the menu.

JadedTuna

@tjferry, I am not sure, what was your error, as you didn't post the traceback. But anyway, I've made your menu work if you need it: http://www.randomgalaxy.com/programming/python/forums/omz-pythonista/air-hockey-menu.txt

techteej

I get the error that AttributeError: 'HockeyScene' object has no attribute 't', you can tell that by changing air-hockey.py to any other file that uses the scene module.

JadedTuna

@tjferry14, can you please put full traceback, like on what line, etc. And btw, I don't see class HockeyScene same as variable t usage somewhere...

techteej

@shadowslayer Sorry. The HockeyScene is in the program I am trying to launch. Here is the full traceback.

https://www.flickr.com/photos/99736631@N02/13412392113/

Problem being, I don't have a line 570 in the program I am trying to launch. I didn't modify Pythonista either. Thanks for your menu as well. Updated the main question script with your code.

JadedTuna

@tjferry14, Oh, I see... Btw, can I take a look at the source code for HockeyScene? Maybe you modified something, but didn't noticed. At least, I've never ran into such a traceback before...

ccc

You might want to look at the undocumented scene.Button class for detecting clicks in text blocks: http://omz-forums.appspot.com/pythonista/post/6403240845377536

There has been a lot written in this Forum about the inability to run a second scene once you have called scene.run() a first scene. If you look again at error above, it is complaining about line 570 in the file scene.py, not your app. You might search this forum for "multiscene" to get a sense of one workaround to the scene in a scene issue.

In particular, http://omz-forums.appspot.com/pythonista/post/4657033039052800 is close to your issue.

techteej

@ShadowSlayer the code for the Hockey Scene can be found here: https://gist.github.com/gillibrand/3271073

@ccc I had a feeling I would have to use a multi scene, but I thought that just launching the file might be simpler/faster. Thanks for your help.

techteej

Do you think I might be able to get some help with Multi scene? Or perhaps a more efficient way of doing this? I've tried but cannot seem to get anything from the examples.

JadedTuna

@tjferry14, I can say nothing but what @ccc said. You should probably use multiscene. But I've never heard of it before, so I can't help you with this.

Good luck with it!

techteej

So, I implemented the air hockey code into the multi scene examples, and I am still presented with the error from my first post.

Quite frustrating.

ccc

You need to have the menu app and the hockey app use the SAME multiscene. If you are calling scene.run() twice, it will not work.

techteej

from scene import *
from sound import *
from copy import copy
from math import modf, floor
import time

screen_size = Size()

class Player (object): # Player object...
    def __init__(self, name, color): # define the player color
        self.color = color # set color as the color of player
        self.score = 0 # set score as zero 
        self.name = name # set name as the name as the player

    def __str__(self):
        return self.name

class Puck (object): # Puck object
    def __init__(self, pos, scene): # define the puck
        self.vector = Vector3(0, 0, 0) # set the puck as a vector
        self.pos = pos # set the puck position

    def reverse_vector(self):
        v = self.vector
        self.vector = Vector3(-v.x, -v.y, 0)

class Game (Scene):
    def setup(self):
        self.left_player = Player("Green Player", Color(0.50, 1.00, 0.00)) # sets green player color
        self.right_player = Player("Gold Player", Color(1.00, 0.80, 0.40)) # sets gold player color
        self.players = (self.left_player, self.right_player) # sets players in left corner and right corner

        self.start_time = 0.0
        self.stop_time = 0.0
        self.last_seconds = 0
        self.running = False

        self.start_time = time.time()
        self.running = True

        for s in ('Drums_06', 'Woosh_1', 'Powerup_2'): # for these sound effects...
            load_effect(s) # load the sound effects in

        center = self.size.w / 2 # sets center as center of the screen
        middle = self.size.h / 2 # sets middle as middle of the screen

        gh = self.size.h / 4 # sets goal height

        self.puck_radius = gh / 2.5 # sets puck size/radius
        self.puck = self.centered_puck() # makes puck start out centered
        # the last time the puck was hit; Used to animate its slow down
        self.puck_start_t = 0 # sets puck start to the dead center of the screen 
        pr = self.puck_radius # set pr to puck radius

        self.line_width = self.puck_radius / 6 # sets puck radius inside the center line 
        lw = self.line_width # sets lw to line width

        self.red_line = Rect(center - lw / 2, 0, lw, self.size.h) # sets red center line

        self.red_circle = Rect(w=gh, h=gh) # sets circle in the red center line
        self.red_circle.center(self.bounds.center()) # set the size of the circle in the center line

        self.left_goal = Rect(w=gh, h=gh) # sets left goal as a rectangle
        self.right_goal = Rect(w=gh, h=gh) # sets right goal as a rectangle 
        self.left_goal.center(0, middle) # sets the left goal to be centered 
        self.right_goal.center(self.size.w, middle) # sets the right goal to be centered

        self.winner = None # when someone reaches seven goals, save them to show a message

    def score_goal(self, player):
        player.score += 1 # sets player score to plus one for backend purposes

        self.puck = self.centered_puck() # sets up variable for the puck being centered

        # following block of code sets the score so when a player reaches 7 they win
        for p in self.players:
            if p.score > 6:
                self.winner = p
                break

        overlay = Layer(self.bounds) # sets the cover as an overlay, so its not behind the ice
        def hide_overlay():
            overlay.animate('alpha', 0.0, 1, completion=lambda: overlay.remove_layer())

        if self.winner:
            message = "%s Wins" % (self.winner) # shows a message for whatever player wins
            on_completion = None
            self.hide_overlay = hide_overlay
        else: 
            message = "Goal!" # shows a message for when a player scores a goal
            on_completion = hide_overlay
            self.stop_time = time.time()
            self.running = True
            # restart the puck on the other player's side
            if player == self.left_player:
                self.puck.pos.x += self.size.w / 4 + self.puck_radius
            else:
                self.puck.pos.x -= self.size.w / 4 + self.puck_radius

        size = self.puck_radius * 1.0 # sets puck radius
        text_layer = TextLayer(message, 'AvenirNext-Heavy', size) # sets text font to Futura
        text_layer.frame.center(self.bounds.center()) # sets the text to display from the center
        text_layer.frame.y += self.size.h / 4 # sets the text to autosize itself
        text_layer.animate('scale_x', 1.3, 0.3, autoreverse=True) # sets the x animation display time/size
        text_layer.animate('scale_y', 1.3, 0.3, autoreverse=True) # sets the y animation display time/size
        overlay.add_layer(text_layer) # sets the text layer to overlay

        #following code sets color, etc. for when a player scores a goal
        start_color, end_color = copy(player.color), copy(player.color)
        start_color.a = 0
        end_color.a = .5
        overlay.background = start_color
        overlay.animate('background', end_color, .10, completion=on_completion)
        self.add_layer(overlay)
        play_effect('Woosh_1') # sets effect of puck scoring to Woosh

    def centered_puck(self): # set center
        pos = Rect(w=self.puck_radius, h=self.puck_radius) 
        pos.center(self.bounds.center())
        return Puck(pos, self)

    def move_puck(self): # this lets the puck move by touching it once, like a real air hockey game
        puck = self.puck

        ease_x = (self.t - self.puck_start_t) / 4.0
        ease = max(0, 1 - curve_ease_out(ease_x))

        puck.pos.x += puck.vector.x * ease 
        puck.pos.y += puck.vector.y * ease

        if puck.pos.right() > self.size.w and not puck.pos.intersects(self.right_goal): # if puck hits the side, play the drum sound effect
            puck.vector.x *= -1
            play_effect('Drums_06') 
            puck.pos.x = min(self.size.w - puck.pos.w, puck.pos.x)

        if puck.pos.left() > self.size.w: # sets the left goal
            self.score_goal(self.left_player)
            return

        if puck.pos.left() < 0 and not puck.pos.intersects(self.left_goal):
            puck.vector.x *= -1
            play_effect('Drums_06')
            puck.pos.x = max(0, puck.pos.x)

        if puck.pos.right() < 0: # sets the right goal
            self.score_goal(self.right_player)
            return

        if puck.pos.top() > self.size.h or puck.pos.bottom() < 0:  # if puck hits the side, play the drum sound effect 
            puck.vector.y *= -1
            play_effect('Drums_06')
            puck.pos.y = max(0, puck.pos.y)
            puck.pos.y = min(self.size.h - puck.pos.h, puck.pos.y)


    def draw(self):
        stroke_weight(self.line_width)
        background(1, 1, 1)

        # red lines on ice in middle
        stroke(1.00, 0.40, 0.40)
        fill(1.00, 0.40, 0.40)
        rect(*self.red_line.as_tuple())
        fill(1, 1, 1)
        ellipse(*self.red_circle.as_tuple())

        # red goal markers
        for goal in (self.left_goal, self.right_goal):
            rect(*goal.as_tuple())

        for touch in self.touches.values():
            self.handle_touch(touch)

        # black puck
        no_stroke()
        fill(0, 0, 0)
        ellipse(*self.puck.pos.as_tuple())

        self.draw_scores()

        self.move_puck()

        if self.root_layer:
            self.root_layer.update(self.dt)
            self.root_layer.draw()

        if self.winner is not None: # shows message/allows player to tap to play again
            c = Rect()
            c.center(self.bounds.center())
            tint(1, 1, 1)
            message = "Tap to Play Again"
            y_offset = self.size.h / 4
            size = self.puck_radius
            text(message, x=c.x, y=c.y-y_offset, font_size=size, font_name='AvenirNext-Heavy')


    def draw_scores(self): # sets score font/size
        for p, x in zip(self.players, (50, self.size.w - 50)):
            tint(*p.color.as_tuple())
            text(str(p.score), x=x, y=self.size.h- 50, font_size=32, font_name='AvenirNext-Heavy')
            tint(0.00, 0.50, 1.00)
            #Format the elapsed time (dt):
            dt = 0.0
            if self.running:
                dt = (time.time() - self.start_time)
            else:
                dt = (self.stop_time - self.start_time)
            minutes = abs(dt) / 60 # absolute value of dt divided by 60
            seconds = abs(dt) % 60 # absolute value times percentage of 60 
            centiseconds = modf(abs(dt))[0] * 100 
            s = '%02d:%02d.%02d' % (minutes, seconds, centiseconds)
            text(s, x=510, y=700, font_size=34, font_name='AvenirNext-Heavy')

    def handle_touch(self, touch):
        if self.winner:
            # check for winner overlay and clear.
            # Overlays to restart game to play again
            self.winner = None
            self.hide_overlay()
            del self.hide_overlay
            self.left_player.score = 0
            self.right_player.score = 0

            # stops the clock
            self.stop_time = time.time()
            self.running = False

            # resets the clock
            self.start_time = 0.0
            self.last_seconds = 0
            self.stop_time = 0.0

            # starts the clock  
            self.start_time = time.time()
            self.running = True

            return

        tx = touch.location.x # sets x touch location...
        ty = touch.location.y # sets y touch location...
        finger = Rect(tx - 20, ty - 20, 40, 40) # sets touch location for finger size

        if finger.intersects(self.puck.pos):
            dx = tx - touch.prev_location.x
            dy = ty - touch.prev_location.y

            # Richochet for unmoving finger, to make it like a real air hockey game
            if abs(dx) < 5 and abs(dy) < 5:
                self.puck.reverse_vector()
                # Slide puck out from the finger. No puck holding.
                if dx == 0: dx = self.puck.vector.x
                if dy == 0: dy = self.puck.vector.y
                if not any([dx, dy]): return
                while finger.intersects(self.puck.pos):
                    self.puck.pos.x += dx
                    self.puck.pos.y += dy
            else:
                self.step = 0
                self.puck_start_t = self.t
                self.puck.vector = Vector3(dx * .8, dy * .8, 0)
                self.puck.pos.x += dx
                self.puck.pos.y += dy


class Start (Scene):
    def draw(self):
        background(0.40, 0.80, 1.00) # light blue background color

        fill(0.50, 1.00, 0.00) # play button fill color
        rect(320, 350, 358, 100) # play button rectangle

        tint(1.00, 1.00, 1.00) # white text color
        text('Play Game', font_name='AvenirNext-Heavy', font_size=60.0, x=500.0, y=400.0)

    def touch_ended(self, touch):
        main_scene.switch_scene(Game)


class GameOver (Scene):
    def setup(self):
        self.button = Button(Rect((screen_size.w/2)-100, (screen_size.h/2)-50, 200, 100), 'RESTART')
        self.button.action = self.restart
        self.add_layer(self.button)

    def restart(self):
        main_scene.switch_scene(Game)

    def draw(self):
        background(0,0,0)
        self.button.draw()
        no_tint()
        text('Your score was: {0}'.format(score), x=screen_size.w/2, y=screen_size.h-50, font_size=24)


class MultiScene (Scene):
    def __init__(self, start_scene):
        self.active_scene = start_scene()

    def switch_scene(self, new_scene):
        self.active_scene = new_scene()
        self.setup()

    def setup(self):
        global screen_size
        screen_size = self.size
        self.active_scene.add_layer = self.add_layer
        self.active_scene.size = self.size
        self.active_scene.setup()

    def draw(self):
        self.active_scene.touches = self.touches
        self.active_scene.draw()

    def touch_began(self, touch):
        self.active_scene.touch_began(touch)

    def touch_moved(self, touch):
        self.active_scene.touch_moved(touch)

    def touch_ended(self, touch):
        self.active_scene.touch_ended(touch)

main_scene = MultiScene(Start)
run(main_scene)
techteej

Not sure why this isn't working, as I'm not calling the run twice.

Sebastian

It should work if you change your MultiScene class to this:

class MultiScene (Scene):
    def __init__(self, start_scene):
        self.active_scene = start_scene()

    def switch_scene(self, new_scene):
        self.active_scene = new_scene()
        self.setup()

    def setup(self):
        global screen_size
        screen_size = self.size
        self.active_scene.add_layer = self.add_layer
        self.active_scene.size = self.size
        self.active_scene.bounds = self.bounds
        self.active_scene.root_layer = self.root_layer
        self.active_scene.setup()

    def draw(self):
        self.active_scene.touches = self.touches
        self.active_scene.t = self.t
        self.active_scene.draw()

    def touch_began(self, touch):
        self.active_scene.touch_began(touch)

    def touch_moved(self, touch):
        self.active_scene.touch_moved(touch)

    def touch_ended(self, touch):
        self.active_scene.touch_ended(touch)
techteej

@Sebastian, your solution works fine, but the overlays don't show their color for goals. They show up, but with a white background. Same with the game over layer.

techteej

1 week later and I'm still scratching my head.

ccc

Two things to try...

In the draw() method of "sub-scenes", instead of calling background(r, g, b) try calling main_scene.background(r, g, b)... I am not sure if it will work but it is worth a try.

If that does not work, you could try putting your overlays directly onto main_scene instead of onto sub-scenes.

techteej

Nope, first method does not work. I'm gonna try putting the overlays directly onto main_scene then try.

techteej

How would I do that again? You'll have to excuse me, I am only a beginner.