gui.py
from graphicsUtils import *
from ghostbusters import *
import math
COLOR_BLACK = formatColor(0,0,0)
COLOR_WHITE = formatColor(1,1,1)
COLOR_RED = formatColor(0.9,0,0)
COLOR_ORANGE = formatColor(0.9,.5,0)
COLOR_YELLOW = formatColor(1,1,0)
COLOR_GREEN = formatColor(0,0.9,0)
COLOR_OCEAN_BLUE = formatColor(0,.5,1)
COLOR_GREY = formatColor(0.5,0.5,0.5)
GRID_SIZE = 100
MARGIN = GRID_SIZE
PANEL_WIDTH = 1.6 * GRID_SIZE
INFO_LINES = 3
SHOW_BELIEFS = True
SHOW_GHOSTS = False
USE_TIME = False
DISPLAY_FONT = "Courier New"
#DISPLAY_FONT = "Times"
FONT_SMALL = GRID_SIZE / 4
FONT_LARGE = GRID_SIZE / 3
GHOST_SHAPE = [
( 0, 0.3 ),
( 0.25, 0.75 ),
( 0.5, 0.3 ),
( 0.75, 0.75 ),
( 0.75, -0.5 ),
( 0.5, -0.75 ),
(-0.5, -0.75 ),
(-0.75, -0.5 ),
(-0.75, 0.75 ),
(-0.5, 0.3 ),
(-0.25, 0.75 )
]
GHOST_SIZE = 0.65
GHOST_COLORS = []
GHOST_COLORS.append(formatColor(1.0,0.6,0.0)) # Yellow
GHOST_COLORS.append(formatColor(.1,.75,.7)) # Green
GHOST_COLORS.append(formatColor(0,.3,.9)) # Blue
GHOST_COLORS.append(formatColor(.98,.41,.07)) # Orange
GHOST_COLORS.append(formatColor(.4,0.13,0.91)) # Purple
GHOST_COLORS.append(formatColor(.9,0,0)) # Red
def scaleGridSize(scale):
global GRID_SIZE, MARGIN, FONT_SMALL, FONT_LARGE, PANEL_WIDTH
GRID_SIZE = GRID_SIZE * scale
MARGIN = GRID_SIZE
PANEL_WIDTH = 1.6 * GRID_SIZE
FONT_SMALL = int(GRID_SIZE) / 6
FONT_LARGE = int(GRID_SIZE) / 4
class GlobalPosition:
def __init__(self, topLeft=(0,0), bottomRight=(0,0)):
self.topLeft = topLeft
self.bottomRight = bottomRight
self.status = 'clickable'
def contains(self, point):
x,y = point
if x < self.topLeft[0]: return False
if x > self.bottomRight[0]: return False
if y < self.topLeft[1]: return False
if y > self.bottomRight[1]: return False
return True
GRID_POSITION = GlobalPosition()
BUTTON_BUST = GlobalPosition()
BUTTON_TIME = GlobalPosition()
class InfoPane:
def __init__(self, layout):
self.width = PANEL_WIDTH
self.xbase = MARGIN*2 + (layout.cols-1)*GRID_SIZE
self.ybase = MARGIN/2
self.height = MARGIN + (layout.rows-1)*GRID_SIZE
self.info = []
self.infoText = []
self.drawPane()
def to_screen(self, pos, y = None):
"""
Translates a point relative from the top left of the info pane.
"""
if y == None: x,y = pos
else: x = pos
x = self.xbase + x
y = self.ybase + y
return x,y
def drawBustButton(self, status='clickable'):
size_x = 0.75 * self.width
size_y = 0.25 * GRID_SIZE
height = (FONT_SMALL+3) * 14
screen_x, screen_y = self.to_screen((size_x, height))
BUTTON_BUST.status = status
if status == 'clickable':
color = COLOR_OCEAN_BLUE
elif status == 'unclickable':
color = COLOR_GREY
elif status == 'clicked':
color = COLOR_RED
sq = square( (screen_x, screen_y),
(size_x, size_y),
icolor = color,
ocolor = COLOR_WHITE,
filled = 1,
width = 1,
smooth = 0)
text( (screen_x, screen_y), COLOR_WHITE, 'BUST', DISPLAY_FONT, FONT_LARGE, "bold", anchor = 'c')
BUTTON_BUST.topLeft = (screen_x - size_x, screen_y - size_y)
BUTTON_BUST.bottomRight = (screen_x + size_x, screen_y + size_y)
def drawTimeButton(self, status='clickable'):
size_x = 0.75 * self.width
size_y = 0.25 * GRID_SIZE
height = ((FONT_SMALL+3) * 14) + (2* size_y) + 10
screen_x, screen_y = self.to_screen((size_x, height))
BUTTON_TIME.status = status
if status == 'clickable':
color = COLOR_OCEAN_BLUE
elif status == 'unclickable':
color = COLOR_GREY
elif status == 'clicked':
color = COLOR_RED
sq = square( (screen_x, screen_y),
(size_x, size_y),
icolor = color,
ocolor = COLOR_WHITE,
filled = 1,
width = 1,
smooth = 0)
text( (screen_x, screen_y), COLOR_WHITE, 'TIME+1', DISPLAY_FONT, FONT_LARGE, "bold", anchor = 'c')
BUTTON_TIME.topLeft = (screen_x - size_x, screen_y - size_y)
BUTTON_TIME.bottomRight = (screen_x + size_x, screen_y + size_y)
def drawPane(self):
self.drawBustButton()
if USE_TIME: status = 'clickable'
else: status = 'unclickable'
self.drawTimeButton(status)
color = COLOR_WHITE
size = FONT_SMALL
lineHeight = size+3
self.ghostText = text( self.to_screen(0, 0),
color, 'GHOSTS REMAINING: 0', DISPLAY_FONT, size, 'bold')
self.bustText = text( self.to_screen(0, lineHeight),
color, 'BUSTS REMAINING: 0', DISPLAY_FONT, size, 'bold')
self.scoreText = text( self.to_screen(0, 2*lineHeight),
color, 'SCORE: 0', DISPLAY_FONT, size, 'bold')
linepos = 5*lineHeight
screenpos = self.to_screen((0, linepos))
text( screenpos, color, 'MESSAGES:', DISPLAY_FONT, size, 'bold')
count = INFO_LINES
for line in range(INFO_LINES):
height = linepos + (count * (size+3))
pos = self.to_screen(0,height)
self.infoText.append(text( pos, COLOR_WHITE, '', DISPLAY_FONT, size))
self.info.append('')
count -= 1
def updateScore(self, state, busts):
changeText(self.ghostText, "GHOSTS REMAINING: % 4d" % state.getNumGhosts())
changeText(self.bustText, "BUSTS REMAINING: % 4d" % busts)
changeText(self.scoreText, "SCORE: % 14d" % state.score)
def updateMessages(self, msg):
self.info.append(msg)
self.info = self.info[-1*INFO_LINES:]
count = 0
for line in self.info:
changeText(self.infoText[count], line)
count += 1
class GhostbusterGraphics:
def __init__(self):
pass
def initialize(self, state):
"""
initialize the gui
state is a ghostbusters.GameState
"""
self.layout = state.layout
self.setup()
self.infoPane = InfoPane(self.layout)
self.infoPane.updateScore(state, state.getNumGhosts())
self.showState(state)
def setup(self):
width = self.layout.cols - 1
height = self.layout.rows - 1
screen_width = MARGIN + (width*GRID_SIZE) + MARGIN + PANEL_WIDTH + MARGIN
screen_height = (2*MARGIN) + (height*GRID_SIZE)
GRID_POSITION.topLeft = (MARGIN/2, MARGIN/2)
GRID_POSITION.bottomRight = ((1.5*MARGIN) + (width*GRID_SIZE), (1.5*MARGIN) + (height*GRID_SIZE))
begin_graphics(screen_width,
screen_height,
COLOR_BLACK,
title='ghostbusters')
# trying to create a button
#self.bustButton = createButton('bust', self.startBusting(), pos=(0,0))
def showGhosts(self, ghosts):
"""
currently not used
show the position of each ghost
"""
for ghost in ghosts:
row, col = ghost
screen_x, screen_y = self.to_screen((row, col))
sq = square( (screen_x, screen_y),
(0.25*GRID_SIZE, 0.25*GRID_SIZE),
icolor = COLOR_BLACK,
ocolor = COLOR_BLACK,
filled = 1,
width = 1,
smooth = 1)
def showState(self, state, revealGhosts=False):
"""
draws the board to the gui
default is the board with no special info
"""
displayBoard = self.layout.board
if revealGhosts:
for ghost in state.ghosts:
row, col = ghost.pos
displayBoard[row][col] = 'B'
square_color = COLOR_OCEAN_BLUE
sensor_color = square_color
for row in range(self.layout.rows):
for col in range(self.layout.cols):
screen_x, screen_y = self.to_screen((row, col))
# start with a blank board
sq = square( (screen_x, screen_y),
(0.5*GRID_SIZE, 0.5*GRID_SIZE),
icolor = COLOR_OCEAN_BLUE,
ocolor = COLOR_WHITE,
filled = 1,
width = 1,
smooth = 0)
value = displayBoard[row][col]
if value == ' ':
continue
def showMove(self, observation):
pos, result = observation
# get screen position
screen_x, screen_y = self.to_screen(pos)
# get a sensor display color
if result == Readings.RED:
sensor_color = COLOR_RED
elif result == Readings.ORANGE:
sensor_color = COLOR_ORANGE
elif result == Readings.YELLOW:
sensor_color = COLOR_YELLOW
elif result == Readings.GREEN:
sensor_color = COLOR_GREEN
# show sensor reading
squares( (screen_x, screen_y),
(0.5*GRID_SIZE, 0.5*GRID_SIZE),
icolor = COLOR_OCEAN_BLUE,
ocolor = sensor_color,
filled = 1,
width = 1,
smooth = 0,
count = GRID_SIZE / 10)
desc = 'sensor at ' + str(pos) + ' [' + str(result) + ']'
self.infoPane.updateMessages(desc)
def endGame(self, bustResults, ghosts):
"""
show the bust results
"""
for bustResult in bustResults:
pos, result = bustResult
screen_x, screen_y = self.to_screen(pos)
if result == 'hit':
sq = square( (screen_x, screen_y),
(0.5*GRID_SIZE, 0.5*GRID_SIZE),
icolor = COLOR_RED,
ocolor = COLOR_WHITE,
filled = 1,
width = 1,
smooth = 0)
text( (screen_x, screen_y), COLOR_WHITE, 'HIT!', DISPLAY_FONT, FONT_LARGE , 'bold', anchor = 'c')
elif result == 'miss':
sq = square( (screen_x, screen_y),
(0.5*GRID_SIZE, 0.5*GRID_SIZE),
icolor = COLOR_GREEN,
ocolor = COLOR_WHITE,
filled = 1,
width = 1,
smooth = 0)
text( (screen_x, screen_y), COLOR_WHITE, 'MISS', DISPLAY_FONT, FONT_LARGE, 'bold', anchor = 'c')
for n, pos in enumerate(ghosts):
self.drawGhost(pos, n)
msg = 'game over [click to close]'
self.infoPane.updateMessages(msg)
wait_for_click()
def showBeliefs(self, locations, beliefs, ghosts):
nGhostDrawn = 0;
for pos in locations:
if beliefs.has_key(pos):
belief = beliefs[pos]
else:
belief = 0
row, col = pos
(screen_x, screen_y) = self.to_screen((row, col))
# heat map
scaled_belief = belief
scaled_belief = min(scaled_belief, 1.0)
scaled_belief = max(scaled_belief, 1e-20)
scaled_belief = 1.0 / (1.0 - math.log(scaled_belief))
#scaled_belief = scaled_belief ** 0.5
#heat_color = formatColor(0,0,scaled_belief)
heat_color = formatColor(scaled_belief/1.1,0, (1-scaled_belief)**2)
if SHOW_BELIEFS:
sq = square( (screen_x, screen_y),
(0.40*GRID_SIZE, 0.40*GRID_SIZE),
icolor = heat_color,
ocolor = heat_color,
filled = 1,
width = 1,
smooth = 0)
if SHOW_GHOSTS and pos in ghosts:
self.drawGhost(pos, nGhostDrawn)
nGhostDrawn += 1
# belief values
if SHOW_BELIEFS:
belief_text = '%0.2f' % belief
if belief < 0.01:
belief_text = '<0.01'
text( (screen_x, screen_y), COLOR_WHITE, belief_text, DISPLAY_FONT, FONT_LARGE, "bold", anchor = 'c')
def drawGhost(self, pos, nGhostDrawn):
screen_x, screen_y = self.to_screen(pos)
#sq = square( (screen_x, screen_y),
# (0.25*GRID_SIZE, 0.25*GRID_SIZE),
# icolor = COLOR_BLACK,
# ocolor = COLOR_BLACK,
# filled = 1,
# width = 1,
# smooth = 1)
coords = []
for (x, y) in GHOST_SHAPE:
coords.append((x*0.7*GRID_SIZE*GHOST_SIZE + screen_x, y*0.7*GRID_SIZE*GHOST_SIZE + screen_y - GRID_SIZE*0.0 ))
colour = GHOST_COLORS[nGhostDrawn]
body = polygon(coords, colour, colour, filled = 1)
WHITE = formatColor(1.0, 1.0, 1.0)
BLACK = formatColor(0.0, 0.0, 0.0)
dx = 0
dy = 0
leftEye = circle((screen_x+0.7*GRID_SIZE*GHOST_SIZE*(-0.24+dx/1.5), screen_y-0.7*GRID_SIZE*GHOST_SIZE*(0.31-dy/1.5)), 0.7*GRID_SIZE*GHOST_SIZE*0.1, WHITE, WHITE)
rightEye = circle((screen_x+0.7*GRID_SIZE*GHOST_SIZE*(0.24+dx/1.5), screen_y-0.7*GRID_SIZE*GHOST_SIZE*(0.31-dy/1.5)), 0.7*GRID_SIZE*GHOST_SIZE*0.1, WHITE, WHITE)
leftPupil = circle((screen_x+0.7*GRID_SIZE*GHOST_SIZE*(-0.24+dx), screen_y-0.7*GRID_SIZE*GHOST_SIZE*(0.31-dy)), 0.7*GRID_SIZE*GHOST_SIZE*0.04, BLACK, BLACK)
rightPupil = circle((screen_x+0.7*GRID_SIZE*GHOST_SIZE*(0.24+dx), screen_y-0.7*GRID_SIZE*GHOST_SIZE*(0.31-dy)), 0.7*GRID_SIZE*GHOST_SIZE*0.04, BLACK, BLACK)
def to_screen(self, point):
( row, col ) = point
x = col*GRID_SIZE + MARGIN
y = row*GRID_SIZE + MARGIN
return ( x, y )
def win(self, state):
msg = 'YOU WIN! [click to close]'
self.infoPane.updateMessages(msg)
wait_for_click()
def pauseGUI(self):
wait_for_click()
def square(pos, size, icolor, ocolor, filled, width, smooth=0):
x, y = pos
dx, dy = size
return polygon([(x - dx, y - dy), (x - dx, y + dy), (x + dx, y + dy), (x + dx, y - dy)], ocolor, icolor, filled, smooth)
def squares(pos, size, icolor, ocolor, filled, width, smooth=0, count=1):
for i in range(1, int(count)+1):
square(pos,(size[0]-i,size[1]-i),icolor,ocolor,filled,width)
def getEvent(point):
(x, y) = point
if GRID_POSITION.contains(point):
row = int ((y - MARGIN + GRID_SIZE * 0.5) / GRID_SIZE)
col = int ((x - MARGIN + GRID_SIZE * 0.5) / GRID_SIZE)
return 'grid', (row, col)
elif BUTTON_BUST.contains(point):
if BUTTON_BUST.status != 'clickable':
return None, None
return 'bust-button', None
elif BUTTON_TIME.contains(point):
if BUTTON_TIME.status != 'clickable':
return None, None
return 'time-button', None
return None, None