A downloadable game

Ball Catcher V2

Project Description

A simple falling ball catching game with advanced (compare to original V1) , without using OOB. Use keyboard to control the catcher. Further, there are other types of balls either worth more points or give extra lives.

Assignment Rubrics

  • 4 points - use arrays and loops to programmatically move the falling objects (that means more than one falling object!)
  • 1 point - the catching object must move based on player input (using the same method as last time is fine)
  • 4 points - collision detection between the two objects now done with loops and arrays (distance or rectangular, depending on visuals)
  • 1 point - the falling objects respawns to a random location when caught or fall off the screen
  • Extra Credit (1 point) - display a score and design a more complex scoring system (eg. different point values, multipliers, etc.)
  • Extra Credit (2 points) - add hysteresis to the player controlled object and/or the score display (check out the first minute or so from this video to see what that might look like: Super Mario 64 Coin Count (Links to an external site.))

My Code

#Falling Object Game V2(PesudoVer)
import copy
#Create dict for normal ball
normalBallDict = {
    "posX":random(20,580), 
    "posY":-20, 
    "rad":15, 
    "speed":2.3,
    "ballType":"normal",
    "ballColor":"#ffffff",
    "score":10,
    "refreshInterval":200,
    "minimumRefreshInterval":30,
    "intervalIncrement":10,
    "acceleration":0.03,
    "accelerationIncrement":0.001,
}
#Create dict for slow big ball
slowBallDict = {
    "posX":random(30,570), 
    "posY":-30, 
    "rad":30, 
    "speed":0.1,
    "ballType":"big",
    "ballColor":"#ff6524",
    "score":100,
    "refreshInterval":600,
    "minimumRefreshInterval":60,
    "intervalIncrement":20,
    "acceleration":0.1,
    "accelerationIncrement":0.1,
}
#Create dict for fast w/ extra life ball
fastBallDict = {
    "posX":random(30,570), 
    "posY":-20, 
    "rad":10, 
    "speed": 10,
    "ballType":"fast",
    "ballColor":"#84f23f",
    "score":50,
    "refreshInterval":800,
    "minimumRefreshInterval":100,
    "intervalIncrement":30,
    "acceleration":0.2,
    "accelerationIncrement":0.5,
}
#Create Catcher's dict
catcher = {
    "posX":0,
    "posY":0,
    "acce":2, 
    "maxSpd":15,
    "curSpd":0, #define moving to left as "negative" speed, and vise versa
    "width":200,
    "height":20,
    "cornerRounding":10,
    "btmDistance":100,
    "yTolerance":10,
    "color":"#ffffff",
}
#Game Status, 0 = menu, 1 = in-game, 2 = failscreen (showScore)
gameStatus = 0
#Create list to store all ball's info
ballList = []
#Create some global vars for current playing match
gameInProgress = False
#gameInProgress = True #DEBUG
playerLifesConst = 5
playerLifes = playerLifesConst
trueScore = 0
displayedScore = 0
highestScore = 0
frameSinceGame = 0
normalBallRefreshInterval = normalBallDict['refreshInterval']
slowBallRefreshInterval = slowBallDict['refreshInterval']
fastBallRefreshInterval = fastBallDict['refreshInterval']
screenResolutionWidth = 600
screenResolutionHeight = 800
playerPressedKey = "nothingPressed" #have 3 type, nothing/left/right
def interfaceSelector():
    if gameStatus == 0:
        drawMenu()
    elif gameStatus == 1:
        drawGame()
    elif gameStatus == 2:
        drawScore()
        
        
def drawMenu():
    #TODO: make menu here, change gameStatus to 1 when player click play
    drawColorfulBackground()
    drawTitle()
    drawInteractiveButton("Play")
    
def drawColorfulBackground():
    cirRad = 50
    for column in range(screenResolutionWidth / cirRad + 2):
        for row in range(screenResolutionHeight / cirRad + 2):
            fill(frameCount % 255, (frameCount + column * cirRad) % 255, (frameCount - row * cirRad) % 255)
            circle(column * cirRad + frameCount % cirRad, row * cirRad + frameCount % cirRad, cirRad * 3)
    
def drawTitle():
    #f = loadFont("TheBomb-7B9gw.ttf")
    #textFont(f)                        #Nah, font won't work
    textSize(100)
    fill("#4f099e")
    text("Catcher", 150, 300)
    
def drawInteractiveButton(inputText):
    global gameStatus, trueScore, displayedScore
    #the box should be 400 x 100, on 300, 500
    if (mouseX < 500 and mouseX > 100 and mouseY < 550 and mouseY > 450):
        fill("#1483a8")
        rect(300, 500, 460, 130, 30, 30, 30, 30)
        fill("#3ccafa")
        textSize(100)
        text(inputText, 210, 520)
        
        if mousePressed:
            gameStatus = 1
            trueScore = 0
            displayedScore = 0
    else:
        fill("#3361a6")
        rect(300, 500, 400, 100, 20, 20, 20, 20)
        fill("#6ea2f0")
        textSize(80)
        text(inputText, 220, 520)
