2015-12-15 01:49:27

Hi, I have just recently finished taking a python class and I wanted to create an audio game. I have played with pygame and pyaudiogame in the past, so I thought I would start there. I have played with pyaudiogame quite a bit, but I became frustrated with it because it is incomplete. So I am working with just pygame and I wanted to create a menu, but all though I found a lot on the web on the topic I kept getting lost because of the visual aspect that everyone based their code around.

So I am working with accessible output and pygame in 2.python 7. I use Jaws and would like to be able to create an accessible menu using pygame, but I need help. Can someone help me?

2015-12-15 03:57:34

Maybe.  I haven't used Pygame in a while.  I prefer Pygllet, but that is very much less friendly for new programmers even if it is architecturally better in the long run, and my big thing these days is C++ digital signal processing anyway.
Work out how to do the following things in order.  If you have already done some of these, that's fine; if you have trouble, ask because one of the Python people here can probably help:
Make accessible output 2 say something.
Make a Pygame window appear.
Say the pressed key.  Don't worry about modifiers, but if I press a then say a.  This will break some for punctuation, also don't worry about that.  All the screen reader output methods in everything do it without a bunch of extra steps that are not worth it here.
Get a counter incrementing and decrementing when you press the arrow keys.  Don't let it go below 0 or past a maximum value that you define.  Announce the changes to this counter.
Now you're at the point of doing a menu.  I suggest a menu class.  You'll have a list of strings (the options), a run function which starts running a little private Pygame loop and getting keys and incrementing and decrementing the counters.  When enter is pressed, you return the string for the option or the index of the option or whatever.  Alternatively (depending how much you know) you make the list of options a list of tuples where the first item is a string and the second item is a function to call and then call that when the user presses enter.  Whatever you can manage or need is fine.  It's about 30 to 40 lines and it's reusable.
And that's it.  If you need, I can type up a class with some functions that you call, but I honestly think working out how to do this yourself is ultimately more helpful.  This is the most basic method, but there are more advanced options.  Be aware only that this isn't necessarily sufficient forever (but it is for a good while).  This will work for a simple side-scroller where every interaction pauses things; this will not work for an online game where everything has to keep going because you can't pause everyone or where you need to implement something like a Swamp radar.  It's simple but it leads to a lot of code duplication and controls will not be mappable.  Get this far and if you care we can talk about more advanced options.

My Blog
Twitter: @ajhicks1992

2015-12-15 06:39:53

I've never used Pygame, though the underlying concepts for menu's should generally work the same between the two. Not sure how helpful it may be but I've put together an example with Pyglet and Pyttsx which is what I usually use, and included displayed text.

import pyglet
from pyglet.window import key
import pyttsx

class Test(pyglet.window.Window):
    def __init__(self):
        super(Test, self).__init__(640, 480, resizable=False, fullscreen=False, caption="Test")
        self.clear()

    #list for storing keyboard input
        self.key_input = []

    #initialize the Text to Speech engine
        self.tts = pyttsx.init()

    #disable tts loop to use our own loop
        self.tts.startLoop(False)

    #setup text display
        self.label = pyglet.text.Label('title screen',x=300,y=240)

    #load menu classes
        self.menus = {'titlescreen':titlescreen(self.tts),
                      'options':options(self.tts)}
    #set current menu variable
        self.state = 'titlescreen'

    #state the initial menu on start up
        self.tts.say('title screen')

    #schedule main update loop
        pyglet.clock.schedule_interval(self.update, .01)



    def update(self,dt):
    #update tts engine
        self.tts.iterate()
    #update menu states
        self.state = self.menus[self.state].update(self.key_input,self.state,self.label)
    #if menus return close, shutdown program
        if self.state == 'close':
            self.tts.endLoop()
            self.close()

    #purge key input at end of cycle
        self.key_input = []
    #draw screen
        self.draw()



#draw screen
    def draw(self):
        self.clear()
        self.label.draw()



#get key presses
    def on_key_press(self,symbol,modifiers):
        self.key_input.append(key.symbol_string(symbol) + " press")



#titlescreen menu class
class titlescreen(object):
    def __init__(self,tts):
    #set local TTS container
        self.tts = tts
    #current selected menu
        self.select = 0



    def update(self,key,state,label):
        if 'LEFT press' in key:
            self.select -= 1
        #keep self.select in menu bounds
            if self.select < 0:
                self.select = 1
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('options')
            #set new text label
                label.text = 'options'
            elif self.select == 1:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('quit')
            #set new text label
                label.text = 'quit'

        if 'RIGHT press' in key:
            self.select += 1
        #keep self.select in menu bounds
            if self.select > 1:
                self.select = 0
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('options')
            #set new text label
                label.text = 'options'
            elif self.select == 1:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('quit')
            #set new text label
                label.text = 'quit'


        if 'RETURN press' in key:
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('options menu selected')
                self.select = 0
                return 'options'
            elif self.select == 1:
                self.select = 0
                return 'close'

        return state



