world_creation.py :  » IDE » RUR » rurple1.0rc3 » rur_py » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » IDE » RUR 
RUR » rurple1.0rc3 » rur_py » world_creation.py
""" RUR-PLE: Roberge's Used Robot - a Python Learning Environment
    world_creation.py - See description below
    Version 0.8.7
    Author: Andre Roberge    Copyright  2005
    andre.roberge@gmail.com

    world_creation.py includes two classes:

    World(), which incorporates the logic of the world in which the robot moves.

    Visible_world() which is derived from World() and contains all the
    visual representation (colours and sizes of walls, beepers, etc.).
    """
import wx
from rur_py.translation import _
import robot_factory

#---------------------------------------------------------------------------


class Singleton(object):
    """From the 2nd edition of the Python cookbook."""
    def __new__(cls, *args, **kwargs):
        if '_inst' not in vars(cls):
            cls._inst = object.__new__(cls, *args, **kwargs)
        return cls._inst

class World(Singleton):
    """ This class defines the world's logic.  The world representation on
        the computer screen is as follows:
                  11111111112
        012345678901234567890
        #####################0
        #                   #1
        # # # # # # # # # # #2
        #                   #3
        # #@#@# # # # # # # #4
        # @   @             #5
        # # # # # # # # # # #6
        #                   #7
        # # # # # # # # # # #8
        #                   #9
        # # # # # # # # # # #10
        #                   #11
        #####################12

        where "#" represents a built-in "tile"  (outside wall or
        "anchor point" from which a wall is attached) and "@" represents
        a user-added tile/wall.

        A robot is allowed to move horizontally or vertically along "streets"
        or "avenues".  In the above drawing, street 1 would be on row 11,
        street 2 would be on [screen ]row 9, etc.
        (the row numbers are on the right of the drawing).
        The column/row above follows the usual computer graphical
        convention, with the origin at the TOP left corner.
        Avenues/streets follow the usual mathematical convention with the
        origin at the BOTTOM left corner.
        Thus, avenue 1 would be on column 1, avenue 2 would be on column 3, etc.

        In general, we will have avenue n on column 2n-1.
        Similarly, we will have street n on [screen] row (max_row) - (2n-1).

        User-defined walls can only occur on (even row, odd column) or
        (odd row, even column).  For example, in the above diagram, we have
        user-defined walls, indicated by @, on
        (5, 2), (5, 6), (4, 3) and (4, 5).

        Outside walls are on column 0 and some even-numbered column, as well
        as on row 0 and some even-numbered row.  We thus have an odd number
        of both rows and columns.
        """


    walls_list = []
    borders = []
    beepers_dict = {}
    def __init__(self, avenues=10, streets=10, walls = [], beepers = {},
                  robot = {}):
        self.av = avenues
        self.st = streets
        self.robot_dict = robot
        self.robot_number = 0
        self.num_cols = 2*avenues + 1
        self.num_rows = 2*streets + 1
        World.walls_list = walls
        for (col, row) in World.walls_list:
            if not (col+row)%2:   # old "debug" statement; kept in case
                print "Wall in impossible position"
                print "col =", col, "row =", row
                print "Please contact manufacturer (or check your input file)."
        World.beepers_dict = beepers
        self.setBorders()

    def setBorders(self):
        """ setBorder(self): The world is surrounded by a continuous wall.
            This function sets the corresponding "wall list" or "border"
            based on the world's dimensions."""
        for col in range(1, self.num_cols-1, 2):
            if (col, 0) not in World.borders:
                World.borders.append( (col, 0) )
            if (col, self.num_rows) not in World.borders:
                World.borders.append( (col, self.num_rows-1) )
        for row in range(1, self.num_rows-1, 2):
            if (0, row) not in World.borders:
                World.borders.append( (0, row) )
            if (self.num_cols, row) not in World.borders:
                World.borders.append( (self.num_cols-1, row) )

    def resetDimensions(self, avenues=10, streets=10):
        """ resetDimensions(self, avenues=10, streets=10):
            This is to allow changing world dimensions without
            having to restart the program.  It also removes
            all user-defined walls and beepers."""
        self.av = avenues
        self.st = streets
        self.num_cols = 2*avenues + 1
        self.num_rows = 2*streets + 1
        World.borders = []
        World.walls_list = []
        World.beepers_dict = {}
        self.setBorders()

    def ToggleWalls(self, col, row):
        """ ToggleWalls(self, col, row):
            This function is intended for adding or removing a
            wall from a GUI world editor."""
        if (col+row)%2 :  # safety check
            if (col, row) in World.walls_list: #toggle value
                World.walls_list.remove((col, row))
            else:
                World.walls_list.append((col, row))
        else:     # "old debug" statement; kept in case
            print "Wall in impossible position."
            print "col =", col, "row =", row
            print "Please contact manufacturer with bug report."

    def setBeepers(self, (av, st), nb):
        """ setBeepers(self, (av, st), nb):
            This function is intended to set the number of beepers from a
            GUI world editor."""
        if (av, st) in World.beepers_dict:
            if nb == 0:
                del World.beepers_dict[(av, st)]
            else:
                World.beepers_dict[(av, st)] = nb
        elif nb > 0:
            World.beepers_dict[(av, st)] = nb
        else:     # old "debug" statement; kept in case
            pass
            #print "Attempting to set the number of beepers to a negative "
            #print "value using setBeepers() in World().  This should never "
            #print "happen.  Please contact manufacturer with bug report."

    def isClear(self, col, row):
        """ isClear(self, col, row):
            This function informs the user (robot) whether or not a given
            combination of (col, row) is Clear i.e. if there is no wall
            or border there."""
        if (col, row) in World.walls_list:
            return False
        if (col, row) in World.borders:
            return False
        else:
            return True

    def addOneBeeper(self, av, st):
        """ addOneBeeper(self, av, st):
            This function is intended for adding a single
            beeper in the world from a robot put_beeper() command."""
        if (av, st) in World.beepers_dict:
            World.beepers_dict[(av, st)] += 1
        else:
            World.beepers_dict[(av, st)] = 1

    def removeOneBeeper(self, av, st):
        """ removeOneBeeper(self, av, st):
            This function is intended for removing a single
            beeper in the world from a robot pick_beeper() command.
            The robot command should first check to make sure there is
            a beeper to be removed by using getBeepers."""
        if (av, st) in World.beepers_dict:
            World.beepers_dict[(av, st)] -= 1
            if World.beepers_dict[(av, st)] == 0:
                del World.beepers_dict[(av, st)]
        else:     # old "debug" statement; kept in case
            pass
            #print "Attempting to remove a beeper using method "
            #print "removeOneBeepers() in World where there is no beeper."
            #print "This should not occur."
            #print "Please contact manufacturer with a bug report!"

    def addOneRobot(self, avenues=1, streets=1, orient_key = 'E',
                 beepers=0, name = None, colour = 'grey', better = False):
        """ addOneRobot(self, av, st, orient, beep, name, colour):
            This function is intended for adding a single robot."""
        if name is None:
            self.robot_number += 1
            name = "Robot" + str(self.robot_number)
        while name in self.robot_dict:
            name += str(self.robot_number)
        if not better:
            self.robot_dict[name] = robot_factory.Used_robot(
                   avenues, streets, orient_key, beepers, name, colour, self)
        else:
            self.robot_dict[name] = robot_factory.New_improved_robot(
                   avenues, streets, orient_key, beepers, name, colour, self)
        return self.robot_dict[name]