def drawGame():
    #TODO: mainGame goes here
    global ballList, gameInProgress, frameSinceGame, gameStatus, catcher, playerLifes, normalBallRefreshInterval, slowBallRefreshInterval, fastBallRefreshInterval
    
    #print("Draw Game Running", gameInProgress) #DEBUG
    
    if gameInProgress == False:
        #Initializing game here
        gameInProgress = True
        #ballList.clear() # Clear function in python is not working in Processing's python D: 
        #have to use some methods to find a workaround 
        
        #Reseting Game
        emptyList = []
        ballList = emptyList
        
        #add first ball to the game here
        newBallDict = copy.deepcopy(normalBallDict)
        ballList.append(newBallDict)
        
        
        playerLifes = playerLifesConst
        trueScore = 0
        displayedScore = 0
        
        #Init catcher's pos
        catcher['posY'] = screenResolutionHeight - catcher['btmDistance']
        catcher['posX'] = screenResolutionWidth / 2
        
        #ball's refresh interval reset
        normalBallRefreshInterval = normalBallDict['refreshInterval']
        slowBallRefreshInterval = slowBallDict['refreshInterval']
        fastBallRefreshInterval = fastBallDict['refreshInterval']
        print(normalBallRefreshInterval)
        
        #CountFrames since this match start
        frameSinceGame = 0
        
        
        
        
    else:
        frameSinceGame += 1
        
        #Go scoreBoard if failed
        if playerLifes <= 0:
            gameInProgress = False
            gameStatus = 2
            #gameStatus = 1 #test before code scoreboard
        
        #background(0)          #nah, I'm not gonna use black background anymore
        
        drawColorfulBackground()
        
        fill(40 ,40 ,40 , 200)
        rect(screenResolutionWidth / 2, screenResolutionHeight / 2, screenResolutionWidth, screenResolutionHeight)
        
        ballCreator()
        #print("ballCreator Runs!")
        
        ballIterator()
        #print("ballIterator Runs!")
        
        updateCatcher()
        #print("updateCatcher Runs!")
        updateUI()
        #print("updateUI Runs!")
def ballCreator():
    global ballList, normalBallRefreshInterval, slowBallRefreshInterval, fastBallRefreshInterval
    #when frame reached normal ball refresh interval 
    if (frameSinceGame % normalBallRefreshInterval) == 0:
        newBallDict = copy.deepcopy(normalBallDict)
        newBallDict['posX'] = random(20,580)
        ballList.append(newBallDict)
        #print("New normal ball added")
        normalBallRefreshInterval -= normalBallDict['intervalIncrement']
        if normalBallRefreshInterval <= normalBallDict['minimumRefreshInterval']:
            normalBallRefreshInterval = normalBallDict['minimumRefreshInterval']
            
    #when frame reached big ball refresh interval         
    if (frameSinceGame % slowBallRefreshInterval) == 0:
        newBallDict = copy.deepcopy(slowBallDict)
        newBallDict['posX'] = random(30,570)
        ballList.append(newBallDict)
        slowBallRefreshInterval -= slowBallDict['intervalIncrement']
        print("New slow ball added")
        if slowBallRefreshInterval <= slowBallDict['minimumRefreshInterval']:
            slowBallRefreshInterval = slowBallDict['minimumRefreshInterval']
            
    #when frame reached fast ball refresh interval         
    if (frameSinceGame % fastBallRefreshInterval) == 0:
        newBallDict = copy.deepcopy(fastBallDict)
        newBallDict['posX'] = random(30,570)
        ballList.append(newBallDict)
        fastBallRefreshInterval -= fastBallDict['intervalIncrement']
        print("New fast ball added")
        if fastBallRefreshInterval <= fastBallDict['minimumRefreshInterval']:
            fastBallRefreshInterval = fastBallDict['minimumRefreshInterval']
    
        
def ballIterator():
    global ballList, playerLifes, trueScore
    #Check collision, if not colliding, update position
    for ballElement in ballList:
        if checkBtmCollision(ballElement):
            playerLifes -= 1
            ballList.remove(ballElement)
        elif checkCatcherCollision(ballElement):
            if ballElement['ballType'] == "fast":
                playerLifes += 1
            trueScore += ballElement['score']
            #print("score added")
            ballList.remove(ballElement)
        else:
            updateBalls(ballElement) #Include drawing part
    
        
def checkBtmCollision(ballElement):
    #TODO: check is this ball colliding with Bottom or not, return boolean
    ballPosY = ballElement['posY']
    ballRad = ballElement['rad']
    
    if (ballPosY + ballRad) > screenResolutionHeight:
        #print("touch BTM")
        return True
    return False
    
