My first attempt at a game - Dice

Sorry about that. Here is the code:

from pandac.PandaModules import NodePath, DirectionalLight, AmbientLight, VBase4, Vec4, Vec3
from pandac.PandaModules import OdeWorld, OdeBody, OdeMass, Quat, OdePlaneGeom, BitMask32
from pandac.PandaModules import OdeSimpleSpace, OdeJointGroup, OdeBoxGeom
from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import TextNode
from direct.task import Task
import random, sys, time

FRAMESKIP = 4

# Panda Render Window
class PandaApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # global things to do
        base.disableMouse()     # turn off internal camera control
        base.setBackgroundColor(0.2, 0.2, 0.2) # not that we care really, can't see it

        # std random startup stuff
        random.seed()

        # main physics world lives here, set the gravity to earth std
        self.myWorld = OdeWorld()
        self.myWorld.setGravity(0, 0, -9.81)

        # Create a space and add a contactgroup for our physics system
        self.space = OdeSimpleSpace()
        self.space.enable()
        self.space.setAutoCollideWorld(self.myWorld)
        self.contactgroup = OdeJointGroup()
        self.space.setAutoCollideJointGroup(self.contactgroup)

        # The surface table is needed for autoCollide
        self.myWorld.initSurfaceTable(1)
        self.myWorld.setSurfaceEntry(0, 0, 20, 3.8, 1.1, 0.9, 0.00001, 0.0, 0.002)

        # Load Sounds (notes) for dice models
        self.loadSounds()

        # load dice models and set them for dynamics
        self.LoadModels()

        # load the room we bounce in, set the physics floor and walls
        self.LoadRoom()
        
        # setup global collision system callback
        self.space.setCollisionEvent("ode-collision")
        base.accept("ode-collision", self.onCollision)

        # add a directional light
        dlight = DirectionalLight('dlight')
        dlight.setColor(VBase4(0.9, 0.9, 0.9, 1))
        dlnp = render.attachNewNode(dlight)
        dlnp.setHpr(0, -60, 0)
        render.setLight(dlnp)

        # and an ambient light too
        alight = AmbientLight('alight')
        alight.setColor(VBase4(0.15, 0.15, 0.15, 1))
        alnp = render.attachNewNode(alight)
        render.setLight(alnp)

        # camera coords
        self.cameraX = 0
        self.cameraY = -25
        self.cameraZ = 11
        self.cameraH = 0
        self.cameraP = -16
        self.cameraR = 0

        # fix the camera here
        self.camera.setPos(self.cameraX, self.cameraY, self.cameraZ)
        self.camera.setHpr(self.cameraH, self.cameraP, self.cameraR)

        # add keyboard controls
        self.accept('space', self.throwDice)
        self.accept('escape', sys.exit)
        self.accept('c', self.addCredits)
        self.accept('b', self.addBet)
         
        # setup some flags and things
        self.frame = 0
        self.computedWin = True
        self.addForce = False
        self.runSimulation = False
        self.inSimulation = False
        self.rotationRunning = False
        self.collision = False
        self.winlist = {}
        self.positionlist = {}
        self.prevlist = {}

        # money and such
        self.credits = 10
        self.bet = 1
        self.winnings = 0

        # wins per face
        self.ones = 0
        self.twos = 0
        self.threes = 0
        self.fours = 0
        self.fives = 0
        self.sixes = 0

        # setup the screen text displays and cute little dice button
        self.drawScreenText()

        # start everything and we are done with initialize
        taskMgr.doMethodLater(0.1, self.startRot, "StartRotation")
        taskMgr.doMethodLater(0.7, self.startGrav, "StartGravity")

    def loadSounds(self):
        f0 = loader.loadSfx("models/f.wav")
        g0 = loader.loadSfx("models/g.wav")
        b0 = loader.loadSfx("models/bb.wav")
        c0 = loader.loadSfx("models/c.wav")
        e0 = loader.loadSfx("models/eb.wav")
        f1 = loader.loadSfx("models/f1.wav")
        g1 = loader.loadSfx("models/g1.wav")
        b0 = loader.loadSfx("models/bb1.wav")
        c1 = loader.loadSfx("models/c1.wav")
        self.sounds = [f0, g0, c0, f1, g1, c1, f0, g0, c1 ]
        self.soundIndex = 0

    # Text on the Screen
    def drawScreenText(self):
        text = ""
        self.textObject = OnscreenText(text=text, \
                                       pos=(-1.05,-0.95), \
                                       scale = 0.10, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)
        text = "Credits:"
        self.CreditsText = OnscreenText(text=text, \
                                        pos=(-1.3, 0.9), \
                                        scale = 0.1, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "%d" % self.credits
        self.CreditsNum = OnscreenText(text=text, \
                                        pos=(-0.9, 0.9), \
                                        scale = 0.13, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "Bet:"
        self.BetText = OnscreenText(text=text, \
                                        pos=(.94, 0.9), \
                                        scale = 0.1, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "%d" % self.bet
        self.BetNum = OnscreenText(text=text, \
                                        pos=(1.14, 0.9), \
                                        scale = 0.13, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "Win: 0"
        self.textWin = OnscreenText(text=text, \
                                       pos=(.94,-0.95), \
                                       scale = 0.10, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)
        text = "Idle"
        self.textStatus = OnscreenText(text=text, \
                                       pos=(.94,-0.85), \
                                       scale = 0.04, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)

        # Add button
        buttonText = ("", "", "Throw", "")
        self.bDie = DirectButton(text = buttonText, \
                    geom = self.displayDie, \
                    geom_pos = (0,0,0), \
                    geom_scale = (0.1, 0.1, 0.1), \
                    geom_hpr = (0, 0, 0), \
                    relief = None, \
                    text_fg = (1, 1, 1, 1), \
                    text_scale = (0.3, 0.3), \
                    text_shadow = (0, 0, 0, 1), \
                    text_style = ScreenTitle, \
                    frameColor = (1, 1, 1, 1), \
                    borderWidth = (0.05, 0.05), \
                    #image = 'symbols\symbol_1.png', \
                    pos = (-1.2, 0, -.90), \
                    scale = 0.3, \
                    hpr = (0,0,0), \
                    command=self.throwDice)

    #start up the physics task
    def startGrav(self, task):
        self.startGravitySim()

    # spin the dice weirdly rotate task
    def startRot(self, task):
        self.startRotation()

    # add some credits to our pot
    def addCredits(self):
        self.credits = self.credits + 1
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

    # bet some credits on this spin
    def addBet(self):
        self.bet = self.bet + 1
        if self.bet > 5:
           self.bet = 1
        s = "%d" % self.bet
        self.BetNum.setText(s)

    # spin the models randomly
    def rotateModels(self, task):
        self.frame = self.frame + 1
        if self.frame >= FRAMESKIP:
           self.frame = 0
           for die in self.dice:
               x = random.randint(0,359)
               y = random.randint(0,359)
               z = random.randint(0,359)
               die[0].setHpr(x,y,z)
               die[3].setPosition(die[0].getPos(render))
               die[3].setQuaternion(die[0].getQuat(render))
        if self.collision == False:
           return Task.cont
        else:
           return Task.done

    # start the dice rotating
    def startRotation(self):
        if self.rotationRunning == False:
           self.rotationRunning = True
           self.inSimulation = False
           self.taskMgr.add(self.rotateModels, "rotateModels")

    # force (explode) the dice
    def forceDie(self):
        for i in range(0,9):
            body = self.dice[i][3]
            a = random.randint(-17000000,17000000)
            b = random.randint(-17000000,17000000)
            c = random.randint(10000000,33000000) # up is always big to overcome gravity
            body.setForce(Vec3(a, b, c))

    # do a throw
    def throwDice(self):
        if self.credits - self.bet < 0:
           return

        if self.computedWin == False:
           return

        self.computedWin = False

        self.textStatus.setText("Throw Dice")
        #print "THROW DICE"
        self.credits = self.credits - self.bet
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

        self.winnings = 0
        w = "Win: %d" % self.winnings
        self.textWin.setText(w)

        for i in range(0,9):
            self.prevlist[i] = (0.0, 0.0, 0.0)
        self.moving = 0
        self.forceDie()
        taskMgr.doMethodLater(0.2, self.evaluateWinList, "EvaluateWinList")

    def checkMotion(self):
        moving = False
        for d in self.winlist.keys():
            (l,m,n) = self.winlist[d]
            a = "%03.0f" % l
            b = "%03.0f" % m
            c = "%03.0f" % n
            
            (o,p,q) = self.prevlist[d]
            e = "%03.0f" % o
            f = "%03.0f" % p
            g = "%03.0f" % q

            if a != e:
               moving = True
            if b != f:
               moving = True
            if c != g:
               moving = True

        for d in self.winlist.keys():
            self.prevlist[d] = self.winlist[d]

        return moving

    # evaluate (count up faces) dice as they come to rest on the floor, a task that runs every frame
    def evaluateWinList(self, task):

        if self.checkMotion() == False:
           self.moving = self.moving + 1
        else:
           self.moving = 0
       
        ones = 0
        twos = 0
        threes = 0
        fours = 0
        fives = 0
        sixes = 0

        a = b = c = 0

        for d in self.winlist.keys():
            l, m, n = self.winlist[d]
            a = int(l+0.5)
            b = int(m+0.5)
            c = int(n+0.5)
            if b == 0 and c == -89:
               ones = ones + 1
            elif b == 0 and c == 0:
               twos = twos + 1
            elif b == 90:
               threes = threes + 1
            elif b < -88:
               fours = fours + 1
            elif b == 0 and c != 90:
               fives = fives + 1
            elif b == 0 and c == 90:
               sixes = sixes + 1

        if self.ones == ones and self.twos == twos and self.threes == threes \
           and self.fours == fours and self.fives == fives and self.sixes == sixes:

           t = ""
           if ones > 3:
              t = t + "%d ones " % ones
           if twos > 3:
              t = t + "%d twos " % twos
           if threes > 3:
              t = t + "%d threes " % threes
           if fours > 3:
              t = t + "%d fours " % fours
           if fives > 3:
              t = t + "%d fives " % fives
           if sixes > 3:
              t = t + "%d sixes " % sixes
           if ones > 0 and twos > 0 and threes > 0 and fours > 0 and fives > 0 and sixes > 0:
              t = t + "six sequence"
           self.textObject.setText(t)

        else:
            self.ones = ones
            self.twos = twos
            self.threes = threes
            self.fours = fours
            self.fives = fives
            self.sixes = sixes

        self.bDie["geom_hpr"] = (a,b,c)
        if self.moving > 5:
           taskMgr.doMethodLater(0.1, self.computeTotals, "compute total wins")
           return Task.done
        return Task.cont

    def computeTotals(self, task):
        self.textStatus.setText("Compute win")
        self.winnings = 0
        
        # five of a kind or greater pays bet * number of a kind
        if self.ones > 3:
           self.winnings = self.winnings + (self.ones * self.bet)
            
        if self.twos > 3:
           self.winnings = self.winnings + self.twos * self.bet

        if self.threes > 3:
           self.winnings = self.winnings + (self.threes * self.bet)
           
        if self.fours > 3:
           self.winnings = self.winnings + self.fours * self.bet
           
        if self.fives > 3:
           self.winnings = self.winnings + self.fives * self.bet
           
        if self.sixes > 3:
           self.winnings = self.winnings + self.sixes * self.bet

        if self.ones > 0 and self.twos > 0 and self.threes > 0 and self.fours > 0 and self.fives > 0 and self.sixes > 0:
           self.winnings = self.winnings + (2 * self.bet)

        w = "Win: %d" % self.winnings
        self.textWin.setText(w)

        self.credits = self.credits + self.winnings
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

        self.computedWin = True

        return Task.done

    # start the gravity/collision simulation        
    def startGravitySim(self):
        self.addForce = False
        if self.runSimulation == False:
           self.deltaTimeAccumulator = 0.0
           self.stepSize = 1.0 / 90.0
           self.runSimulation = True
           taskMgr.doMethodLater(0.1, self.simulationTask, "Physics Simulation")

    # callback for collisions of the dice
    def onCollision(self, entry):
        geom1 = entry.getGeom1()
        geom2 = entry.getGeom2()
        body1 = entry.getBody1()
        body2 = entry.getBody2()

        for np, geom, sound, body in self.dice:
            if geom == geom1 or geom == geom2:
               velocity = body1.getAngularVel()
               if velocity[0] > 2.0 and sound.status != sound.PLAYING:
                  sound.setVolume(velocity[0] / 80.0)
                  sound.play()
                  self.collision = True
       
    # run the gravity/collision simulation task
    def simulationTask(self, task):
        self.inSimulation = True
        self.space.autoCollide()
        #self.myWorld.quickStep(globalClock.getDt())
        self.myWorld.quickStep(0.016)
        i = 0
        for np, geom, sound, body in self.dice:
            self.positionlist[i] = np.getPos()
            self.winlist[i] = np.getHpr()
            i = i + 1
            if not np.isEmpty():
               np.setPosQuat(render, geom.getBody().getPosition(), Quat(geom.getBody().getQuaternion()))
               self.contactgroup.empty() # Clear the contact joints
        if self.runSimulation == True:
           return task.cont

    # set already existing models to standard places
    def setModels(self):
        i = 0
        z = 2.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.4, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1
        z = 3.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.0, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1
        z = 4.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.4, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1

    # build individual die, set mass, collision and physics
    def buildModel(self, x, y, modelDice):
        die = modelDice.copyTo(self.root)
        die.setPos(x, 0.0, y)
        die.setScale(0.25, 0.25, 0.25)
        diceBody = OdeBody(self.myWorld)
        mass = OdeMass()
        wt = random.randint(10000, 14240)
        mass.setBox(wt, 1, 1, 1)
        diceBody.setMass(mass)
        diceBody.setPosition(die.getPos(render))
        diceBody.setQuaternion(die.getQuat(render))
        diceBody.enable()
        diceGeom = OdeBoxGeom(self.space, 1, 1, 1)
        diceGeom.setCollideBits(BitMask32(0x00000001))
        diceGeom.setCategoryBits(BitMask32(0x00000001))
        diceGeom.setBody(diceBody)
        Sound = self.sounds[self.soundIndex]
        self.soundIndex = self.soundIndex + 1
        return die, diceGeom, Sound, diceBody

    # import the dice model base and build all nine
    def LoadModels(self):
        self.displayDie = loader.loadModel("models/die")    # for button
        self.displayDie.setDepthTest(True)
        self.displayDie.setDepthWrite(True)

        modelDice = loader.loadModel("models/die")
        self.root = render.attachNewNode("Root")
        self.dice = []

        y = 2.0
        for x in [-1.0, -0.4, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

        y = 3.0
        for x in [-1.0, 0.0, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

        y = 4.0
        for x in [-1.0, 0.4, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

    def LoadRoom(self):
        # put dice in a box, model only
        room = loader.loadModel("models/room")
        room.reparentTo(render)
        room.setScale(1, 2, 1)
        room.setPos(0, -1, 0)
        room.setTwoSided(True)

        # install physics - floor
        groundGeom = OdeBoxGeom(self.space, (2000, 2000, 1))
        groundGeom.setCollideBits(BitMask32( 0xffffffff))
        groundGeom.setCategoryBits(BitMask32(0xffffffff))

        # wall left
        wall0Geom = OdeBoxGeom(self.space, (1, 2000, 2000))
        wall0Geom.setPosition(-6.5,0,0)
        wall0Geom.setCollideBits(BitMask32(0xffffffff))
        wall0Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall right
        wall1Geom = OdeBoxGeom(self.space, (1, 2000, 2000))
        wall1Geom.setPosition(6.5,0,0)
        wall1Geom.setCollideBits(BitMask32(0xffffffff))
        wall1Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall back
        wall2Geom = OdeBoxGeom(self.space, (2000, 1, 2000))
        wall2Geom.setPosition(0,6.5,0)
        wall2Geom.setCollideBits(BitMask32(0xffffffff))
        wall2Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall behind cam
        wall3Geom = OdeBoxGeom(self.space, (2000, 1, 2000))
        wall3Geom.setPosition(0,-6.8,0)
        wall3Geom.setCollideBits(BitMask32(0xffffffff))
        wall3Geom.setCategoryBits(BitMask32(0xffffffff))

        # ceiling
        wall4Geom = OdeBoxGeom(self.space, (2000, 2000, 1))
        wall4Geom.setPosition(0,0,11)
        wall4Geom.setCollideBits(BitMask32(0xffffffff))
        wall4Geom.setCategoryBits(BitMask32(0xffffffff))
        


# MAIN - start everything
panda = PandaApp()
run()