Source code for levelWindow

#Standard libraries
from time import sleep,time
from math import sqrt,cos,sin,pi,copysign,radians,isnan
from random import random,randrange,choice,randint
from os.path import dirname, realpath
import os
import sys
import warnings
import traceback
import inspect

###########Calico Specific Libraries###############

#Simulation and graphic drawing
from Myro import *
from Graphics import *

#Libraries that allow access to Calico objects. 
#(May eventually migrate to C# code)
import Gdk 
import Cairo 

##########Developer Created Libraries##############

#Files within Common Functions that facility creating of game infrastucture
if __name__ == "__main__":
    sys.path.append(dirname(realpath(__file__))+"/../CommonFunctions")
    sys.path.append(dirname(realpath(__file__))+"/../GameObjects")
    sys.path.append(dirname(realpath(__file__))+"/../WindowObjects")
    from hazards import * 
    from interactables import *
    from events import *
    from spriteSheet import SpriteSheet

#Hack to allow the access of the global calico object
from setCalico import *
#Global functions made for the user. 
#Includes new robot commands like pickup() and use()
from userFunctions import *
#Helper functions for create text or buttons for panels.
from constructionFunctions import *
#TODO - Functions for smart formating of text and images. 
#Will refactor into a general fancyText object. JH
from briefingFunctions import *
# Use for quickly making shapes
from makeShapeFunctions import *

#Data structure that stores user data. 
from windowObjects import GameData,statObject
#Objects that will be eventually folded into C# code
from tempFrameObjects import MasterFrame,WorldFrame, LRC_Background

#Allows levelWindow to posses helper functions that create different game 
#objects
from actors import *
from conversation import *
from events import *
from interactables import Platform

#Path of images used through levelWindow
IMAGE_PATH=dirname(dirname(realpath(__file__)))+"/CommonImages/"
USE_FAILED="Nothing to use()."
PICKUP_FAILED="Nothing to pickup()."
from version import VER