#options menu class
class options(object):
    def __init__(self,tts):
    #set local TTS container
        self.tts = tts
    #current selected menu
        self.select = 0



    def update(self,key,state,label):
        if 'LEFT press' in key:
            self.select -= 1
        #keep self.select in menu bounds
            if self.select < 0:
                self.select = 1
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('title screen')
            #set new text label
                label.text = 'title screen'
            elif self.select == 1:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('settings')
            #set new text label
                label.text = 'settings'

        if 'RIGHT press' in key:
            self.select += 1
        #keep self.select in menu bounds
            if self.select > 1:
                self.select = 0
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('title screen')
            #set new text label
                label.text = 'title screen'
            elif self.select == 1:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('settings')
            #set new text label
                label.text = 'settings'

        if 'RETURN press' in key:
            if self.select == 0:
            #if tts is speaking, interrupt it for new speech
                if self.tts.isBusy() == True:
                    self.tts.stop()
                self.tts.say('title screen menu selected')
                self.select = 0
                return 'titlescreen'

        return state



if __name__ == '__main__':
    window = Test()
    pyglet.app.run()
-BrushTone v1.3.3: Accessible Paint Tool
-AudiMesh3D v1.0.0: Accessible 3D Model Viewer

2015-12-15 12:11:54

Hello,
There's a game_utils Python module for simplified making of audio game stuff including menus. It hasn't been updated since 2013 though. It's made by Q-Continuum also known as Accessible Apps. It's using sound_lib, a simplified BASS library wrapper, also made by Q-Continuum, as well as accessible_output for speech output.
http://hg.q-continuum.net/game_utils/archive/tip.zip
Although I'm programming in Python by default, I decided to try making my games with BGT. Of course, both making games in Python and BGT has advantages and disadvantages.
Python syntax is far easier to read, but you have to care about indentation.
In BGT, game executables are smaller in size, and work faster. In Python, you will get a larger game distribution, and will work slower, especially if we talk about loading a game.
In Python, you can make cross-platform games with not too many modifications. In BGT, you can make only Windows games.
In BGT, you can easily pack and encrypt your sounds, which makes hackers much harder to get access to them. In Python, I think the only way to protect your sound files is by putting them inside the password-protected zip file, which can still be easier to hack, in my opinion.
Correct me if I'm wrong though.

2015-12-15 17:17:35

Accessible_output2 is far, far easier for TTS.  Also I don't think Pytts goes through the screen reader.  Still, the Pyglet way is the more advanced way I was talking about, and the more your game looks like that the better you can do in the long run.  Using either Pygame or BGT, you get an easier start, but will eventually begin duplicating parts of Pyglet yourself.  Namely this is the event handling.  We've had interesting threads on this before, though I don't have a link at the moment.
Be aware that Bass technically requires commercial licenses for commercial apps.  Violate this at your own risk.  You don't get much more than the Pygame or Pyglet audio engines offer though, and if you need more advanced stuff you can just go borrow SoundRTS's sound handling module.
Sticking to pure facts on BGT versus Python: Python has serialization that's not half-baked and which can save anything save open file handles in one line.  You can get packages for all the advanced BGT stuff like networking and pathfinding, and they will be able to do way more.  Also you can do DRM down the road when it matters; BGT is crackable and because it's all using the same DRM, once someone puts in the effort it's over.  Using someone else's DRM is nice and easy, but it's also a single point of failure for every audiogame ever.  Finally, barring Philip suddenly giving me the stuff I need, Libaudioverse will not be in BGT.  Libaudioverse is currently usable high-quality 3D audio and synthesis with environmental reverb and, at some point in the next couple months, the ability to read your tile maps.  Though admittedly I'm the author and it's GPL at the moment (I think that will be changing).

My Blog
Twitter: @ajhicks1992

2015-12-15 23:37:26

Thanks everyone. So I wrote out a class Menu(), but I can't get it working the way it should be. I am still a little new to using classes, but I understand how they work.

When I run my program it just runs game() and rules(), and says [start game, none]

Can someone help?

here is my code:

this is what I am passing in

                mainMenu = [["Start Game", game()], ["Start Rules", rules()], ["Exit", "Exit"]]
                main = Menu()
                main.run(mainMenu, "Main Menu")