def checkCatcherCollision(ballElement):
    #TODO: check is this ball colliding with catcher or not, return boolean 
    ballPosX = ballElement['posX']
    ballPosY = ballElement['posY']
    ballRad = ballElement['rad']
    
    #calculate Y-axis collision first, should between a range of tolerance
    upperBound = (screenResolutionHeight - catcher['btmDistance']) - (catcher['height'] / 2.0)
    lowerBound = upperBound + catcher['yTolerance']
    ballLowerBound = ballPosY + ballRad
    
    #then calculate X-axis collision
    leftBound = (catcher['posX'] - (catcher['width'] / 2.0))
    ballLeftBound = ballPosX - (ballRad * (2.0 / 3.0))
    rightBound = (catcher['posX'] + (catcher['width'] / 2.0)) 
    ballRightBound = ballPosX + (ballRad * (2.0 / 3.0))
    
    #print(ballRightBound, rightBound)
    if((ballRightBound < rightBound) and (ballLeftBound > leftBound)):
        #print("within x range")
        if ((ballLowerBound < lowerBound) and (ballLowerBound > upperBound)):
            #print("touched catcher")
            return True
    return False
def updateBalls(ballElement):
    #TODO: update all balls pos and other attributes
    ballElement['posY'] += ballElement['speed']
    ballElement['speed'] += ballElement['acceleration']
    fill(ballElement['ballColor'])
    circle(ballElement['posX'], ballElement['posY'], ballElement['rad'] * 2)
    
def detectInput():
    global playerPressedKey
    
    if keyPressed:
        #if keyCode == LEFT and keyCode == RIGHT #Ummmm, Processing can't handle keys pressed at the same time properly
        if keyCode == LEFT:
            playerPressedKey = "leftPressed"
        elif keyCode == RIGHT:
            playerPressedKey = "rightPressed"
    else:
        playerPressedKey = "nothingPressed"
    
    
def updateCatcher():
    #TODO: update catcher's pos
    global catcher
    
    detectInput()
    #print("detectInput Runs!", playerPressedKey)
    if playerPressedKey == "leftPressed":
        catcher['curSpd'] -= catcher['acce']
        #check if speed is higher than maxSpd
        if abs(catcher['curSpd']) > catcher['maxSpd']:
            catcher['curSpd'] = -1 * catcher['maxSpd']
        
    elif playerPressedKey == "rightPressed":
        catcher['curSpd'] += catcher['acce']
        #check if speed is higher than maxSpd
        if abs(catcher['curSpd']) > catcher['maxSpd']:
            catcher['curSpd'] = catcher['maxSpd']
            
    elif playerPressedKey == "nothingPressed":
        if abs(catcher['curSpd']) > catcher['acce']:
            if catcher['curSpd'] < 0:
                catcher['curSpd'] += catcher['acce']
            else:
                catcher['curSpd'] -= catcher['acce']
        else:
            catcher['curSpd'] = 0
    
    #Finally, update pos
    catcher['posX'] += catcher['curSpd']
    if (catcher['posX'] + catcher['width'] / 2.0) > screenResolutionWidth:
        catcher['posX'] = screenResolutionWidth - (catcher['width'] / 2.0)
        catcher['curSpd'] = 0
    elif (catcher['posX'] - catcher['width'] / 2.0) < 0:
        catcher['posX'] = catcher['width'] / 2.0
        catcher['curSpd'] = 0
    
    #print(catcher['curSpd'])
    #draw catcher here
    fill(catcher['color'])
    rect(catcher['posX'], catcher['posY'], catcher['width'], catcher['height'], 0, 0, catcher['cornerRounding'], catcher['cornerRounding'])
def updateUI():
    global displayedScore
    
    #update displayedScorefirst!
    if displayedScore < trueScore:
        displayedScore += 1
    
    textSize(30)
    fill(122, 255, 211)
    scoreText = ("Score: %i" % (displayedScore))
    text(scoreText, 0, 30)
    
    lifeRemainText = ("Life: %i" % (playerLifes))
    fill(238, 255, 112)
    text(lifeRemainText, 0, 60)
    
def drawScore():
    #TODO: draw scoreboard here
    global trueScore, displayedScore, highestScore
    
    #re-initialize scores when game is over
    drawScoreBoard()
    
    if trueScore > highestScore:
        highestScore = trueScore
    gameInProgress = False
    
def drawScoreBoard():
    drawColorfulBackground()
    
    fill(10, 10, 10, 200)
    rect(screenResolutionWidth / 2, screenResolutionHeight / 2, screenResolutionWidth - 60, screenResolutionHeight - 60, 10, 10, 10, 10)
    highScoreText = ("Highest Score: %i" % (highestScore))
    curScoreText = ("Your Score:        %i" % (trueScore))
    fill(255)
    
    textSize(45)
    text(highScoreText, 100, 300)
    text(curScoreText, 100, 380)
    drawInteractiveButton("Reset")
    
def setup():
    size(screenResolutionWidth, screenResolutionHeight)
    stroke(0)
    frameRate(60) #framebased physics, yay
    rectMode(CENTER)
    
def draw():
    interfaceSelector()


Screenshot



Simple Painter App Gameplay

Download

Download
application.windows64.zip 42 MB

Leave a comment

Log in with itch.io to leave a comment.