[docs]class HelpPageWidget(): def __init__(self,panel,point1,point2,title,buttonInfo,tag,debug=False): self.parent=panel #shapes are drawn to the center of the panel so they must be #offset to the upper left corner (0,0) oX=self.parent.width/2 oY=self.parent.height/2 #main container that is drawn to the panel, #holds the titles, dividers, help buttons, etc self.display=Rectangle((point1[0]-oX,point1[1]-oY),(point2[0]-oX,point2[1]-oY)) if debug: self.display.fill=Color("red") else: self.display.fill.alpha=0 self.display.setWidth(0) self.display.draw(self.parent) self.display.tag=tag #do not confuse the display width and height with the #parent width and height self.width=self.display.width self.height=self.display.height self.margin = 5 #add title and divider line #note items are drawn directly to the shape self.title=createPanelText(self.display,self.margin,0,title,c=Color("black"),fS=18) currY=1.25*self.title.fontSize#self.title.height self.divider1=createPanelLine(self.display,(0,currY),(self.width,currY),c=Color("black")) #add buttons self.numRows=len(buttonInfo) self.maxCols=3 #for i in range(self.numRows): # if len(buttonInfo[i])>self.maxCols: # self.maxCols=len(buttonInfo[i]) #hard coding these dimensions for now #self.buttonW=self.width/self.maxCols self.buttonW=75#self.width/3 #self.buttonH=(self.height-currY)/self.numRows self.buttonH=25#(self.height-currY)/4 #dictionary that holds all the buttons #setButtonsVisible and setButtonsInvisible will use the #key value to hide and show buttons. #currently using a button's dispaly name as the key self.buttonAndText={} for i in range(self.numRows): for j in range(len(buttonInfo[i])): #if any field of buttonInfo is set to None don't add the button if buttonInfo[i][j][0] and buttonInfo[i][j][1] and buttonInfo[i][j][2]: [b,t]=createPanelButton(self.display,j*self.buttonW,currY+i*self.buttonH,self.buttonW,self.buttonH, buttonInfo[i][j][0],buttonInfo[i][j][1],None,Color(buttonInfo[i][j][2]), Color("black"),12) #I'm saving the text object along with the button #could simply just save the button if I wanted. RV self.buttonAndText[t.getText()]=[b,t] return self.display
[docs] def getClickedButton(self,x,y): #required to register the correct click location p=self.display.center cX=p[0] cY=p[1] for key,bt in self.buttonAndText.iteritems(): # JH: The offsets should no longer be required #print("Hit a button at ",x,y) if bt[0].hit(x,y): return bt[0].tag
[docs] def setButtonsInvisible(self,buttonDisplayNames=[]): if len(buttonDisplayNames)==0: for key,bt in self.buttonAndText.iteritems(): bt[0].visible=False else: for d in buttonDisplayNames: if d in self.buttonAndText: self.buttonAndText[d][0].visible=False
[docs] def flashButton(self): for b in self.newlyVisible: if b.visible: b.visible=False else: b.visible=True
[docs] def setButtonsVisible(self,buttonDisplayNames=[],numFlashes=0): if isinstance(buttonDisplayNames, str): buttonDisplayNames=list(buttonDisplayNames) self.newlyVisible=[] if len(buttonDisplayNames)==0: for key,bt in self.buttonAndText.iteritems(): bt[0].visible=True self.newlyVisible.append(bt[0]) else: for d in buttonDisplayNames: if d in self.buttonAndText: self.buttonAndText[d][0].visible=True self.newlyVisible.append(self.buttonAndText[d][0]) if numFlashes>0: #2 times numFlahes b/c the button with go invisible and then visible buttonFlashEvent = RepeatedActionEvent(0.4,numFlashes*2,self.flashButton) L=getLevelObject() L.addStepEvent(buttonFlashEvent)
[docs]class LevelWindow(LevelObject): """ The level window is the parent for all Scribbler Adventure levels. The level window is the object on which you can build a Scribbler Adventure level. To create a level you'll need to overwrite the create level function, and add statements to it that will build your level. """ def __init__(self, levelName="levelName", gameData=None, parent=None, debug=False): """ Creates a LevelWindow object. Args: levelName: The name of the level. gameData: Data that should be provided by the parent portal window. parent: A reference to the parent window (the calico app). debug: Set to True to enable certain debug statements. """ ########### Level Window Specific ############## #: Turns on debug messages self.debug=debug #: Name used in panel and window self.levelName=levelName #: Application that launched this level self.parent=parent ############ Viewport Dimensions ############ #: Width of the updated screen area (level + ui) self.viewW=925 #: Heigth of the updated screen area (level + ui) self.viewH=750 #: X origin of viewPort self.vX=0 #: Y origin of viewPort self.vY=0 ############## User Data and Stats ################ if gameData==None: #Todo: Create a gameData object that simply extends a dictionary, but has all of these #default parameters in place so that to init gameData it is simply self.gameData=GameData[VER] #currently these four lines of code are repeated in PortalWindow and ScribblerAdventureWindow #which is bad. RV self.gameData={} self.gameData["ScribblerAdventure"]={} self.gameData["ScribblerAdventure"]["logins"]=[] self.gameData["ScribblerAdventure"]["highestStage"]=0 self.gameData["ScribblerAdventure"]["highestLevel"]=0 #mostly holds login/clicks for stages self.gameData["ScribblerAdventure"]["stageData"]={} #holds clicks for levels as well as game info like [score,wins,bestTime,loses,restarts] self.gameData["ScribblerAdventure"]["levelData"]={} #self.gameData=GameData(VER) else: self.gameData=gameData #: Holds current user score for the level self.levelScore=0 #: MaxTime is 60 seconds or one minute self.maxTime=60 ############ Game Mechanics Variables ################# #attributes that control the game dynamices self.startTime=None self.runTimer=False self.gameOver=0 #used to lock the end pad so that the game over doesn't trigger #used in self.endCollide self.endPadLocked=False #general contact object. Currently being used for the endPad self.tempContact=None # Can be toggled to false to prevent dialog from closing on movement. self.closeDialogOnMove = True ############## Level Objects ############### #: The conversation object that manages all conversations. self.conversation = Conversation() #: List for storing robots self.robots=[] ########### Misc ################## #attribute used to measure frame-rate, if available. self.previousTime=0 self.avarageFps=0 self.frameCount=0 #: Need to iterate over calico.tags to access the keys self.printTags=[] for t in calico.tags: self.printTags.append(t) self.teleportState=0 ############ Data Structures for User Commands ############# #: List keeping track of all the interactable object the robot is #: currenlty in contact with to speed-up interactions self.interactContactList = [] #: List keeping track of all the objects you can pickup that the robot #: is currenlty in contact with to speed-up pickup. self.pickupContactList = [] ############# Events Intialization #################### #: The event queue self.stepEvents = [] #: The "events to add" queue to avoid concurrency issues self.toAdd = [] #: The events that need to be triggered when the robot leaves the #: starting pad self.startEvents = [] #: The events that need to be triggered when the robot reaches the end #: pad. self.endEvents = [] #: A singular handle to the status box hide event, such that later hide #: event can overwrite older ones. self.statusBoxHideEvent = StaticEvent() self.addStepEvent(self.statusBoxHideEvent) #TODO - Fix tracking. JH #The event to track the robots position and update the world in response #self.trackRobotEvent = TrackRobotEvent(self, rStartX, rStartY) #self.addStepEvent(self.trackRobotEvent) #The event to check whether the screen has been resized #(TODO: Should be migrated to Calico). self.trackResizeEvent = TrackResizeEvent(self) self.addStepEvent(self.trackResizeEvent) #: Event queue that blocks the conversation self.queue = EventQueue() self.queue.sticky = True self.addStepEvent(self.queue) ############### Panel Parameters ############### #Need to specify the dimensions of the panels before the simulation is #created self.panel1H=700 self.panel1W=700 self.panel2H=50 self.panel2W=self.panel1W self.panel3H=self.panel1H+self.panel2H self.panel3W=225 #Panel3 items and info pages # ''' # self.rowColors=[Color("red"), # Color("lightblue"), # Color("purple"), # Color("green"), # Color("orange")] # self.rowTitles=["Robot Actions:", # "Robot Sensors:", # "Calico", # "Programming:", # "Misc:"] # #the rows of tags should line up with the titles' # #the itmes in rowTags[0] are associated with rowTitles[0] # self.rowTags=[ # ["Move1","Move2","Wait","Pen","Beep", "Talk", "Use", "Pickup"], # ["Stall","Line","Light","IR","Obstacle"], # ["Shell","Output","Errors","Scripts"], # ["variables","commands","print","functions1","functions2","indent", # "if","while","for", "compare","lists"], # ["Shortcuts"]] # ''' #dictionary that stores association between a help page tag and the image that brings it up #alternatively the filename could be a pickle of a briefing object/shape self.pages={"talkPage":"talkPage.png","forwardPage":"move1Page.png","backwardPage":"move1Page.png", "turnLeftPage":"move1Page.png","turnRightPage":"move1Page.png","motorsPage":"move2Page.png", "shellPage":"shellPage.png","outputPage":"outputPage.png","errorsPage":"errorPage.png", "shortcutsPage":"shortcutsPage.png","waitPage":"waitPage.png","usePage":"usePage.png", "functions1Page":"functions1Page.png","commandsPage":"commandsPage.png","scriptPage":"scriptPage.png", "irPage":"irPage.png","linePage":"linePage.png","ifPage":"ifPage.png","comparePage":"comparePage.png", "pickupPage":"pickupPage.png","functions2Page":"functions2Page.png","variablesPage":"variablesPage.png", "whilePage":"whilePage.png","forPage":"forPage.png","distancePage":"distancePage.png","obstaclePage":"obstaclePage.png", "setIRPage":"setIRPage.png","beepPage":"beepPage.png","microphonePage":"microphonePage.png","lightPage":"lightPage.png", } for key,value in self.pages.iteritems(): self.pages[key]=IMAGE_PATH+value #helps pages for the intro stage. triples - (displayName, tag, color) self.IntroHelpPages=[ [("talk()","talkPage","red"),("forward()","forwardPage","red"),("backward()","backwardPage","red")], [("turnLeft()","turnLeftPage","red"),("turnRight()","turnRightPage","red"),("motors()","motorsPage","red")], [("Shell","shellPage","purple"),("Output","outputPage","purple"),("Errors","errorsPage","purple")], [("Shortcuts","shortcutsPage","orange")]] self.GauntletHelpPages=[ [("wait()","waitPage","red"),("use()","usePage","red")], [("functions1","functions1Page","green"),("commands","commandsPage","green")], [("Script","scriptPage","purple")]] self.LabyrinthHelpPages=[ [("pickup()","pickupPage","red"), ("variables", "variablesPage", "green"), ("functions2","functions2Page","green")]] self.ChamberHelpPages=[ [("getIR()","irPage","red"),("getLine()","linePage","red")], [("if","ifPage","green"),("compare","comparePage","green")] ] self.FixmeHelpPages=[ [("while","whilePage","green"),("for","forPage","green")]] self.ClosedLoopHelpPages=[ [("getDistance()","distancePage","red"),("getObstacle()","obstaclePage","red"),("setIRPower()","setIRPage","red")], [("getMicrophone()","microphonePage","red"),("beep()","beepPage","red"),("getLight()","lightPage","red")], ] #brief screen objects self.briefButton=[] self.briefShapeList=[] #entry objects self.entryObjects=[] #used to record current showing page #allows toggling current page on and off self.pageTag=None # #these page file names should line up with the tags # self.pageNames=[ # ["move1Page.png","move2Page.png","waitPage.png","penPage.png", # "beepPage.png","talkPage.png","usePage.png","pickupPage.png"], # ["stallPage.png","linePage.png","lightPage.png","irPage.png", # "obstaclePage.png"], # ["shellPage.png","outputPage.png","errorPage.png","scriptPage.png"], # ["variablesPage.png","commandsPage.png","printPage.png", # "function1Page.png","functions2Page.png","indentPage.png", # "ifPage.png","whilePage.png","forPage.png","comparePage.png", # "listsPage.png"], # ["shortcutsPage.png"]] # for i in range(len(self.pageNames)): # for j in range(len(self.pageNames[i])): # self.pageNames[i][j]=IMAGE_PATH+self.pageNames[i][j] # #dictionary to holds pairing between tags and page filenames # self.pages={}
[docs] def setup(self,runSimulator=True): """ Creates the window and starts the level. Args: runSimulator: if true, the simulator is started. """ #Creates the simulation as well as the master frame, which then holds #the world and uiFrame. self.sim=self._createSim() self.sim.simStep = 0.03 self.speechBox = SpeechBox(self) self.speechBox.draw(self) # Create the panel objects and draw them to self.uiFrame which is # created in self.createSim() self.panel1=makeSimplePanel((self.vX,self.vY), (self.panel1W,self.panel1H), self.uiFrame,"panel1", makeColor(0,0,0,0),self.panel1Click,False) self.panel2=makeSimplePanel((self.vX,self.vY+self.panel1H), (self.panel1W,self.panel1H+self.panel2H), self.uiFrame,"panel2", Color("white"),self.panel2Click) self.panel3=makeSimplePanel((self.vX+self.panel1W,self.vY), (self.panel1W+self.panel3W,self.panel1H+self.panel2H), self.uiFrame,"panel3",Color("white"),self.panel3Click) #Draw buttons, pictures, text, and shapes to the panels self._setupPanel1(self.panel1) self._setupPanel2(self.panel2) self.helpWidgets=[] self._setupPanel3(self.panel3) #Catch all for objects added to a level. Preferred order of creation #1. Background/Foreground/Texture #2. Static world objects #3. Start and end platforms #4. Dynamic world objects including moving NPCs or hazards #5. Create the robot #6. Fog of War if needed self.createLevel() if runSimulator: self.sim.setup() #Catch all for actions that don't need to be done in levelWindow init or #setup. Such as: #-Creating and loading the brief screen #-Setting and starting conversation with talk() #-Pausing the robot self.onLevelStart()
########################### # Initial Setup Functions # ########################### def _createSim(self,bW=1): """ Creates the simulation as well as the master frame, which then holds the world and uiFrame. Args: bW: width of the border of the simulator. """ #preserving location of last window try: lastWindow=getWindow() #if lastWindow is not None: #print("Last window is ",lastWindow) position=lastWindow.GetPosition() width=max(lastWindow.width,self.viewW) height=max(lastWindow.height,self.viewH) #print("Width and height are ",width,height) #print("Last position is ",position) #sim.window.Move(position[0],position[1]+21) #create simulation sim = Simulation(self.levelName, self.viewW, self.viewH, Color("white")) sim.window.Move(position[0],position[1]+21) sim.window.Resize(width,height) except: sim = Simulation(self.levelName, self.viewW, self.viewH, Color("white")) #sim.window.AppPaintable = True #sim.window.canvas.AppPaintable = True #p = Picture(IMAGE_PATH+"woodFloor3.png") #Set application background #self.background = LRC_Background(IMAGE_PATH+"woodFloor3.png") self.background = LRC_Background(IMAGE_PATH+"full_background.png") pixmap = Gdk.Pixmap(None, 3000, 1500, 24) #pixmap = Gdk.Pixmap(None, 397, 800, 24) #pixmap = Gdk.Pixmap(None, 10, 10, 24) with Gdk.CairoHelper.Create(pixmap) as g: # The -1 here is to fix a bug where the left most column of pixels # would not be painted g.SetSourceSurface(self.background.background, -1, 0) g.Paint() sim.window.canvas.BinWindow.SetBackPixmap(pixmap, False) #sim.window.setBackground(Color("black")) #sim.window.canvas.BinWindow.AppPaintable = True #create the master frame self.masterFrame = MasterFrame(0,0) self.masterFrame.levelWindow = self self.masterFrame.draw(sim.window) #Set world background rec = Rectangle((self.vX,self.vY), (self.vX+self.viewW,self.vY+self.viewH), color=Color("grey")) rec.draw(self.masterFrame) self.worldFrame = WorldFrame(0,0) self.worldFrame.levelWindow = self self.worldFrame.draw(self.masterFrame) self.uiFrame = Frame(0,0) self.uiFrame.draw(self.masterFrame) #sim.window.addScrollbars(wW+100,wH+100) setOptionSim("fast",True) setOptionSim("random",False) #Turned the walls into rectangles for debugging purpose #creates the border around the panel1 #top border rec = Rectangle((self.vX,self.vY), (self.vX+self.panel1W,self.vY+bW), color=Color("black")) rec.tag="outer wall 1" rec.bodyType = "static" rec.draw(self.worldFrame) rec.addToPhysics() #sim.window.draw(rec) #left border rec = Rectangle((self.vX,self.vY), (self.vX+bW,self.vY+self.panel1H), color=Color("black")) rec.tag="outer wall 2" rec.bodyType = "static" rec.draw(self.worldFrame) rec.addToPhysics() #sim.window.draw(rec) #right border rec = Rectangle((self.vX+self.panel1W-bW,self.vY), (self.vX+self.panel1W,self.vY+self.panel1H), color=Color("black")) rec.tag="outer wall 3" rec.bodyType = "static" rec.draw(self.worldFrame) rec.addToPhysics() #sim.window.draw(rec) #bottom border rec = Rectangle((self.vX,self.vY+self.panel1H), (self.vX+self.panel1W,self.vY+self.panel1H+bW), color=Color("black")) rec.tag="outer wall 4" rec.bodyType = "static" rec.draw(self.worldFrame) rec.addToPhysics() #sim.window.draw(rec) sim.userThread=self.gameThread#self.updateClock sim.window.onMouseDown(self.mouseDown) sim.window.onKeyPress(self.keyPressed) return sim def _setupPanel1(self,panel1): """ create a dummy rectangle that will be replaced with images, briefing prompts, or other prompts Args: panel1: A retangle used as the canvas for panel 1. """ dummyShape=Picture(IMAGE_PATH+"blankPrompt.png",outline=makeColor(0,0,0,0)) ##Rectangle((0,0),(10,10),color=Color("blue")) dummyShape.visible=True dummyShape.draw(panel1) def _setupPanel2(self,panel2): """ Paints the bottom panel on the provided rectangle. Args: panel2: A rectangle used a the canvas for panel 2. """ #shortcut variables for panel width and height p2W=panel2.width p2H=panel2.height fS=12 #font size pad=1 #padding around border for panel2 objects rad=5 #radius of click buttons spacer=5 #offset of panel2 center to top left origin oX=p2W/2-pad oY=p2H/2 #levelName= createPanelText(panel2,2*pad,pad,self.levelName,Color("black"),fS) if self.maxTime==60: timeText="01:00:00" else: timeText="00:00:00" createPanelText(panel2,2*pad,pad+p2H/2,timeText,Color("black"),fS) objPrompt=createPanelText(panel2,pad+(p2W/3)*0.5,pad,"Objective:",Color("black"),fS) #self.objectiveText=createPanelText(panel2,pad+(p2W/5)*0.60,pad+p2H/4,"",Color("black"),fS) self.objectiveText=createPanelText(panel2,pad+(p2W/3)*0.60,pad,"",Color("black"),fS,yJust="top") objTri=Polygon((objPrompt.x-10,objPrompt.y),(objPrompt.x,objPrompt.y-5), (objPrompt.x-10,objPrompt.y-10)) objTri.fill=Color("green") objTri.draw(panel2) createPanelText(panel2,pad+3*(p2W/5),pad,"Score:",Color("black"),fS) createPanelText(panel2,pad+3*(p2W/5),pad+p2H/2,str(self.levelScore),Color("black"),fS) buttonH=(p2H/2)-pad if self.parent==None: createPanelButton(panel2,2*p2W/3,pad,p2W/9,buttonH,"Prev","Prev",None,Color("white"),Color("gray"),fS) createPanelButton(panel2,2*p2W/3+p2W/9,pad,p2W/9,buttonH,"Next","Next",None,Color("white"),Color("gray"),fS) createPanelButton(panel2,2*p2W/3+2*p2W/9,pad,p2W/9,buttonH,"Menu","Menu",None,Color("white"),Color("gray"),fS) else: createPanelButton(panel2,2*p2W/3+2*p2W/9,pad,p2W/9,buttonH,"Menu","Menu",None,Color("white"),Color("black"),fS) if self.parent.prevLevelExists(): createPanelButton(panel2,2*p2W/3,pad,p2W/9,buttonH,"Prev","Prev",None,Color("white"),Color("black"),fS) else: createPanelButton(panel2,2*p2W/3,pad,p2W/9,buttonH,"Prev","Prev",None,Color("white"),Color("gray"),fS) if self.parent.nextLevelExists()=="level exists": createPanelButton(panel2,2*p2W/3+p2W/9,pad,p2W/9,buttonH,"Next","Next",None,Color("white"),Color("black"),fS) else: createPanelButton(panel2,2*p2W/3+p2W/9,pad,p2W/9,buttonH,"Next","Next",None,Color("white"),Color("gray"),fS) createPanelButton(panel2,2*p2W/3,p2H/2,p2W/9,buttonH,"Quit","Quit",None,Color("white"),Color("black"),fS) createPanelButton(panel2,2*p2W/3+p2W/9,p2H/2,p2W/9,buttonH,"Restart","Restart",None,Color("white"),Color("black"),fS) createPanelButton(panel2,2*p2W/3+2*p2W/9,p2H/2,p2W/9,buttonH,"Dismiss","Dismiss",None,Color("white"),Color("black"),fS) def _addHelpWidget(self, panel3, title, buttonList, height): self.helpWidgets.append(HelpPageWidget(panel3,(0,self.helpPagesY),(panel3.width,self.helpPagesY+height),title,buttonList,"helpWidget:" +str(len(self.helpWidgets)),debug=False)) self.helpPagesY+=height def _setupPanel3(self,panel3): """ Draws all side panel elements to the provided rectangle. Args: panel3: A Rectangle on which to draw the elements. """ pad=5 #padding between stuff buttonW = 55 buttonH = 20 rad=5 #radius of round rectangles fS=12 #font size # Create title helpPagesTitle = createPanelText(panel3,5,0,"Help Pages:",c=Color("black"),fS=18) L2=createPanelLine(panel3,(pad,0+helpPagesTitle.fontSize+pad),(panel3.width-pad,0+helpPagesTitle.fontSize+pad),lw=3) #helpWidgets is buggy. You have to make sure the helpWidget box is big enough to be able to clcik the buttons #Set debug to True to see the click box of the widget. self.helpPagesY = 25 self._addHelpWidget(panel3, "Intro", self.IntroHelpPages, 125) self._addHelpWidget(panel3, "Gauntlet", self.GauntletHelpPages, 100) self._addHelpWidget(panel3, "Labyrinth", self.LabyrinthHelpPages, 75) self._addHelpWidget(panel3, "Chamber", self.ChamberHelpPages, 100) self._addHelpWidget(panel3, "Coming soon (tm)", self.FixmeHelpPages, 75) self._addHelpWidget(panel3, "Closed Loop Control", self.ClosedLoopHelpPages, 100) ## self.helpWidgets.append(HelpPageWidget(panel3,(0,25),(panel3.width,150),"Intro",self.IntroHelpPages,"helpWidget:0",debug=False)) ## self.helpWidgets.append(HelpPageWidget(panel3,(0,150),(panel3.width,250),"Gauntlet",self.GauntletHelpPages,"helpWidget:1",debug=False)) ## self.helpWidgets.append(HelpPageWidget(panel3,(0,250),(panel3.width,350),"Labyrinth",self.LabyrinthHelpPages,"helpWidget:2",debug=False)) ## self.helpWidgets.append(HelpPageWidget(panel3,(0,350),(panel3.width,450),"Chamber",self.ChamberHelpPages,"helpWidget:3",debug=False)) ## self.helpWidgets.append(HelpPageWidget(panel3,(0,450),(panel3.width,500),"Closed Loop Control",self.ClosedLoopHelpPages,"helpWidget:4",debug=False)) # FIXME JH: Dirty hack to make sure the buttons for closed loop control don't show up in all levels. # Need to design a system where we don't have to do this. self.helpWidgets[4].setButtonsInvisible() self.helpWidgets[5].setButtonsInvisible() briefStartY=500 briefTitle=createPanelText(panel3,pad,600,"Briefings:",Color("black"),18) L2=createPanelLine(panel3,(pad,600+briefTitle.height+pad),(panel3.width-pad,600+briefTitle.height+pad),lw=3) #Add placeholder buttons that launch briefing screens. for i in range(5): self.briefButton.append(createPanelButton(panel3,pad,pad*i+buttonH*i+630,panel3.width-2*pad,buttonH,"Briefing "+str(i+1),"brief:"+str(i),fS=12)) self.briefButton[-1][0].visible=False ''' #shortcut variables for panel width and height p3W=panel3.width p3H=panel3.height #rowTitles=["Robot Actions:","Robot Sensors:","Programming:"] rowText=[] #tags go with the title #rowTags=[["Move1","Move2","Pen","Beep"],["Line","Light","IR","Obstacle"], # ["Shell","Scripts","Functions","Variables","if","while","for"]] rowButtons=[] #rowColors=[Color("red"),Color("lightblue"),Color("purple")] rowLimit = 4 #number of buttons per row pad=5 #padding between stuff #offset of panel3 center to top left origin oX=p3W/2-pad oY=p3H/2-pad buttonW = 55 buttonH = 20 rad=5 #radius of round rectangles fS=12 #font size currY=-oY #current y location for placing objects #main title for this panel lastText=createPanelText(panel3,pad,pad,"Help Pages:",Color("black"),18) L1=Line((-oX,currY+19),(-oX+p3W,currY+19),color=Color("black")) L1.draw(panel3) L1.setWidth(3) #print(lastText.getY()+p3H/2) currY=lastText.getY()+p3H/2+buttonH+pad #currY=buttonH+pad for i in range(len(self.rowTitles)): lastText=createPanelText(panel3, pad, currY, self.rowTitles[i], Color("black"), 15) currY=lastText.getY()+p3H/2+buttonH#currY+buttonH for j in range(len(self.rowTags[i])): if j!=0 and j%rowLimit==0: currY=currY+buttonH [lastShape,lastText]=createPanelButton(panel3, j%rowLimit*buttonW, currY, buttonW, buttonH, self.rowTags[i][j], self.rowTags[i][j], None, self.rowColors[i], Color("black"), fS) self.pages[self.rowTags[i][j]]=self.pageNames[i][j] currY=lastShape.getY()+p3H/2+buttonH ''' ############################# # UI Manipulation Functions # ############################# #Direct all set, show,and hide of panel1 through these functions #Could potentially enable more control of when panel1 appears and disappears #Placing an image onto panel1
[docs] def setImagePanel1(self,imageFileName,show=True): """ Directly set an image as the contents of panel 1. Args: imageFileName: The name of an image file (png, jpg) show: If True, the image will be displayed immediately """ if isinstance(imageFileName,str): if os.path.isfile(imageFileName): self.setShapePanel1(Picture(imageFileName),show=show) else: self.outputPrint("Error, "+imageFileName+" does not exist.",0) else: self.outputPrint("Error please enter a string filename",0)
[docs] def setShapePanel1(self,s,show=True,offX=None,offY=None): """ Directly set a shape as the contents of panel 1. Args: imageFileName: The name of an image file (png, jpg) show: If True, the shape will be displayed immediately """ for i in range(len(self.uiFrame.shapes)): if self.uiFrame.shapes[i].tag=="panel1": if show: self.showPanel1(self.uiFrame.shapes[i]) self.uiFrame.shapes[i].shapes[0]=s w=self.uiFrame.shapes[i].shapes[0].width h=self.uiFrame.shapes[i].shapes[0].height if offX==None and offY==None: self.uiFrame.shapes[i].shapes[0].moveTo(0,0) else: self.uiFrame.shapes[i].shapes[0].move(offX,offY) #resets the points in self.panel1.shapes[0] self.panel1=self.uiFrame.shapes[i] #set the drawn_on_shape variable so clicks register properly self.panel1.shapes[0].drawn_on_shape=self.panel1 return
[docs] def showPanel1(self,p=None): """ Show the shape that is currently set to panel 1. Args: p: Optional pointer to the shape currently displayed in panel 1. Providing this pointer slighlty increases performance. """ #if whatever function calling this show has a handle to panel1 #there's no need to refind it if p!=None: p.visible=True else: for i in range(len(self.uiFrame.shapes)): if self.uiFrame.shapes[i].tag=="panel1": self.uiFrame.shapes[i].visible=True return
#self.sim.window.canvas.shapes[i]=Picture(message) #self.sim.window.canvas.shapes[i].moveTo(self.p1X+self.panel1W/ #2,self.p1Y+self.panel1H/2) #self.sim.window.canvas.shapes[i].visible=True #return #Hides whatever object is drawn to panel1
[docs] def hidePanel1(self,p=None): """ Hide the shape that is currently set to panel 1. Args: p: Optional pointer to the shape currently displayed in panel 1. Providing this pointer slighlty increases performance. """ if p!=None: p.visible=False else: for i in range(len(self.uiFrame.shapes)): if self.uiFrame.shapes[i].tag=="panel1": self.uiFrame.shapes[i].visible=False return
[docs] def isVisiblePanel1(self): """ Checks whether panel 1 is visible. Return: True if panel 1 is visible, and False otherwise. """ for i in range(len(self.uiFrame.shapes)): if self.uiFrame.shapes[i].tag=="panel1": return self.uiFrame.shapes[i].visible
[docs] def dismiss(self): """ Function that performs same action as the Dismiss button or pressing 'b'. Dismisses/hides whatever is on Panel1 """ if self.isVisiblePanel1(): self.pageTag=None self.hidePanel1()
[docs] def updateClock(self): """Callback that updates the clock""" if self.runTimer: elapse=time()-self.startTime timeScore=self.maxTime-elapse if timeScore<=0: timeScore=0 m=int(timeScore/60) sec=int(timeScore%60) ss=int((timeScore%60-sec)*100) txt=str(m).zfill(2)+":"+str(sec).zfill(2)+":"+str(ss).zfill(2) self.panel2.shapes[1].setText(txt)
[docs] def flashObjective(self): if self.objectiveText.visible: self.objectiveText.visible=False else: self.objectiveText.visible=True
[docs] def setObjectiveText(self,message,numFlashes=0): """ Sets the current objective text. Args: message: A string containing the message to be displayed. """ self.objectiveText.setText(message) if numFlashes>0: #2 times numFlahes b/c the text will go invisible and then visible objectiveFlashEvent = RepeatedActionEvent(0.4,numFlashes*2,self.flashObjective) self.addStepEvent(objectiveFlashEvent)
[docs] def clearObjectiveText(self): """Sets the objective text to the empty string.""" self.objectiveText.setText("")
[docs] def addEntryObject(self,e): """Adds an entry object for the window to keep track of.Entry object focused considered during mouse down and key press""" self.entryObjects.append(e)
[docs] def clearEntryObject(self,e): """Clears list of entry objects. Entry object focused considered during mouse down and key press""" self.entryObjects=[]
[docs] def addBriefShape(self,s,text=None): """ Adds a shape to the list of briefing shapes. Args: s: A reference to the shape to be added. text: Sets the text of the button associated with this briefing. """ if isinstance(s,Shape): self.briefShapeList.append(s) i=len(self.briefShapeList)-1 if i<len(self.briefButton): self.briefButton[i][0].visible=True if text: self.briefButton[i][1].setText(text)
[docs] def launchBriefScreen(self,index): """ Will display the indicated briefscreen on top of the game area. Args: index: Integer indicating the index of the briefscreen to be shown. """ if index<self.briefShapeList: if isinstance(self.briefShapeList[index],Shape): self.setShapePanel1(self.briefShapeList[index],show=True)
[docs] def clearBriefList(self): """ Clears the list of briefscreens. Should probably not be called from within a level. """ self.briefShapeList=[]
[docs] def setButtonsVisible(self,helpWidgetIndex, buttonNamesList=[],numFlashes=0): self.helpWidgets[helpWidgetIndex].setButtonsVisible(buttoNamesList,numFlashes)
[docs] def setButtonsInvisible(self,buttonList=[]): self.helpWidgets[0].setButtonsInvisible(buttonNamesList)
#helper pages for retrieving the displayName, tag, and color of a help page button #info is passed to some type of briefing function or help page in order to add clickable buttons
[docs] def getI(self,buttonName): for i in range(len(self.helpWidgets)): #Find the widget with this button name if buttonName in self.helpWidgets[i].buttonAndText: return self.getHelpPageButtonInfo(i,buttonName,"Inline")
[docs] def getR(self,buttonName): for i in range(len(self.helpWidgets)): #Find the widget with this button name if buttonName in self.helpWidgets[i].buttonAndText: return self.getHelpPageButtonInfo(i,buttonName,"Related")
[docs] def getHelpPageButtonInfo(self,helpWidgetIndex,buttonName,returnType=None): buttonAndText=self.helpWidgets[helpWidgetIndex].buttonAndText[buttonName] button=buttonAndText[0] if returnType=="Inline": return Inline2(buttonName,"helpPage:"+button.tag, button.fill) elif returnType=="Related": return [Related(buttonName,"helpPage:"+button.tag, button.fill), " "] else: [buttonName,"helpPage:"+button.tag, button.fill]
[docs] def checkFocus(self): """ Helper function that iterates through the list of entry objects that could be added to this window. If any of the foucs for the entries are true than the focus for the main window is set to False. Mainly used to prevent the mouse and key presses from interrupting the text entries """ self.winFocus=True for e in self.entryObjects: if e.focus: self.winFocus=False continue
[docs] def addMouseDown(self,f): """ It's assumed the developer knows what they are doing and are adding a proper function 'f' with that has the fields (obj,event) """ self.sim.window.onMouseDown(f)
###################### # Callback functions # ######################
[docs] def mouseDown(self,obj, event): """ Callback function for when the window is clicked. Overwrite this function to execute a specific action when the user clicks the screen. Args: obj: A pointer to the screen being clicked on, which should be the level window itself. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ self.checkFocus() if self.debug and self.winFocus: print(event.x,event.y)
[docs] def keyPressed(self,obj,event): """ Callback function for when a key is pressed. This function can be overwritten to implement custom keystrokes, but do not forget to call this function at the end (LevelWindow.keyPressed), otherwise basic keystrokes like closing the window or restarting the level will no longer work. Args: obj: A pointer to the level window object. event: The event triggering this callback. Can be read to discover which key was pressed. """ self.checkFocus() if self.winFocus: if str(event.key)=="q" or str(event.key)=="Q": self.quitGame() elif str(event.key)=="r" or str(event.key)=="R": self.restartGame() elif (str(event.key)=="n" or str(event.key)=="N"): self.gotoNextLevel() elif (str(event.key)=="p" or str(event.key)=="P"): self.gotoPrevLevel() #dismisses panel1 screen as well as the speechbox elif (str(event.key)=="d" or str(event.key)=="D"): if self.isVisiblePanel1(): self.pageTag=None self.hidePanel1() elif str(event.key)=="F1": self.debugConsole()
#else: # self.createBriefScreen() # self.showPanel1() #Hides the speech box if visible. #Might have to add the condition where you check if #the conversation is done before you allow the #speechbox to be hidden, e.g. #if self.conversation.currentResponse().text=="": #RV #if self.speechBox.frame.visible: # self.speechBox.hide()
[docs] def panel1Click(self,obj,event): """ Callback for when panel 1 is clicked. Can be overwritten to launch a cusomt event when the game area is clicked. Args: obj: A pointer to the level window object. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ if self.debug: print("Clicking panel 1 at ",event.x,event.y) p=self.panel1.center cX=p[0] cY=p[1] #Clicked the object in self.panel1.shapes[0] if self.panel1.shapes[0].hit(event.x,event.y) and self.isVisiblePanel1(): #print("hit the display object") for s in self.panel1.shapes[0].shapes: if s.hit(event.x,event.y) and s.tag: #not None #print("Hit object ",s.tag) if self.pageTag==s.tag: #hide the page self.hidePanel1() self.pageTag=None else: if(s.tag.find("helpPage:")!=-1): self.setImagePanel1(self.pages[s.tag.split(":")[-1]]) self.pageTag=s.tag.split(":")[-1] elif s.tag.find("brief:")!=-1: index=int(s.tag.split(":")[-1]) #print("index is ",index) self.launchBriefScreen(index) self.pageTag=s.tag
[docs] def panel2Click(self,obj,event): """ Callback for when panel 2 is clicked. Should probably not be overwritten, as it handles basic button presses. Args: obj: A pointer to the level window object. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ p=self.panel2.center cX=p[0] cY=p[1] for s in self.panel2.shapes: #JH: This change only works in the new version of Calico #Since Calico calls hit internally as well, I don't know #how to make this backward compatible if s.hit(event.x,event.y): if s.tag=="Restart": self.restartGame() elif s.tag=="Quit": self.quitGame() elif s.tag=="Prev": self.gotoPrevLevel() elif s.tag=="Next": self.gotoNextLevel() elif s.tag=="Menu": if self.parent==None: pass else: self.quitGame() elif s.tag=="Dismiss": if self.isVisiblePanel1(): self.hidePanel1()
#else: #This is a hack for now. Eventually when the brief screens are created #save the shape so I don't have to regenerate it. The shape will #be shave to self.briefScreen, but first all other instances of #self.briefScren need to be cleared in the refactor. RV # self.createBriefScreen() # self.showPanel1() #if self.speechBox.frame.visible: # self.speechBox.hide()
[docs] def helpPageEvents(self,clickedPanel,event): """ Defunked function. Kept around for examples of code. RV. Callback for when panel 3 is clicked. Should probably not be overwritten, as it handles basic button presses. Args: clickedPanel: A pointer to the panel that was clicked. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ #required to register the correct click location p=clickedPanel.center cX=p[0] cY=p[1] #for s in self.panel3.shapes: for p in clickedPanel.shapes: #JH: This change only works in the new version of Calico #Since Calico calls hit internally as well, I don't know #how to make this backward compatible if p.hit(event.x,event.y): if s.tag.find("brief")!=-1: #if you click the same page again if self.pageTag==s.tag: #hide the page self.hidePanel1() self.pageTag=None #if the page you click isn't the same as the previous tag else: index=int(s.tag.split(":")[-1]) #print("index is ",index) self.launchBriefScreen(index) self.pageTag=s.tag if s.tag in self.pages: #close brief screen if up #if self.briefScreen.visible: # self.briefScreen.visible=False #if you click the same page again if self.pageTag==s.tag: #hide the page self.hidePanel1() self.pageTag=None #if the page you click isn't the same as the previous tag else: self.setImagePanel1(self.pages[s.tag]) self.pageTag=s.tag return
[docs] def briefScreenClick(self,obj,event): """ Callback for when a briefscreen is clicked. Should probably not be overwritten, as it handles basic button presses. Args: obj: A pointer to the level window object. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ self.helpPageEvents(self.briefScreen,event)
[docs] def panel3Click(self,obj,event): """ Callback for when panel 3 is clicked. Should probably not be overwritten, as it handles basic button presses. Args: obj: A pointer to the level window object. event: The event triggering this callback. Can be read to discover which button was clicked, and where the mouse pointer was. """ #required to register the correct click location p=self.panel3.center cX=p[0] cY=p[1] #temporary variable so I don't have to repeat the code for hiding the panel twice clickedTag=None for s in self.panel3.shapes: #TODO: In theory you should also check if the clicked item is visible. #This goes more for the help pages than the briefing screens. I'm not #adding the condition b/c it could help with debugging and might be a fun #bug/easter egg/secret. RV #if s.hit(event.x-cX,event.y-cY): # JH: The offsets should no longer be required # (and if fact, should break the buttons) if s.hit(event.x,event.y): #print("clicked tag is ",s.tag) if s.tag.find("brief:")!=-1: #print("clicked on brief screen") clickedTag=s.tag elif s.tag.find("helpWidget:")!=-1: #print("click on help page") index=int(s.tag.split(":")[-1]) #print("Searching for page hit at ",e.x-cX,e.y-cY) # JH: The offsets should no longer be required #clickedTag=self.helpWidgets[index].getClickedButton(event.x-cX,event.y-cY) clickedTag=self.helpWidgets[index].getClickedButton(event.x,event.y) else: #print("Not recognized tag ",s.tag) clickTag=s.tag break if clickedTag: #print("Going into clicked Tagged section. Original tag is ",s.tag) #if the page you click is the same as the previous tag hide it if self.pageTag==clickedTag: #hide the page self.hidePanel1() self.pageTag=None else: if s.tag.find("brief:")!=-1: index=int(s.tag.split(":")[-1]) #print("index is ",index) self.launchBriefScreen(index) self.pageTag=s.tag elif s.tag.find("helpWidget:")!=-1: self.setImagePanel1(self.pages[clickedTag]) self.pageTag=clickedTag else: #TODO: If the user doesn't click on a help button on the help widget you'll come here #That's fine. Just hide panel1 and no need to print the message below print("Clicked on object with unknown tag") print(clickedTag)
#self.helpPageEvents(self.panel3,e) ############################### # Functions to Control Calico # ###############################
[docs] def getScopeVariable(self,name=None): """ Function to retrieve variables in scope """ if self.exists(name): return calico.manager.scope.GetVariable(name) else: return None
[docs] def exists(self,name=None): """ Function to check if a variable is in scope """ try: calico.manager.scope.GetVariable(name) except: return False else: return True
[docs] def burnEvidence(self): """ Method for clearing the output and history. Useful when someone enters a password and you want to clear you're tracks. """ if self.gameOver==0: #calico.clear_output() calico.Output.Buffer.Text="" calico.history.clear() self.outputPrint("The Calico Project, Version 3.1.1\n",2)
[docs] def outputPrint(self,message,tagNo=0): """ Method for printing colored text to the output. Args: message: String containing the message to be displayed. tagNo: Integer indicating in which of the predetermined colors the text will be printed. """ calico.PrintLine(self.printTags[tagNo].Key, message)
[docs] def stopScript(self): """Stops the current script from running, if any.""" if calico.isRunning and calico.CurrentDocument is not None: calico.CurrentDocument.Stop() else: calico.AbortThread()
[docs] def setErrorCallback(self,callback): """ Adds a callback that is called whenever an error is thrown in Calico The callback must have an argument for the exception thrown and the initial command Exp: def expCallback(e,command): """ calico.manager["python"].engine.OnErrorCallback=callback
############################ # Game Mechanics Functions # ############################
[docs] def saveStats(self): """Saves the currenlty recorded statistics to a file (I guess?)""" name=self.levelName score=self.levelScore if self.startTime!=None: elapseTime=round(time()-self.startTime,2) else: elapseTime=0 #TODO: make better flags in order to count things like quits #and transitions to next or prev level if self.gameOver==0: #restart, quitgame, next, or prev level win=0 winTime=0 lose=0 restart=1 elif self.gameOver==-1: #lose win=0 winTime=0 lose=1 restart=0 elif self.gameOver==1: #win win=1 winTime=elapseTime lose=0 restart=0 else: #should never be reached win=0 winTime=0 lose=0 restart=0 #save data if self.parent != None: self.parent.saveGameData([score,elapseTime,win,winTime,lose,restart])
#stat=statObject(name,score,elapseTime,win,winTime,lose,restart) #self.gameData.updateStats(stat) #self.gameData.updateStats(self.levelName,[score,elapseTime,win,winTime,lose,restart]) #self.updateGameData([score,elapseTime,win,winTime,lose,restart]) ## def updateGameData(self,data): ## if self.levelName not in self.gameData["ScribblerAdventure"]["levelData"]: ## self.gameData["ScribblerAdventure"]["levelData"][self.levelName]={} ## self.gameData["ScribblerAdventure"]["levelData"][levelName]["logins"]=[] ## self.gameData["ScribblerAdventure"]["levelData"][levelName]["stats"]=[] ## ## self.gameData["ScribblerAdventure"]["levelData"][self.levelName].append(data) ##
[docs] def levelTransition(self): """ All the steps required for a level transition (quit, menu, next, prev, and restart) that have to be done to save data and to prevent crashing """ #very import or calico will crash trying to reinit the level self.stopScript() #accumalte stats self.saveStats()
[docs] def restartGameNoStopScript(self): """ Version of restartGame that doesn't stop scripts used for the restart() user command may cause calico to crash if used improperly. RV. """ #stop any script and saves #self.levelTransition() tempScore=self.levelScore self.__init__(self.gameData,self.parent) #hides the brief screen self.hidePanel1()
[docs] def restartGame(self): """Stops all scripts and restarts the level.""" #stop any script and saves self.levelTransition() tempScore=self.levelScore #tempStatusOn=self.statusOn self.__init__(self.gameData,self.parent) #hides the brief screen self.hidePanel1()
[docs] def quitGame(self): """Saves statistics and exits the game""" self.levelTransition() self.sim.window.Destroy() if self.parent != None: self.parent.showWindow()
## if self.parent != None: ## self.parent.onReturn()
[docs] def setGameOver(self,value): """ Indicates that the game is over, for better or worse. Args: value: 1 indicates that the player has won the game, -1 indicates that the player has lost the game. """ try: if self.gameOver == 0:# and self.runTimer: self.gameOver=value r=getRobot() if r: r.stop() self.runTimer=False endTime=time() if self.gameOver==-1: self.setImagePanel1(IMAGE_PATH+"loseGamePrompt.png") else: timeBonus=0 self.levelScore+=100+timeBonus if self.parent==None: #print("You won the game!") self.setImagePanel1(IMAGE_PATH+"winGamePromptNoEvent.png") else: unlocked=self.parent.levelSolved() self.setImagePanel1(IMAGE_PATH+"winGamePromptEvent.png" ) self.launchEndEvents() except: print(traceback.format_exc())
[docs] def loseGame(self): """ The player loses the game. Short for setGameOver(-1). This can be easier to use than setGameOver(-1) because it does not require a lambda when used in a callback function. """ self.setGameOver(-1)
[docs] def winGame(self): """ The player wins the game. Short for setGameOver(1). This can be easier to use than setGameOver(1) because it does not require a lambda when used in a callback function. """ self.setGameOver(1)
[docs] def confirmDebugCommand(self,success,message): if success: print("Success at "+message) else: print("Failure at "+message)
[docs] def debugConsole(self): """ Launches and an input dialog box where you can enter commands that can be used to debug and quickly move around the levels """ cmd=ask("Enter debug command.") print(cmd) if self.parent!=None: if cmd=="incrementHighestStage": self.parent.setHighestStage(self.gameData["ScribblerAdventure"]["highestStage"]+1) self.confirmDebugCommand(True,"Incrementing highest stage.") elif cmd=="decrementHighestStage": self.parent.setHighestStage(self.gameData["ScribblerAdventure"]["highestStage"]-1) self.confirmDebugCommand(True,"Decrementing highest stage.") elif cmd=="incrementHighestLevel": self.parent.setHighestLevel(self.gameData["ScribblerAdventure"]["highestLevel"]+1) self.confirmDebugCommand(True,"Incrementing highest level.") elif cmd=="decrementHighestLevel": self.parent.setHighestLevel(self.gameData["ScribblerAdventure"]["highestLevel"]-1) self.confirmDebugCommand(True,"Decrementing highest level.") elif cmd.startswith("setHighestStage"): try: highestStage = int(cmd.split()[1]) self.parent.setHighestStage(highestStage) self.confirmDebugCommand(True,"Set highest stage to: " + str(highestStage)) except: self.confirmDebugCommand(False,"Setting highest stage") elif cmd.startswith("setHighestLevel"): try: highestLevel = int(cmd.split()[1]) self.parent.setHighestLevel(highestLevel) self.confirmDebugCommand(True,"Set highest level to: " + str(highestLevel)) except: self.confirmDebugCommand(False,"Setting highest stage") if cmd=="loseCurrentLevel": self.loseGame() self.confirmDebugCommand(True,"Losing current level.") elif cmd=="winCurrentLevel": self.winGame() print("got here") self.confirmDebugCommand(True,"Winning current level")
[docs] def checkPassword(self,testPass): """ Checks if the incoming string matches the password. Ends the game if password is correct. Launches an incorrect password message if incorrect." """ fail=True if isinstance(testPass,str): entry=testPass.ToLower() if entry=="lrcmentor": #self.burnEvidence() self.speechBox.hide() self.setGameOver(1) fail=False if fail: #self.setResponse("Incorrect Password.") self.addResponse(IMAGE_PATH+"lockClosed.png","Incorrect Password. This incident will be reported.") talk()
[docs] def lockEndPad(self): """ Helper function that unlocks the end pad through the self.endPadLocked variable. This variable can be used to lock the end pad until the user accomplishes some task in the level. """ self.endPadLocked=True
[docs] def unlockEndPad(self): """ Helper function that locks the end pad through the self.endPadLocked variable. This variable can be used to lock the end pad until the user accomplishes some task in the level. """ self.endPadLocked=False
[docs] def onEndPad(self): if self.tempContact: if self.tempContact.IsTouching(): return True else: return False else: return False
[docs] def gameThread(self): """ This is the main function passed to Myro it calls the update clock and a another virtual function that can be used in different levels """ self.updateClock() try: self.levelThread() except: print(traceback.format_exc())
[docs] def gotoNextLevel(self): """Transitions to the next level, if possible.""" self.levelTransition() if self.parent!=None: self.parent.launchNextLevel()
[docs] def gotoPrevLevel(self): """Transitions to the previous level, if possible.""" self.levelTransition() if self.parent!=None: self.parent.launchPrevLevel()
########################## ##### User Functions ##### ########################## #User commands are defined here, but are loaded into memory by importing #the userFunctions.py file
[docs] def pickup(self): """ Function for picking up items from the game world. Return: The item that was picked up, or a string containing an error message if the pickup failed. """ wait(0.0000001) self.pickupContactList = cleanContactList(self.pickupContactList) for item, _ in self.pickupContactList: if item.visible(): item.hide() item.onPickup() return item.returned self.printToSpeechBox(PICKUP_FAILED) return PICKUP_FAILED
[docs] def skip(self): """ Function to skip dialogue. Also dismisses whatever is on panel 1. """ #TODO: skip() doesn't work if you put it and restart() at the top of a Script #restart() #skip() #forward(4,1) if self.debug: print("DEBUG: In skip function.") while len(self.queue.events) > 0 or self.conversation.blocked: if self.debug: print("DEBUG: Spinning in skip:", len(self.queue.events), self.conversation.blocked) pass self.conversation.getActiveThread().skip() if self.conversation.getActiveThread().blockNextSkip: # One of our actions has invoked the block skip, # possibly to notify the user of an error that should not be hidden, # do not hide the speech box. # # Leave the conversation in the state that the action calling blockSkip left it. self.conversation.getActiveThread().blockNextSkip = False if self.debug: print("DEBUG: Leaving skip function through blockSkip.") return self.speechBox.hide() self.addStepEvent(self.speechBox.hide) #Dismiss commands for panel1 if self.isVisiblePanel1(): self.pageTag=None self.hidePanel1() # Wait for animations to finish while len(self.queue.events) > 0: if self.debug: print("DEBUG: Waiting for animation to finish.") print("DEBUG: Animation parts left:", len(self.queue.events)) # Finish the conversation if self.conversation.autoUnpause: self.pauseRobot(False) self.conversation.autoUnpause = False self.closeDialogOnMove = True if self.debug: print("DEBUG: Leaving skip function.")
#wait(0.5)
[docs] def talk(self): """ Function for talking. Returns: Whatever is set to be returned, which is usually a string containing the spoken text. """ if self.debug: print("DEBUG: In talk function.") #print("Talk (1601):", "x" in userGlob) while len(self.queue.events) > 0 or self.conversation.blocked: if self.debug: print("Spinning in talk", len(self.queue.events), self.conversation.blocked) pass thread = self.conversation.getActiveThread() hasNext = thread.hasNext() response = thread.currentResponse() text = response.text portrait = response.portrait if text != "": self.printToSpeechBox(text, portrait, self.closeDialogOnMove, hasNext=thread.hasNext()) elif self.speechBox.visible(): self.speechBox.hide() else: self.printToSpeechBox("There is no one to talk to.") thread.nextResponse() response.launchActions() if self.conversation.getActiveThread().blockNextSkip: # If block skip is set, it probably means that the conversation was # extended. Recheck if the conversation has been extended. self.conversation.getActiveThread().blockNextSkip = False #hasNext = self.conversation.getActiveThread().hasNext() if not self.conversation.getActiveThread().hasNext() and self.conversation.autoUnpause: self.pauseRobot(False) self.conversation.autoUnpause = False self.closeDialogOnMove = True # actionEvent = StaticEvent(action, remove=True) # self.addStepEvent(actionEvent) if self.debug: print("DEBUG: Leaving talk function.") return response.returned
[docs] def use(self, items): """ Attempts to use the items passed by the user. Args: items: The items passed by the used, which can literaly be anything. Return: This function may return arbitrary objects based on whatever the use function did or did not accomplish. If the use function failed, a string containing an error message is returned. """ # Use one of the items if one of them is usable (slow?) for item in items: if hasattr(item, "canUse") and hasattr(item, "onUse"): if item.canUse: try: # Allows multiple items to be used simultaniously return item.onUse(items) except TypeError: # Call for backward compatibility return item.onUse() # Wait one simulation step, so the collision with the object has time to happen wait(0.00001) # The seperation doesn't always trigger (happens when the object is moved # outside of the physics environment). For these cases, clean them here. self.interactContactList = cleanContactList(self.interactContactList) for contactTuple in self.interactContactList: interact, _ = contactTuple if interact.canInteract: return interact.onInteract(items) # Print our failure to interact self.printToSpeechBox(USE_FAILED) return USE_FAILED
#Note there is a restart() user command that essentially calls def #restartGameNoStopScript(self) ################################### # Virtual Functions To Overwrite # ################################### #The sections after this provide helper functions that could be placed in #these virtual funcitons.
[docs] def onStartLevel(self): """Called right before the player gains control.""" pass
[docs] def createLevel(self): """Virtual function that should overwritten to create a level.""" pass
############################## # On Level StartUp Functions # ##############################
[docs] def getBriefObject(self): """Function returns a Shape with text and buttons drawn on it.""" pass
#RV: Now that there's getBriefObject, can this function be deleted?
[docs] def buildBriefing(self): """Function to be overwritten to build the briefing""" pass
[docs] def createBriefScreen(self): """Creates the briefscreen.""" briefImage=self.getBriefImage() if briefImage: self.setShapePanel1(briefImage,show=False) else: self.setImagePanel1(IMAGE_PATH+"blankBriefWithText.png",show=False)
# Probably need a way to check if value is a bool. RV # TODO: Add stop() to the updateSpeed in C# code. RV
[docs] def pauseRobot(self,value,index=None): """ Helper function to stop the robot. Args: value: True to pause the robot, False to unpause. index: The index of the robot to be paused. If no index is provided all robots are paused. """ if index==None: for r in self.robots: r.pauseRobot=value if value==True: r.stop() else: if index<len(self.robots): self.robots[index].pauseRobot=value if value==True: self.robots[index].stop()
##############Conversation Functions################
[docs] def printToSpeechBox(self, text, portrait=None, closeOnMove=True, closeOnTime=0, hasNext=False): """ Prints a message directly to the speech box. Args: text: The text to be printed (str) portrait: The portrait to be show with the string. Can be a Calico Picture object or a string representing the location of an image file to be loaded. A default Scribbler image is shown if None is passed. closeOnMove: If true, an on move event will be scheduled that closes the box as soon as the robot moves. closeOnTime: If true, a timed event will be scheduled that closes the box after a certain number of ticks. Can not be used with 'closeOnMove'. If both options are true, only the onMoveEvent will be scheduled. hasNext: Boolean the will tell the speechbox if there is more conversation left. Should be false if this is a one-of message. """ try: #Create a new event showBoxEvent = ShowBoxEvent(self) #Set portrait if not portrait: portrait=makePic(IMAGE_PATH + "scribblerPortrait.png") elif not isinstance(portrait, Picture): portrait=makePic(portrait) showBoxEvent.portrait = portrait if self.conversation.speechBoxYPos is None: # Determine wheter the speech box will appear at the top or bottom # of the level. robot = getRobot() if robot.frame.y < 400: showBoxEvent.y = 550 else: showBoxEvent.y = 50 else: showBoxEvent.y = self.conversation.speechBoxYPos #Set text showBoxEvent.text = text #Set wheter box has more responses left showBoxEvent.hasNext = hasNext #Create the box on the next tick self.addStepEvent(showBoxEvent) #Add a close event if requested if closeOnTime: timedEvent = TimedEvent(closeOnTime, self.speechBox.hide) self.statusBoxHideEvent.newAction = timedEvent elif closeOnMove: onMoveEvent = OnMoveEvent(self.speechBox.hide) self.statusBoxHideEvent.newAction = onMoveEvent except: calico.PrintLine(self.printTags[0].Key, traceback.format_exc())
[docs] def convEvent(self, action): currentAction = self.conversation.threads[0].responses[-1].action if not isinstance(currentAction, list): if currentAction is None: currentAction = [] else: currentAction = [currentAction] currentAction += [lambda: self.queue.add(action)] self.conversation.threads[0].responses[-1].action = currentAction
[docs] def convPause(self): self.pauseRobot(True)
[docs] def convStart(self): self.pauseRobot(True) self.conversation.autoUnpause = True self.closeDialogOnMove = False self.talk()
[docs] def convUnpause(self): self.convEvent(lambda: self.pauseRobot(False))
[docs] def addResponse(self, portrait=None, text="", action=None, sticky=False): """ Convenience functions for adding responses to the conversation object. Args: portrait: The portrait to be show with the string. Can be a Calico Picture object or a string representing the location of an image file to be loaded. A default Scribbler image is shown if None is passed. text: The text to be printed (str). sticky: Whether this text should be repeated when there is no more conversation left. """ self.conversation.addResponse(portrait, text, action, sticky)
[docs] def setResponse(self, portrait=None, text="", action=None, sticky=False): """ Convenience functions for setting responses to the conversation object. The only difference between adding and setting responses for the conversation object is that, if you set a response for the conversation object, it will immedidiately become the next response, skipping all other responses, whereas if you add a response, a use will have to go through all previous responses before getting the new one. Args: portrait: The portrait to be show with the string. Can be a Calico Picture object or a string representing the location of an image file to be loaded. A default Scribbler image is shown if None is passed. text: The text to be printed (str). sticky: Whether this text should be repeated when there is no more conversation left. """ self.conversation.setResponse(portrait, text, action, sticky)
[docs] def clearResponses(self): """Clears all responses from the conversation object.""" self.conversation.response=[]
[docs] def addThread(self, thread): self.conversation.addThread(thread) return thread
########################## # Add-To-World Functions # ########################## #Following Functions should be added to createLevel() #Functions can be broken into different categories #Note the order objects are made determines z Depth #1. Setting background #2. Creates static objects (walls, non-moving items) #3. Creates start and end pads (glorified event pads) #4. Create robot(s) #5. Create dynamic moving objects (NPCs, hazards, moving items) #6. Creaet fog #Background
[docs] def setBackground(self,back): """ Sets a background image. Args: back: String pointing towards an image file (jpg, png). Return: A reference to the background image, or None if the function was unsuccessful. """ if back!=None: backPicture=LRC_Background(back) backPicture.draw(self.worldFrame) #Might be buggy. TODO Test. JH #backPicture.stackOnBottom() return backPicture
[docs] def setForeground(self,fore): """ Sets a foreground image. Should probably not be used, as it may severly hurt performance. Args: fore: String pointing towards an image file (jpg, png). Return: A reference to the foreground image, or None if the function was unsuccessful. """ if fore!=None: forePicture=LRC_Background(fore) forePicture.draw(self.worldFrame) #forePicture.stackOnTop() return forePicture
#Static/Dynamic Objects
[docs] def addObstacle(self, ob, vis=True): """ Helper function for adding static obstacles. Args: ob: Shape to be added as a static objec to the simulator. Return: A reference to the added shape. """ #print("HERE") if isinstance(ob, Group): for shape in ob.items: self.addObstacle(shape) else: if not vis: ob.visible = False ob.draw(self.worldFrame) ob.addToPhysics() #self.sim.window.draw(ob) ob.bodyType="static" return ob
[docs] def addDecor(self, posOrOb, ob=None, vis=True): pos = None # TODO JH: The only reason for this admitadly bizar construction # is that the Picture class in Calico is the only one that doesn't # take some form of positioning as an argument. # Refactering Calico slightly would allow us to do away with most of # this stuff. if isinstance(posOrOb, tuple): pos = posOrOb else: assert ob is None ob = posOrOb if not vis: ob.visible = False if pos is not None: ob.moveTo(pos[0], pos[1]) ob.draw(self.worldFrame) return ob
[docs] def addActor(self, actor): """ Adds an actor to the world. Args: actor: The actor to be added. Return: A reference to the added actor. """ actor.draw(self) #if actor.speechBubble: # self.npcList.append(actor) return actor
[docs] def quickActor(self, x, y, base, **kwargs): """ Convenience function for adding an actor to the world. QuickActor will create an actor based on the 'base' that is passed to the function. If the base is a string, it is assumed that you wish to create a picture actor, with the string being a directory and filename. If the base is a list, it is assumed you want to create a Sprite actor, where every element in the list is a string pointing to a directory and filename, each becoming a frame for the Sprite. If the base is a shape, it is assumed you want to create a Shape actor, with the Shape being the body of the actor. Args: x: The x position for the actor. y: The y position for the actor. base: The base on which the actor should be created. kwargs: Keyword arguments passed direclty to the actor. Return: A reference to the created actor. """ if "dynamic" in kwargs: if kwargs["dynamic"] and "bodyType" not in kwargs: kwargs["bodyType"] = "dynamic" if "bodyType" not in kwargs: kwargs["bodyType"] = "static" #if isinstance(base, str): # return self.addActor(PictureActor((x, y), base, None, **kwargs)) #elif hasattr(base, "__iter__"): return self.addActor(SpriteActor((x, y), base, None, **kwargs))
#else: # return self.addActor(ShapeActor((x, y), base, **kwargs)) ## def quickDecor(self, x, y, base, **kwargs): ## if isinstance(base, str): ## # print(base[-4:]) ## if base[-4:] == ".png": ## base = makePic(base) ## else: ## base = makeText(base) ## if "color" in kwargs: ## base.color = kwargs["color"] ## if "rotation" in kwargs: ## base.rotateTo = kwargs["rotation"] ## if "scale" in kwargs: ## base.scaleTo = kwargs["scale"] ## base.moveTo(x, y) ## self.addDecor(base) ## return base
[docs] def createRobotStart(self, x, y, a): self.createStartPad(x, y) self.createRobot(x,y,a)
[docs] def quickItem(self, x, y, base, description=None, **kwargs): """ Convenience function that sets a description and pickup equal to True. Args: x: The x position for the actor. y: The y position for the actor. base: The base on which the actor should be created. description: The description that is returned when the item is printed. kwargs: Keyword arguments passed direclty to the actor. Return: A reference to the created actor. """ if description: kwargs["description"] = description if "pickup" not in kwargs: kwargs["pickup"] = True return self.quickActor(x, y, base, **kwargs)
[docs] def quickLock(self, x, y, base, key, action, failMsg="The key does not fit.", **kwargs): """ Convenience function creates a lock, based on a key and an action. Args: x: The x position for the actor. y: The y position for the actor. base: The base on which the actor should be created. key: The key necessary to use this lock. action: The action that is executed whent he key fits the lock. failMsg: The message that is displayed when the key does not fit the lock. kwargs: Keyword arguments passed direclty to the actor. Return: A reference to the created actor. """ if base is None: base = makeCircle(45, "gold") debug = False if "debug" in kwargs: debug = kwargs["debug"] kwargs["interact"] = LockEvent(key, action, failMsg, debug) return self.quickActor(x, y, base, **kwargs)
#Platforms
[docs] def createEndPad(self,x,y,locked=False): """ Creates an end-pad at the indicated location. Args: x: The x-coordinate of the end-pad. y: The y-coordinate of the end-pad. Return: A reference to the end pad. """ self.endPadLocked=locked endPad=Platform((x,y), 35, IMAGE_PATH+"endPlat.png", collision=self.endCollide, debug=False) self.addActor(endPad) return endPad
[docs] def createStartPad(self,x,y): """ Creates a start-pad at the indicated location. Args: x: The x-coordinate of the start-pad. y: The y-coordinate of the start-pad. Return: A reference to the start pad. """ startPad=Platform((x,y), 1, IMAGE_PATH+"startPlat.png", separation=self.startRingCollideSep, debug=False) self.addActor(startPad) return startPad
#Robot
[docs] def createRobot(self,x,y,rotation): """ Creates a robot at the indicated location. Args: x: The x-coordinate of the robot. y: The y-coordinate of the robot. rotation: The rotation of the robot. """ try: #self.robots.append(makeRobot("SimScribblerRounded", self.sim)) self.robots.append(makeRobot("SimScribbler", self.sim)) except Exception as e: if "Unable to make robot" in e.message: print("Rounded Scribbler not found, reverting back to square Scribbler") self.robots.append(makeRobot("SimScribbler", self.sim)) else: raise shapes = self.sim.window.canvas.shapes shapes.RemoveAt(shapes.Count-1) #self.sim.window.canvas.shapes self.robots[-1].frame.draw(self.worldFrame) self.robots[-1].frame.outline=makeColor(0,0,0,0) self.robots[-1].frame.body.Friction=0.15 self.robots[-1].setPose(x,y,rotation)
#Fog
[docs] def createFog(self): """ Creates a 'fog-of-war' for the simulator. Return: A reference to the fog-of-war image. """ fog=Picture(IMAGE_PATH+"fog.png") #fog.fill.alpha=245 fog.draw(self.robots[0].frame) fog.move(-fog.height/2,-fog.width/2) fog.rotate(-90) return fog
########################## #### Event Functions ##### ##########################
[docs] def waitForStep(self): """ Blocks until one iteration of events has been processed. Note: Can not be called in sync with calico thread. """ wait = WaitEvent() self.addStepEvent(wait) while not wait.finished: pass
[docs] def addStepEvent(self, event): """ Add an event to the level thread. Events should be functions. """ if event not in self.toAdd: self.toAdd.append(event)
[docs] def addStartEvent(self, event): """ Add an event to the start events. """ if event not in self.startEvents: self.startEvents.append(event)
[docs] def addEndEvent(self, event): """ Add an event to the end events. """ if event not in self.endEvents: self.endEvents.append(event)
[docs] def launchEndEvents(self): """ Called when the level ends, for whathever reason. """ try: for event in self.endEvents: event() except: print(traceback.format_exc())
[docs] def launchStartEvents(self): """ Called when the robot leaves the starting plate. """ try: for event in self.startEvents: event() except: print(traceback.format_exc())
[docs] def levelThread(self): """ Experiment with an event based level thread. """ try: for event in self.toAdd: #if event not in self.stepEvents: # JH: Necessary? self.stepEvents.append(event) del self.toAdd[:] self.stepEvents = [event for event in self.stepEvents if event()] except: print(traceback.format_exc())
[docs] def clearStepEvents(self): """Clears all events.""" del self.toAdd[:] del self.stepEvents[:]
######################################### #Base Collision and Separation Functions# #########################################
[docs] def startRingCollideSep(self, myfixture, otherfixture): """ Callback for one the robot leaves the starting pad. Args: myfixture: Should be the starting pad. otherfixture: Should be the robot. """ r=getRobot() sepTriggeredByRobot = otherfixture.Body.UserData==r.frame.body.UserData if r is None: return if not self.runTimer and sepTriggeredByRobot: #bring down the briefing it is up #self.briefScreen.visible=False self.hidePanel1() self.runTimer=True self.startTime=time() self.launchStartEvents()
[docs] def endCollide(self,myfixture, otherfixture, contact): """ Callback for one the robot reaches the end-pad. Args: myfixture: Should be the end-pad. otherfixture: Should be the robot. contact: The Farseer Contact holding contact information. Returns: True. Returning anything else may cause weird bugs. """ self.tempContact=contact r=getRobot() if r!=None and not self.endPadLocked: if otherfixture.Body.UserData==r.frame.body.UserData: if not isnan(r.frame.getX()) and not isnan(r.frame.getY()): self.setGameOver(1) return True
[docs] def interactCollide(self, myfixture, otherfixture, contact): """ Collision function for objects that want to be able to interact. Args: myfixture: The object that registered the collision callback. otherfixture: The object that collided with us. contact: The Farseer Contact holding contact information. Returns: True. Returning anything else may cause weird bugs. """ r=getRobot() if r is None: return True if otherfixture.Body.UserData==r.frame.body.UserData: self.interactContactList.append((myfixture.Body.UserData, contact)) return True
[docs] def interactSeparate(self, myfixture, otherfixture): """ The separation function for object that want to be able to interact. While not as important as the collision function, registering the seperation callback OnSeperation, can speed up the simulation by throwing away unnecessary contacts. Args: myfixture: The object that registered the separation callback. otherfixture: The object that separated from us. """ try: r=getRobot() if r is None: return if otherfixture.Body.UserData==r.frame.body.UserData: self.interactContactList=cleanContactList(self.interactContactList) except: print(traceback.format_exc())
[docs] def pickupCollide(self, myfixture, otherfixture, contact): """ Collision function for objects that want to be able to picked up. Args: myfixture: The object that registered the collision callback. otherfixture: The object that collided with us. contact: The Farseer Contact holding contact information. Returns: True. Returning anything else may cause weird bugs. """ try: r=getRobot() if r is None: return if otherfixture.Body.UserData==r.frame.body.UserData: self.pickupContactList.append((myfixture.Body.UserData, contact)) return True except: print(traceback.format_exc())
[docs] def pickupSeparate(self, myfixture, otherfixture): """ The separation function for object that want to be able to be picked up. While not as important as the collision function, registering the seperation callback OnSeperation, can speed up the simulation by throwing away unnecessary contacts. Args: myfixture: The object that registered the separation callback. otherfixture: The object that separated from us. """ try: r=getRobot() if r is None: return if otherfixture.Body.UserData==r.frame.body.UserData: self.pickupContactList=cleanContactList(self.pickupContactList) except: print(traceback.format_exc())
[docs] def deathCollide(self, myfixture, otherfixture, contact): """ Convience collision function you can install on custom hazards. Args: myfixture: The object that registered the collision callback. otherfixture: The object that collided with us. contact: The Farseer Contact holding contact information. Returns: True. Returning anything else may cause weird bugs. """ r=getRobot() if r: if otherfixture.Body.UserData==r.frame.body.UserData: self.setGameOver(-1) return True
################## # Misc Functions # ##################
[docs] def checkVariableValue(self): def fun(): for key in calico.manager.scope.GetVariableNames(): print(key, calico.manager.scope.GetVariable(key))
[docs] def safeMove(self, x, y, a): while self.teleportState==1: pass self.teleportState=1 self.addStepEvent(lambda: self._safeMove(x,y,a))
[docs] def safeMoveBlock(self, x, y, a): self.safeMove(x, y, a) while self.teleportState!=0: pass
def _safeMove(self, x, y, a): robot = getRobot() if not robot: return False self.teleportState=0 robot.stop() #The contacts do not like the robot suddenly teleporting. Clear all of them (they will be recreated). del self.interactContactList[:] del self.pickupContactList[:] robot.setPose(x, y, a) robot.frame.body.ResetDynamics() disableContactList(robot.frame.body.ContactList) self.teleportState=0 return False
#Possible fonts for Calico #http://www.w3.org/TR/CSS2/fonts.html#generic-font-families