class Menu():
        def run(self, items, title):
            #mainMenu = [["Start Game", game()], ["Rules", rules()], ["Exit", "exit"]]
            itemNumber = 0
            itemsLength = len(items)
            currentItem = items[itemNumber]
            closeMenu = False
            spk(title)
            spk(currentItem)
            while closeMenu == False:
                for event in pygame.event.get():
                    key = pygame.key.get_pressed()
                    if key[pygame.K_t]:
                        spk(title)
                    if key[pygame.K_RETURN] or key[pygame.K_ENTER]:
                        return currentItem[1]
                    if key[pygame.K_DOWN]:
                        itemNumber += 1   
                        spk(currentItem)
                    if key[pygame.K_UP]:
                        itemNumber -= 1
                        spk(currentItem)
                    if key[pygame.K_x]:
                        spk("closing menu")
                    closeMenu = True

2015-12-16 00:46:24

In order from most to least important, this is everything I see wrong with it.  Some of it is opinionated and some of it doesn't matter in terms of getting it running.  This is merely how I would fix and improve the code:
Drop the parentheses on the functions.  That's the first thing I see.  game() means the value of executing the function game, whereas game means the function game itself.
The counter can go past the end of the list or to negative values.  You need if statements that protect it from this case, otherwise you're going to blow up with IndexError if you start the menu and hit up.
Making the inner elements tuples (use () instead of []) is both more efficient and better documenting: tuples cannot be accidentally or purposefully modified in-place.
Stylistically, you might want to read PEP8.  Style doesn't matter, not really, but Python does go so far as to suggest a standard.  This may not matter to you; it matters to me.
Something is wrong with the indentation.  The forum is choosing to insert non-breaking spaces "for" you, which means that cutting and pasting this will probably blow up unless you remove them.  I have no idea why this is happening, but I'm totally about to go file against NVDA because we should be handling them like normal indentation.  Put another way, all software is hopelessly broken and welcome to the world of programming.

My Blog
Twitter: @ajhicks1992

2015-12-16 01:33:29 (edited by TJ.Breitenfeldt 2015-12-16 01:55:44)

All right, sorry, a couple things. First I did realize that my indentations were off, I was consistent so my program ran fine and I wasn't paying close attention. Second thanks for the tip for the functions I didn't know that. I Changed that in my list and I am still getting weird output. It is different though. Also, I did not right a condition for the case of the item number being larger or smaller yet. I did know that it was an issue, sorry for not making that clear in the previous post, I just wanted to get this working first.
Can you explain more about why i should use tuples?
Also, I actually try and follow Pep8 when I am righting nontesting python, but here I am just trying to get this working for my own knowledge base. What standards am I not following other than doc strings and comments? I have not read the standards for classes yet sinse I just learned them recently. I am probably going to do that right now.

I actually found a couple other errors too. I didn't include the index number when I was refering to the currentItem to access the sublist.
That actually helpd quite a bit, however now I am not able to change to the next item in the list.
My new output is something like
main menu start game. like it should be, but when I press down errow it does nothing, or it is just lagging a lot I noticed I was having a problem with lag earlier.

I was able to use a find and replace on my tabs to convert them to spaces to make it more readable to NVDA and Jaws. I forget that when pasting code into forms you have to account for tabs converting.

Also I forgot to mention a couple other things to clarify. The game() and rules() functions just output accessible_output, and the function spk I am using I created to simplify accessible_output.

def spk(text):
    speech.Speaker().output(text)

calling and passing in:
mainMenu = [["Start Game", game], ["Start Rules", rules], ["Exit", "Exit"]]
main = Menu()
main.run(mainMenu, "Main Menu")

class Menu():
    def run(self, items, title):
        itemNumber = 0
        itemsLength = len(items)
        currentItem = items[itemNumber]
        closeMenu = False
        spk(title)
        spk(currentItem[0])
        while closeMenu == False:
            for event in pygame.event.get():
                key = pygame.key.get_pressed()
                if key[pygame.K_t]:
                    spk(title)
                if key[pygame.K_RETURN]:
                    return currentItem[1]
                if key[pygame.K_DOWN]:
                    itemNumber += 1   
                    spk(currentItem[0])
                if key[pygame.K_UP]:
                    itemNumber -= 1
                    spk(currentItem[0])
                if key[pygame.K_x]:
                    spk("closing menu")
                    closeMenu = True

2015-12-16 03:31:11

