""" RUR-PLE: Roberge's Used Robot - a Python Learning Environment
world_display.py - Displays "world" with "robot" and allow
keyboard-based interactions
Version 0.8.7
Author: Andre Roberge Copyright 2005
andre.roberge@gmail.com
"""
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import wx
from world_creation import Visible_world
from robot_factory import Used_robot,New_improved_robot
import dialogs
class WorldGUI(wx.ScrolledWindow):
def __init__(self, parent, id = -1, size = wx.DefaultSize):
wx.ScrolledWindow.__init__(self, parent, id, (0, 0), size=size,
style=wx.SUNKEN_BORDER)
self.SetBackgroundColour("WHITE")
self.world = Visible_world()
self.editor = False #
self.InitialiseVariables()
self.MakePopupMenu()
self.bindEvents()
self.SetFocus()
def InitialiseVariables(self):
# Create the world image
self.world.updateImage = False
self.world.DoDrawing()
# Initialize the buffer bitmap. No real DC is needed at this point.
self.buffer = wx.EmptyBitmap(self.world.maxWidth, self.world.maxHeight)
self.drawImage()
# Set the size of the total window, of which only a small part
# will be displayed; apparently SetVirtualSize needs
# a single (tuple) argument, which explains the double (( )).
self.SetVirtualSize((self.world.maxWidth, self.world.maxHeight))
# Set the scrolling rate; use same value in both horizontal and
# vertical directions.
scrollRate = self.world.tile_narrow + self.world.tile_wide
self.SetScrollRate(scrollRate, scrollRate)
# yPos below set so that we are at the bottom of the scrolledWindow
self.SetScrollbars(1, 1, self.world.maxWidth,
self.world.maxHeight, yPos=self.world.maxHeight)
# allow for testing "new and improved" robot
self.newRobot = False
def bindEvents(self):
wx.EVT_PAINT(self, self.OnPaint)
wx.EVT_LEFT_DOWN(self, self.OnLeftDown) # for "drawing"
wx.EVT_CHAR(self, self.MyKeys) # to test Robot actions
wx.EVT_IDLE(self, self.OnIdle)
wx.EVT_RIGHT_UP(self, self.OnRightUp)
def OnIdle(self, event=None):
"""
Do any needed updating on Idle time ...
"""
if self.world.updateImage:
self.drawImage()
self.world.updateImage = False
def OnPaint(self, event=None):
try:
dc = wx.BufferedPaintDC(self, self.buffer, wx.BUFFER_VIRTUAL_AREA)
except:
dc = wx.BufferedPaintDC(self, self.buffer) # before wxPython 2.5.4.1
def isActive(self, event):
xView, yView = self.GetViewStart()
xDelta, yDelta = self.GetScrollPixelsPerUnit()
self.x, self.y = event.GetPositionTuple()
self.x += xView*xDelta
self.y += yView*yDelta
# Test to see if in "active" zone i.e. inside world borders
if (self.x > self.world.xOffset and
self.y > self.world.yTopOffset and
self.y < self.world.maxHeight - self.world.yOffset and
self.x < self.world.maxWidth - self.world.right_scroller_space):
return True
else:
return False
def OnLeftDown(self, event):
"""Called when the left mouse button is pressed and build
or remove walls."""
self.SetFocus()
if self.isActive(event):
col, row = self.world.CalculatePosition(self.x, self.y)
self.world.ChangeWall(col, row)
if self.world.updateImage:
self.drawImage()
self.Refresh()
else:
pass # added for clarity; we are on the outside
def MakePopupMenu(self):
"""Make a menu for beepers that can be popped up later;
this menu will offer the possibility of
putting a given number of beepers
at the current location (street/avenue intersection)."""
menu = wx.Menu()
self._beeperChoice = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, \
11, 12, 13, 14, 15, 20, 40, 60, 80, 99]
maxNumberOfBeepers = len(self._beeperChoice)
# This version is revised from 0.9.1 to avoid "hard-wired"
# IDs in appending the menu and let wxPython decide on suitable
# values. The previous version caused problems on MacOS.
# Note that OnMenuSetBeepers() had to be modified as well.
self.popID = []
for index, beepers in enumerate(self._beeperChoice):
self.popID.append(wx.NewId())
menu.Append(self.popID[index], str(beepers))
self.Bind(wx.EVT_MENU, self.OnMenuSetBeepers,
id=self.popID[index])
self.menu = menu
def OnMenuSetBeepers(self, event):
self._num = self._beeperChoice[self.popID.index(event.GetId())]
def OnRightUp(self, event):
"""called when the right mouse button is released,
will call the popup menu, offering the possibility of
putting a given number of beepers
at the current location (street/avenue intersection)."""
if self.isActive(event):
col, row = self.world.CalculatePosition(self.x, self.y)
if row%2:
if col%2:
pt = event.GetPosition()
self.PopupMenu(self.menu, pt)
av = (col+1)/2
st = (row+1)/2
self.world.setBeepers((av, st), self._num)
if self.world.updateImage:
self.drawImage()
self.Refresh()
else:
pass # added for clarity; we are on the outside
def MyKeys(self, event):
message = """
F5: Help (i.e. this message)
Editing world:
lower case e: toggle in/out of edit walls mode
Left mouse button: add or remove walls (if in edit walls mode)
Right mouse button: put beepers at intersection
Robot actions:
Up arrow: move robot forward
Left arrow: turn robot left
lower case p: pick_beeper
upper case P: put_beeper
Errors will occur if you attempt to move through a wall,
put a beeper when you carry none,
or try to pick up a beeper where there is none.
For testing only (future features):
F7: creates New_improved_robot
Right arrow: turn robot right (only New_improved_robot)
F8: creates Used_robot (default)"""
code = event.GetKeyCode()
if code == wx.WXK_UP: # up arrow
if 'robot' in self.world.robot_dict:
try:
self.world.MoveRobot('robot') # may raise an exception
self.scrollWorld('robot')
except dialogs.HitWallException, mesg:
dialogs.DialogHitWallError(mesg)
elif code == wx.WXK_LEFT: # left arrow
self.world.TurnRobotLeft('robot')
elif code == 112: # p - lower case
if 'robot' in self.world.robot_dict:
try:
self.world.robot_dict['robot'].pick_beeper()
if self.editor:
self.editor.DestroyChildren()
wx.StaticText(self.editor, -1, self.UpdateEditor(), (10, 10))
except dialogs.PickBeeperException, mesg:
dialogs.DialogPickBeeperError(mesg)
elif code == 80: # P - upper case
if 'robot' in self.world.robot_dict:
try:
self.world.robot_dict['robot'].put_beeper()
if self.editor:
self.editor.DestroyChildren()
wx.StaticText(self.editor, -1, self.UpdateEditor(), (10, 10))
except dialogs.PutBeeperException, mesg:
dialogs.DialogPutBeeperError(mesg)
elif code == 101: # e - lower case
if self.world.editWalls:
self.world.editWalls = False
self.world.DoDrawing()
else:
self.world.editWalls = True
self.world.DoDrawing()
elif code == wx.WXK_F5:
dialogs.rurMessageDialog(message, "Help :-)")
elif code == wx.WXK_F7:
self.world.robot_dict['robot'] = New_improved_robot(parent = self.world)
self.world.DoDrawing()
self.newRobot = True
elif code == wx.WXK_F8:
self.world.robot_dict['robot'] = Used_robot(parent = self.world)
self.world.DoDrawing()
self.newRobot = False
elif code == wx.WXK_RIGHT: # right arrow
if self.newRobot: # only New_improved_robot can turn right.
self.world.TurnRobotRight('robot')
else:
pass
else:
pass
if self.world.updateImage:
self.drawImage()
self.Refresh()
#- end of MyKeys()-----------------------------------------
def scrollWorld(self, name):
'''Determines if window needs scrolling and does necessary.'''
# determine robot image bounding box
x0, y0 = self.world.robot_image_origin
x1, y1 = self.world.robot_dict[name]._image_size
x1 += x0
y1 += y0
# marginal space around robot
margin = self.world.tile_narrow + self.world.tile_wide
margin *= 2
# position of top left visible window in "scrollrate" units
xView, yView = self.GetViewStart()
xViewOld, yViewOld = xView, yView
# corresponding amount of pixel per "scroll"
xDelta, yDelta = self.GetScrollPixelsPerUnit()
# size of wisible window
width, height = self.GetSizeTuple()
#-- Determine if window needs to be scrolled so that object
# remains visible. Assume that the object fits entirely
# in the visible view.
if x0 - margin < xView*xDelta:
xView = max(0, -1 + (x0-margin)/xDelta)
elif x1 + margin > xView*xDelta + width:
xView = (x1 + margin - width)/xDelta
if y0 - margin < yView*yDelta:
yView = max(0, -1 + (y0-margin)/yDelta)
elif y1 + margin > yView*yDelta + height:
yView = (y1 + margin - height)/yDelta
# if needed, scroll window by required amount to keep image in view
if xView != xViewOld or yView != yViewOld:
self.Scroll(xView, yView)
def drawImage(self):
dc = wx.BufferedDC(None, self.buffer)
dc.BeginDrawing()
# Simply copy the world image onto the buffer
dc.DrawBitmap(self.world.world_image, 0, 0, True)
# update the editor
if self.editor:
self.editor.DestroyChildren() # removes the old wx.StaticText
wx.StaticText(self.editor, -1, self.UpdateEditor(), (10, 10))
dc.EndDrawing()
def UpdateEditor(self):
av_string = "avenues = " + str(self.world.num_cols//2)
st_string = "streets = " + str(self.world.num_rows//2)
robot_string = ''
for name in self.world.robot_dict:
x, y = self.world.robot_dict[name].getPos()
beepers = self.world.robot_dict[name]._beeper_bag
orientation = self.world.robot_dict[name]._getOrientationKey()
robot_string += name + " = " + \
self.world.robot_dict[name]._getInfoString() + "\n"
if len(self.world.walls_list) > 0:
wall_string = "walls = [\n"
for item in self.world.walls_list:
wall_string = wall_string + (" " + str(item)
+ ', \n')
wall_string = wall_string[0:-3]+'\n]'
else:
wall_string = "walls = []"
if len(self.world.beepers_dict) > 0:
beeper_list = "beepers = {\n"
for key in self.world.beepers_dict:
beeper_list = beeper_list + (" " + str(key) + ': '
+ str(self.world.beepers_dict[key])+ ', \n')
beeper_list = beeper_list[0:-3]+'\n}'
else:
beeper_list = "beepers = {}"
worldmap = av_string + '\n' + st_string + '\n' + robot_string \
+ wall_string + '\n' + beeper_list
return worldmap
|