class Visible_world(World):
    """ Visible_world extends World() by adding method to draw a representation
        of that world.  It comes with default values that should be sufficient
        for all practical purpose but nonetheless allows for a large degree
        of customization. """
    #--- Initialisation
    def __init__(self, avenues=10, streets=10, walls = [],
                 beepers = {}, robot = {},
                editWalls = False,
                screen_offsets = (50, 50, 20, 40),
                tile_info = (34, 6),
                beeper_info = (20, 13, 6, 3),
                wall_colours = ("black", "brown"),
                edit_wall_colours = ("brown", "black"),
                grid_colour = "light grey",
                wall_grid_colour = "brown",
                beeper_outside_colour = "cadet blue",
                beeper_inside_colour = "white",
                beeper_number_colour = "black"
                ):

        World.__init__(self, avenues, streets, walls, beepers, robot)

        # world positioning on "screen"
        self.xOffset = screen_offsets[0] # left
        self.yOffset = screen_offsets[1] # bottom
        self.yTopOffset = screen_offsets[2] # top
        self.right_scroller_space = screen_offsets[3] # right; leaving enough
        #room so that the right boundary is never hidden under the scrollbar.

        # rectangular walls or "tiles"
        self.tile_wide = tile_info[0]
        self.tile_narrow = tile_info[1]

        # the following 4 values are approximate values, used to center
        # beepers and beeper numbers, obtained through trial and error, based
        # on self.tile_wide=34, self.tile_narrow = 6
        self.beeper_radius = beeper_info[0]
        self.beeper_offset = beeper_info[1]
        self.beep_single_digit = beeper_info[2]
        self.beep_double_digit = beeper_info[3]

        # Walls are rectangles of a given colour, filled with a second.
        self.wall_outside_colour = wall_colours[0]

        self.wall_inside_colour = wall_colours[1]
        # allows for the possibility of using different colour when editing
        self.edit_wall_outside_colour = edit_wall_colours[0]
        self.edit_wall_inside_colour = edit_wall_colours[1]
        self.editWalls = editWalls

        self.grid_colour = grid_colour
        self.wall_grid_colour = wall_grid_colour

        self.beeper_outside_colour = beeper_outside_colour
        self.beeper_inside_colour = beeper_inside_colour
        self.beeper_number_colour = beeper_number_colour

        self.background_colour = wx.Brush('white')

        # we will create two basic world images and
        # then set this flag to True; if world dimension changes, we will
        # reset it to False
        self.background_images_created = False
        self.AdjustWorldSize()
        self.InitTileSizes()

        self.object_dict = {} # keeps track or robots

    def AdjustWorldSize(self):
        """ Computes the width and height of the display based on chosen
            parameters."""
        self.maxWidth = (self.num_cols - 1) * (
                        self.tile_wide + self.tile_narrow
                )/2 + self.tile_narrow + self.xOffset \
                         + self.right_scroller_space
        self.maxHeight = (self.num_rows - 1) * (
                        self.tile_wide + self.tile_narrow
                    )/2 + self.tile_narrow + self.yOffset + self.yTopOffset

    def InitTileSizes(self):
        """ Creates a two dimensional array of tiles containing the
            size information."""
        self.tiles_data = [[0 for row in range(self.num_rows)] \
                       for col in range(self.num_cols)]
        ns = self.tile_narrow # temporary variable easier to read in equations
        ws = self.tile_wide   # temporary variable easier to read in equations
        for col in range(0, self.num_cols):
            for row in range(0, self.num_rows):
                x = (col//2)*(ns+ws) + (col%2)*ns + self.xOffset
                y = (row//2)*(ns+ws) + (row%2)*ns + self.yTopOffset
                if col%2:
                    x_side = ws + 2*ns
                    x -= ns
                else:
                    x_side = ns
                if row%2:
                    y_side = ws + 2*ns
                    y -= ns
                else:
                    y_side = ns
                self.tiles_data[col][row] = (x, y, x_side, y_side)

    #--- World changes from Graphical World Editor

    def CalculatePosition(self, x, y):
        """ Computes the corresponding (column, row) value from an (x, y)
            world coordinate."""
        tilePair = self.tile_narrow + self.tile_wide
        x -= self.xOffset
        y -= self.yTopOffset
        numberOfWideAndNarrow = x//tilePair
        if (x - numberOfWideAndNarrow*tilePair < self.tile_narrow):
            col = 2*numberOfWideAndNarrow
        else:
            col = 2*numberOfWideAndNarrow + 1
        numberOfWideAndNarrow = y//tilePair
        if (y - numberOfWideAndNarrow*tilePair < self.tile_narrow):
            row = 2*numberOfWideAndNarrow
        else:
            row = 2*numberOfWideAndNarrow + 1
        row = self.flipRow(row)
        return col, row

    # remember that row 0 on screen is on top; in RUR world, it is on bottom
    def flipRow(self, row):
        """ On the screen, row 0 is at the top.  To facilitate
            the calculation of row <---> street, we flip the rows
            in RUR world so that row 0 is at the bottom."""
        return self.num_rows - row - 1

    def ChangeWall(self, col, row):
        """ After "clicking" a point on the screen, we check if it corresponds
            to a wall area and, if so, adds a wall if there is none, or
            remove it if there is one."""
        if not self.editWalls: return
        if col == 0 or row == 0 or \
           col == self.num_cols -1 or \
           row == self.num_rows - 1 :
            return    # we are on the border

        # wall exists when (col, row) is either (odd, even) or (even, odd)
        if (row+col)%2:
            self.ToggleWalls(col, row)
            self.DoDrawing()

    def MoveRobot(self, name):
        """ Change the robot position on the screen."""
        self.robot_dict[name].move()
        self.DoDrawing()

    def TurnRobotLeft(self, name):
        """ Change the robot orientation on the screen."""
        self.robot_dict[name].turn_left()
        self.DoDrawing()

    def TurnRobotRight(self, name):
        """ Change the robot orientation on the screen;
            only New_improved_robot can turn right."""
        self.robot_dict[name].turn_right()
        self.DoDrawing()

    def setBeepers(self, (av, st), nb):
        """ Sets the number of beepers at a given intersection from
            the Graphical world builder; robot induced changes
            are dealt with in class World."""
        World.setBeepers(self, (av, st), nb)
        self.DoDrawing()

    #--- Drawing routines : "background"

    def DrawBorders(self, dc):
        if self.editWalls:
            dc.SetPen(wx.Pen(self.edit_wall_outside_colour,1))
            dc.SetBrush(wx.Brush(self.edit_wall_inside_colour))
        else:
            dc.SetPen(wx.Pen(self.wall_outside_colour,1))
            dc.SetBrush(wx.Brush(self.wall_inside_colour))
        rects = []
        ### 4 corners
        rects.append( self.tiles_data[0][0] )
        rects.append( self.tiles_data[self.num_cols-1][0] )
        rects.append( self.tiles_data[0][self.num_rows-1] )
        rects.append( self.tiles_data[self.num_cols-1]
                      [self.num_rows-1] )
        for (col, row) in World.borders:
            rects.append(self.tiles_data[col][row]) #no need to flip; symmetry!
        if self.editWalls:
            for col in range(2, self.num_cols-1, 2):
                for row in range(2, self.num_rows-1, 2):
                        rects.append( self.tiles_data[col][row] )
        dc.DrawRectangleList(rects)

    def DrawGrid(self, dc):
        dc.SetPen(wx.Pen(self.grid_colour, 1, wx.DOT))
        tilePair = self.tile_narrow + self.tile_wide
        for row in range(1, self.num_rows - 1, 2):
            y = row*tilePair/2 + self.tile_narrow/2 + self.yTopOffset
            dc.DrawLine(self.xOffset, y,
                        self.maxWidth - self.right_scroller_space, y)
        for col in range(1, self.num_cols - 1, 2):
            x = col*tilePair/2 + self.tile_narrow/2 + self.xOffset
            dc.DrawLine(x, self.tile_narrow + self.yTopOffset, x,
                        self.maxHeight - self.yOffset)
        if self.editWalls:
            dc.SetPen(wx.Pen(self.wall_grid_colour, 1, wx.DOT))
            for row in range(2, self.num_rows - 1, 2):
                y = row*tilePair/2 + self.tile_narrow/2 + self.yTopOffset
                dc.DrawLine(self.xOffset, y,
                            self.maxWidth - self.right_scroller_space, y)
            for col in range(2, self.num_cols - 1, 2):
                x = col*tilePair/2 + self.tile_narrow/2 + self.xOffset
                dc.DrawLine(x, self.tile_narrow + self.yTopOffset, x,
                            self.maxHeight - self.yOffset)

    def DrawLabels(self, dc):
        tilePair = self.tile_narrow + self.tile_wide
        x_shift = self.xOffset//2
        y_shift = -self.yOffset
        if self.editWalls:
            # wx.SWISS is the sans-serif font
            dc.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL))
            pixelShift = dc.GetTextExtent("8")   # try to centre the labels
            dc.SetTextForeground(self.wall_grid_colour)
            for row in range(self.num_rows):
                y = row*tilePair/2 - pixelShift[1]//4 + self.yTopOffset
                dc.DrawText(str(self.flipRow(row)), x_shift, y)
            for col in range(self.num_cols - 1):
                x = (col*tilePair/2 + self.tile_narrow/2
                        + self.xOffset - pixelShift[0]//2)
                dc.DrawText(str(col), x, self.maxHeight + y_shift)
            x_shift = self.xOffset//4
            y_shift = -self.yOffset//2
        else:
            dc.SetTextForeground(wx.BLACK)
            dc.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
            dc.DrawText(_("Avenue"), 5*x_shift,
                        self.maxHeight + y_shift//2)
            dc.DrawRotatedText(_("Street"), x_shift//3,
                               self.maxHeight + 3*y_shift , 90)
        dc.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD))
        pixelShift = dc.GetTextExtent("8")   # try to centre the labels
        dc.SetTextForeground(wx.BLACK)
        for row in range(1, self.num_rows, 2):
            y = row*tilePair/2 - pixelShift[1]//4 + self.yTopOffset
            dc.DrawText(str(self.flipRow(row)//2 + 1), x_shift, y)
        for col in range(1, self.num_cols, 2):
            x = (col*tilePair/2 + self.tile_narrow/2
                   + self.xOffset - pixelShift[0]//2)
            dc.DrawText(str(col//2+1), x, self.maxHeight + y_shift)


    def DrawBackground(self, dc):
        """ Creates two background images, one for normal robot motion,
            the other for editing walls.  This is done to speed up the
            drawing process following a change in the world through a
            robot action.  These two background images need to be
            recreated if the world dimensions change."""
        if not self.background_images_created:
            saved_flag = self.editWalls
            self.editWalls = False
            self.image_noEditWalls = wx.EmptyBitmap(self.maxWidth,
                                                   self.maxHeight)
            offDC = wx.MemoryDC()
            offDC.SelectObject(self.image_noEditWalls)

            offDC.SetBackground(self.background_colour)

            offDC.Clear()
            self.DrawGrid(offDC)
            self.DrawLabels(offDC)
            self.DrawBorders(offDC)
            ## first image completed
            self.editWalls = True
            self.image_EditWalls = wx.EmptyBitmap(self.maxWidth, self.maxHeight)
            # Selecting a new image in offDC; this releases the first one
            # for further use.
            offDC.SetBackground(self.background_colour)
            offDC.SelectObject(self.image_EditWalls)
            offDC.Clear()
            self.DrawGrid(offDC)
            self.DrawLabels(offDC)
            self.DrawBorders(offDC)
            # We are finished with offDC.  We must release its link with
            # the second image
            del offDC
            ## second image completed
            self.editWalls = saved_flag
            self.background_images_created = True
        if self.editWalls:
            dc.DrawBitmap(self.image_EditWalls, 0, 0, True)
        else:
            dc.DrawBitmap(self.image_noEditWalls, 0, 0, True)

    #--- Drawing routines : "foreground"

    def DrawTrace(self, dc, name):
        """ Draws a line that shows the path taken by the robot."""
        tilePair = self.tile_narrow + self.tile_wide
        yOffset = self.tile_narrow/2 + self.yTopOffset
        xOffset = self.tile_narrow/2 + self.xOffset
        for line in self.robot_dict[name].line_trace:
            x_0, y_0, x_1, y_1, orientation, style, colour = line
            width, trace_offset = self.robot_dict[name].get_trace_style(style)
            dc.SetPen(wx.Pen(colour, width))
            X0, Y0 = trace_offset[orientation]
            if (x_0 == x_1) and (y_0 == y_1):    #turning left
                X1, Y1 = trace_offset[(orientation+1)%4]
            else:
                X1, Y1 = X0, Y0
            col_0 = 2*x_0 - 1
            col_1 = 2*x_1 - 1
            row_0 = self.flipRow(2*y_0 - 1)
            row_1 = self.flipRow(2*y_1 - 1)
            y0 = row_0*tilePair/2 + yOffset + Y0
            y1 = row_1*tilePair/2 + yOffset + Y1
            x0 = col_0*tilePair/2 + xOffset + X0
            x1 = col_1*tilePair/2 + xOffset + X1
            dc.DrawLine(x0, y0, x1, y1)

    def DrawRobot(self, dc, name):
        avenue, street = self.robot_dict[name].getPos()
        row = self.flipRow(2*street -1)
        col = 2*avenue -1
        # irrelevant = tile size
        x, y, irrelevant, irrelevant2 = self.tiles_data[col][row]
        xx, yy = self.robot_dict[name].imageOffset
        x += xx
        y += yy
        self.robot_image_origin = (x, y) # for use in automatic scrolling
        dc.DrawBitmap(self.robot_dict[name].robot_image, x, y, True)

    def DrawWalls(self, dc):
        if self.editWalls:
            dc.SetPen(wx.Pen(self.edit_wall_outside_colour,1))
            dc.SetBrush(wx.Brush(self.edit_wall_inside_colour))
        else:
            dc.SetPen(wx.Pen(self.wall_outside_colour,1))
            dc.SetBrush(wx.Brush(self.wall_inside_colour))
        rects = []
        for (column, row) in World.walls_list :
                rects.append( self.tiles_data[column][self.flipRow(row)])
        dc.DrawRectangleList(rects)

    def DrawBeepers(self, dc):
        dc.SetPen(wx.Pen(self.beeper_outside_colour,3))
        dc.SetBrush(wx.Brush(self.beeper_inside_colour))
        circles = []
        points = []
        nb = []
        for (avenue, street) in World.beepers_dict :
            row = self.flipRow(2*street -1)
            col = 2*avenue -1
            # irrelevant = tile size
            x, y, irrelevant, irrelevant2 = self.tiles_data[col][row]
            x += self.beeper_offset
            y += self.beeper_offset
            circles.append( (x, y, self.beeper_radius, self.beeper_radius) )
            if World.beepers_dict[(avenue, street)] < 10:
                points.append((x+self.beep_single_digit, y+2))
            else:
                points.append((x+self.beep_double_digit, y+2))
            nb.append(str(World.beepers_dict[(avenue, street)]))
        dc.DrawEllipseList(circles)
        dc.SetTextForeground(self.beeper_number_colour)
        dc.DrawTextList(nb, points, None, None)

    #--- Drawing routine: "background AND foreground"

    def DoDrawing(self):
        self.world_image = wx.EmptyBitmap(self.maxWidth, self.maxHeight)
        dc = wx.MemoryDC()
        dc.SelectObject(self.world_image)
        dc.Clear()
        dc.BeginDrawing()
        self.DrawBackground(dc)
        self.DrawWalls(dc)
        list_to_delete = []
        for name in self.robot_dict:  # draw all traces
            self.DrawTrace(dc, name)
        self.DrawBeepers(dc)
        for item in self.object_dict:  # see if robot still exists in cpu.py
            if not self.object_dict[item]:
                list_to_delete.append(item)
        for name in self.robot_dict:
            if name not in list_to_delete: # don't draw out-of-scope robots
                self.DrawRobot(dc, name)
        dc.EndDrawing()
        self.updateImage = True
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.