So I did try using a tuple instead of a list to be passed into the function, and now it is reading start game every time I up or down arrow.
Can someone help me figure out why this is happening?

2015-12-16 05:51:46 (edited by magurp244 2015-12-16 06:03:55)

Hmmm, what you seem to be doing is playing the first item in the mainMenu list, but not actually updating it. When you set itemNumber += 1 your not actually using it to select the new current item, I think it should be more like this:

class Menu():
    def run(self, items, title):
        itemNumber = 0
        itemsLength = len(items)
        closeMenu = False
        spk(title)
        spk(items[itemNumber][0])
        while closeMenu == False:
            for event in pygame.event.get():
                key = pygame.key.get_pressed()
                if key[pygame.K_t]:
                    spk(title)
                if key[pygame.K_RETURN]:
                    return items[itemNumber][1]
                if key[pygame.K_DOWN]:
                    itemNumber += 1   
                    spk(items[itemNumber][0])
                if key[pygame.K_UP]:
                    itemNumber -= 1
                    spk(items[itemNumber][0])
                if key[pygame.K_x]:
                    spk("closing menu")
                    closeMenu = True
-BrushTone v1.3.3: Accessible Paint Tool
-AudiMesh3D v1.0.0: Accessible 3D Model Viewer

2015-12-16 06:15:35 (edited by TJ.Breitenfeldt 2015-12-16 06:19:10)

Thanks, this helped. I went back through and removed currentItem and instead called items[itemNumber][0] and it worked, well kind of. I am now able to have jaws output the name of each item, but only after pressing the down arrow multiple times. It is like the program is not recognizing the key press  at first. Is there a better way to design this so that it functions better?
Here is my new code.

class Menu():
    def run(self, items, title):
        itemNumber = 0
        itemsLength = len(items)
        closeMenu = False
        spk(title)
        spk(items[itemNumber][0])
        while closeMenu == False:
            for event in pygame.event.get():
                key = pygame.key.get_pressed()
                if key[pygame.K_t]:
                    spk(title)
                if key[pygame.K_RETURN]:
                    return items[itemNumber][1]
                if key[pygame.K_DOWN]:
                    itemNumber += 1   
                    spk(items[itemNumber][0])
                if key[pygame.K_UP]:
                    itemNumber -= 1
                    spk(items[itemNumber][0])
                if key[pygame.K_x]:
                    spk("closing menu")
                    closeMenu = True

2015-12-16 06:20:22

It might depend on at whivh point your pressing up, you don't have any code to keep itemNumber in bounds of the list. Try this and see if it helps:

                if key[pygame.K_DOWN]:
                    itemNumber += 1   
                    if itemNumber > len(items)-1:
                        itemNumber = 0
                    spk(items[itemNumber][0])
                if key[pygame.K_UP]:
                    itemNumber -= 1
                    if itemNumber < 0:
                        itemNumber = len(items)-1
                    spk(items[itemNumber][0])
-BrushTone v1.3.3: Accessible Paint Tool
-AudiMesh3D v1.0.0: Accessible 3D Model Viewer

2015-12-16 06:32:19

Yes now my program isn't crashing because I am not exceeding the item number in the tuple or list, but it is still requiring me to press down or up arrow several times before I hear the next item.

2015-12-16 06:52:26

I'm not all that familiar with the pygame event loop, so i'm not entirely sure what may be causing it. I've dug through a few examples so you could try writing it like this:

class Menu():
    def run(self, items, title):
        itemNumber = 0
        itemsLength = len(items)
        closeMenu = False
        spk(title)
        spk(items[itemNumber][0])
        while closeMenu == False:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_t:
                        spk(title)
                    if event.key == pygame.K_RETURN:
                        return items[itemNumber][1]
                    if event.key == pygame.K_DOWN:
                        itemNumber += 1   
                        if itemNumber > len(items)-1:
                            itemNumber = 0
                        spk(items[itemNumber][0])
                    if event.key == pygame.K_UP:
                        itemNumber -= 1
                        if itemNumber < 0:
                            itemNumber = len(items)-1
                        spk(items[itemNumber][0])
                    if event.key == pygame.K_x:
                        spk("closing menu")
                        closeMenu = True

If that doesn't work then someone more familiar with pygame may have to chime in, either that or i'd have to take some time to install and familiarize myself with it.

-BrushTone v1.3.3: Accessible Paint Tool
-AudiMesh3D v1.0.0: Accessible 3D Model Viewer

2015-12-16 07:09:32

I tried it, but it still isn't working the way it should. It still requires pressing the arrow keys multiple times for the menu to move to the next item.

2015-12-16 08:11:21

Hi,

This isbecause JAWS uses what we call a keyboard hook, which means it intercepts all key presses before letting them through to the currently focused application. I'm not sure if Pygame has a keyhook to fix this or not, but that's what you'll need for JAWS.

I've been going by Bryn, which is now reflected in my profile. Please do your best to respect this :) Mess ups are totally OK though, I don't mind and totally understand!
Follow on twitter! @ItsBrynify
Thanks, enjoy, thumbs up, share, like, hate, exist, do what ya do,
my website: brynify.me

2015-12-16 08:27:54 (edited by TJ.Breitenfeldt 2015-12-16 08:31:03)

All right, I looked and I don't believe that pygame has a keyboard hook. I did find a python module pyhook at

https://pypi.python.org/pypi/pyHook

I am not certain if this is what you were talking about. How would I go about using this library in my code?

I haven't heard of this before, is there another way that a menu can be created that doesn't require this? Is pygame my problem?

2015-12-16 08:34:57

I see what you are talking about. It seems to be a Jaws issue. NVDA reads the menu perfectly.

2015-12-18 23:50:26

Don't Swamp and SoundRTS work without a problem, though?  It's been forever since I've seriously used Jaws, and I don't even have it on my system anymore.  If this is truly an issue, I know how to duplicate the BGT hooks.  While I think the rest of it isn't worth using, if there's a legitimate need for them then I'll put out a library for it.  Should only be 50 lines, and everything lets you get the window handle, so it should work with all of them.  Yet another case in which jaws is beyond annoying and needs to be spoiled with special magic faerie dust support, go Freedom Scientific.
You can get around the indentation thing by using code tags, which I can't type because they're code tags.  Believe it's code in brackets to open, /code in brackets to close.  I've got a pull request against NVDA to do something sane for non-breaking spaces, so that should be much less of an issue for NVDA users in the near future.  Cut/pasting will not work right without code tags, though, as the non-breaking spaces invisibly pollute.

My Blog
Twitter: @ajhicks1992

2015-12-19 06:07:26

To clarify the possible issue, as you posted while I did:
Jaws is an invasive kudzu vine of horror.  Instead of just grabbing what it needs in a sane manner, it does a bunch of process injection and whatnot, and handles a lot of keys it shouldn't and in cases where it shouldn't.  If this is the issue here, you need a thing that makes Jaws stop hooking; if this is the issue here, then I will drop everything and go make a Python package for it, because this hits me as well as you and shouldn't be a difficult fix.  At least, it's not difficult if I know what's going on, and I think I do.
But I still pose the question: Swamp doesn't do this. SoundRTS doesn't do this.  If this is the issue, does that mean that Jaws users are just not playing those games?  Are they simply not complaining?

My Blog
Twitter: @ajhicks1992

2015-12-19 08:08:00

I vaguely remember giving up on using jaws entirely for sound rts and just using NVDA. I'm even considering not supporting jaws in any non-bgt games that I write. I looked up the keyboard hook functions, didn't understand the Microsoft documentation on them. Installing your own keyboard hooks also has other side effects if I'm not mistaken; e.g in the case of your program crashing with the hook installed.

2015-12-19 13:11:27

Hi,
That was a JAWS problem for ages. You really have to turn it off to play most of games, and implement either SAPI or recorded voice for game prompts.

2015-12-19 17:55:15

Okay, I will go fix this and make a package on Pypi for it.  It will take me a couple days, as we're sitting squarely in the Christmas holidays and I don't keep Jaws installed.
There are side effects if you crash with the hook installed globally, but you can limit hooks to one thread and/or one window.  All that should have to happen here is an installation of a keyboard hook that does nothing, probably about 20 lines of Python at most.  BGT is doing it without issue, though I do disagree with the approach: we shouldn't be "fixing" our screen reader in this incredibly hackish manner, we should be demanding that Jaws stop being as buggy as it is.
If there's interest in a C package, I can probably do that too: just a port of the Python code once the Python code works.

My Blog
Twitter: @ajhicks1992

2015-12-19 18:52:11

Yeah, I'd be interested in a c version.

Though I wonder how many people who play audiogames who use jaws actually needs this, because most people would just have another less buggy/broken screen reader installed.

2015-12-19 22:58:57

(Am I the only one who started out designing games to use the letters instead of the arrow keys? Then again, this was like 2004 and Jaws was all I had.)

看過來!
"If you want utopia but reality gives you Lovecraft, you don't give up, you carve your utopia out of the corpses of dead gods."
MaxAngor wrote:
    George... Don't do that.