Beispiel #1
0
class Pacworld:
  def usage(self):
    print('USAGE:')
    print('{0} [options]'.format(sys.argv[0]))
    print('options are:')
    print('  -h --help    print this help')
    print('  -f        start fullscreen')
    print('  -s --seed=[number]    specify a seed for the random number generator')
    print('  -c --scale=[number]    create a map which is SCALE^2 times larger than the initial display resolution')
    print('  -a        begin in autonomous (self-playing) mode')
  
  def __init__(self, argv):
    logging.basicConfig(format='%(asctime)-15s:%(levelname)s:%(filename)s#%(funcName)s(): %(message)s', level=logging.DEBUG, filename='log/pacworld.log')
    logging.debug("Initializing Pacworld()...")

    # set defaults for CLI arguments
    SCALE_FACTOR = 3  # total map is SCALE_FACTOR^2 times the screen size
    self.crazySeed = None
    self.is_fullscreen = False
    self.start_autonomous = False
    
    try:
      opts, args = getopt.getopt(argv, "hs:c:fa", ["help", "seed=", "scale=", "fullscreen", "autonomous"])
    except getopt.GetoptError:
      self.usage()
      sys.exit(2)
    for opt, arg in opts:
      if opt in ("-h", "--help"):
        self.usage()
        sys.exit()
      elif opt in ("-s", "--seed"):
        self.crazySeed = int(arg)
        logging.info("USING CHOSEN SEED: {0}".format(self.crazySeed))
      elif opt in ("-c", "--scale"):
        SCALE_FACTOR = int(arg)
      elif opt in ("-f", "--fullscreen"):
        self.is_fullscreen = True
      elif opt in ("-a", "--autonomous"):
        self.start_autonomous = True
    
    # if no random seed was given, make one up:
    if self.crazySeed is None:
      self.newRandomSeed()
    
    # Initialize pygame
    pygame.init()
    # reset allowed types on the events queue
    pygame.event.set_allowed(None)
    pygame.event.set_allowed(QUIT)
    
    self.sound = getPacsound()
    
    # Create a clock to manage time
    self.clock = pygame.time.Clock()
    self.frametime = [] # array of recent frametimes, to calculate a rolling average
    self.frametime_idx = 0
    self.min_frametime = 1000
    self.max_frametime = 0
    
    # Initialize keyboard
    self.cur_kb_map = KB_MAP[KB_DVORAK]
    self.input_mode = [INPUT_KEYBOARD]
    pygame.event.set_allowed([KEYDOWN,KEYUP])
    
    # Initialize the joysticks (if present)
    pygame.joystick.init()
    
    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()
    if joystick_count == 0:
      logging.warning("no joysticks found, using only keyboard for input")
    else:
      joy_name = pygame.joystick.Joystick(0).get_name().strip()
      if(joy_name in GAMEPAD_BUTTON_MAP.keys()):
        logging.info("{} present, enabling joystick for input".format(joy_name))
        self.cur_button_map = GAMEPAD_BUTTON_MAP[joy_name]
        self.cur_pad_map = GAMEPAD_FUNCTION_MAP[joy_name]
        self.input_mode.append(INPUT_JOYSTICK)
        pygame.event.set_allowed([JOYBUTTONDOWN,JOYBUTTONUP,JOYAXISMOTION])
      else:
        logging.error("Joystick present, but not currently mapped, name='{}'".format(joy_name))
    
    
    if(INPUT_JOYSTICK in self.input_mode):
      self.pacjoy = Pacjoy(pygame.joystick.Joystick(0))
      self.button_status = []
      self.num_buttons = self.pacjoy.get_numbuttons()
      for i in range( self.num_buttons ):
        self.button_status.append(self.pacjoy.get_button(i))
      joy_axis_x = self.cur_pad_map['move_x']
      joy_axis_y = self.cur_pad_map['move_y']
      # check for variations on RPi
      if(joy_name == INPUT_GAMEPAD):
        if(pygame.joystick.Joystick(0).get_numaxes() == 2):
          joy_axis_x = 0
          joy_axis_y = 1
          logging.debug("Adjusting x/y axes for RPi: x={}, y={}".format(joy_axis_x, joy_axis_y))
      self.pacjoy.setXaxis(joy_axis_x)
      self.pacjoy.setYaxis(joy_axis_y)
    else:
      self.pacjoy = None
    
    # Set the window title
    pygame.display.set_caption("Flat Flip Friends")
    
    # capture current screen res for fullscreen mode
    self.fullscreen_resolution = (pygame.display.Info().current_w, pygame.display.Info().current_h)
    # set window size
    self.windowed_resolution = (800,600)
    # initialize display system
    if self.is_fullscreen:
      flags = pygame.FULLSCREEN
      self.display = Pacdisplay(self.fullscreen_resolution)
      pygame.mouse.set_visible(False)
    else:
      flags = 0
      self.display = Pacdisplay(self.windowed_resolution)
    self.character_size = 80 #int(self.display.getDisplaySize()[0] / 10)  #TODO: make this a configurable value (CLI arg?)
    logging.debug("Character size set to {0}".format(self.character_size))
    # Create the window
    self.surface = pygame.display.set_mode(self.display.getDisplaySize(), flags)
    
    ArtRenderer.renderArt(self.character_size)
    
    random.seed(self.crazySeed)
    
    self.mapSize = [SCALE_FACTOR*x for x in self.display.getDisplaySize()]
    
    gridSize = int(self.character_size * 1.5)
    self.gridDisplaySize = (int(self.mapSize[0] / gridSize), int(self.mapSize[1] / gridSize))  # assumes square grid cells
    logging.debug("gridDisplaySize is {0}".format(self.gridDisplaySize))

    self.last_worldgen = int(time.time())
    # generate world, map, and all the things in it
    self.generateWorld(self.start_autonomous)

    # play a "startup" sound
    self.sound.play('3robobeat')


  def newRandomSeed(self):
    self.crazySeed = random.randint(0, MAX_RANDOM_SEED)
    logging.info("USING RANDOM SEED: {0}".format(self.crazySeed))
    print("USING RANDOM SEED: {0}".format(self.crazySeed))


  def generateWorld(self, start_autonomous):
    now = int(time.time())
    worldage = now - self.last_worldgen
    logging.debug("World regenerated after {} seconds".format(worldage))
    self.last_worldgen = now
    # run garbage collection
    gc.collect()
    # log memory usage
    usage = resource.getrusage(resource.RUSAGE_SELF)
    logging.debug("Process memory usage is: {}".format(usage.ru_maxrss))
    
    self.surface.fill((0,0,0))
    # show notice
    font = pygame.font.Font(None, 52)
    textBitmap = font.render("Flat Flip Friends", True, colors.WHITE)
    textWidth = textBitmap.get_rect().width
    textHeight = textBitmap.get_rect().height
    self.surface.blit(textBitmap, [self.display.getDisplaySize()[0]/2 - textWidth/2, self.display.getDisplaySize()[1]/2 - textHeight*2])
    font = pygame.font.Font(None, 30)
    textBitmap = font.render("Generating world...", True, colors.WHITE)
    textWidth = textBitmap.get_rect().width
    self.surface.blit(textBitmap, [self.display.getDisplaySize()[0]/2 - textWidth/2, self.display.getDisplaySize()[1]/2])
    pygame.display.update()

    # Create the world, passing through the grid size
    theworld = World(self.gridDisplaySize)
    logging.debug("rendered world:\n{0}".format(theworld.to_s()))

    # Create the world map, passing through the display size and world map
    self.map = Map(self.mapSize, self.display, self.character_size, theworld)
    art = theworld.addArt(self.map)
    shapes = self.map.addShapes()
    self.sprites = pygame.sprite.Group(shapes, art)

    # Create the player object and add it's shape to a sprite group
    self.player = Player()
    self.player.selectShape(self.map.shapes[0])  # just grab the first shape for the player
    self.player.shape.autonomous = self.start_autonomous

    self.map.player = self.player

  def get_framespeed_info(self, clock):
    rawtime = clock.get_rawtime()
    if(len(self.frametime) < 30):
      self.frametime.append(rawtime)
    else:
      self.frametime[self.frametime_idx] = rawtime
    self.frametime_idx = (self.frametime_idx + 1) % 30
    average_frametime = int(sum(self.frametime) / len(self.frametime))
    if(average_frametime < self.min_frametime): self.min_frametime = average_frametime
    if(50 < pacglobal.get_frames() and average_frametime > self.max_frametime): self.max_frametime = average_frametime
    return str(self.min_frametime) + ' < ' + str(average_frametime) + ' < ' + str(self.max_frametime)


  def run(self):
    # Runs the game loop
    
    while True:
      # The code here runs when every frame is drawn
      curtime = pygame.time.get_ticks()
      #print "looping"
      
      # Handle Events
      self.handleEvents(curtime)
      
      # update the map
      self.map.update(curtime)
      
      # Update the sprites
      self.sprites.update(curtime)

      # Draw the background
      self.map.draw(self.surface)
      
      # draw the sprites
      #print "DEBUG: drawing shape via sprite group. shape rect is: {0}".format(self.shape.rect)
      # draw the shape by itself onto the display. it's always there.
      self.player.shape.draw(self.surface)
      windowRect = self.map.player.shape.getWindowRect()
      # NOTE: we only want to show the art that is currently onscreen, and it needs to be shifted to its correct position
      for artpiece in self.player.shape.art_onscreen():
        # if artpiece is on the screen, we will draw it
        #logging.debug("drawing art at {0}".format(artpiece.rect))
        artpiece.draw(self.surface, windowRect)
      
      # draw any other shapes that are currently onscreen
      for shape in self.map.shapes:
        # if artpiece is on the screen, we will draw it
        if not shape.onScreen(windowRect): continue
        #logging.debug("drawing shape {0} at {1}".format(shape.id, shape.mapTopLeft))
        shape.draw(self.surface)
      
      # check swirl saturation
      (total_swirls, num_shapes, swirl_saturation_pct) = self.map.getSwirlSaturationPercent()
      if swirl_saturation_pct >= pacdefs.MAX_SWIRL_SATURATION_PERCENT or pacdefs.MAX_WORLD_REGEN_TIME < time.time() - self.last_worldgen:
        self.newRandomSeed()
        self.generateWorld(self.player.shape.autonomous)
      elif pacdefs.DEBUG_NUMSWIRLS:
        # debug number of swirls
        font = pygame.font.Font(None, 30)
        textBitmap = font.render("{} swirls / {} shapes / {} %".format(total_swirls, num_shapes, swirl_saturation_pct), True, colors.WHITE)
        self.surface.blit(textBitmap, (10,10))
      
      # Update the full display surface to the screen
      pygame.display.update()
      
      # Limit the game to 30 frames per second
      self.clock.tick(30)

      # display debug if enabled
      if(pacdefs.DEBUG_FRAMESPEED):
        pygame.display.set_caption("fps: " + str(int(self.clock.get_fps())) + " | framespeed: " + self.get_framespeed_info(self.clock))

      # advance frame counter
      pacglobal.nextframe()


  def toggleFullscreen(self):
    screen = pygame.display.get_surface()
    bits = screen.get_bitsize()
    if self.is_fullscreen:
      self.is_fullscreen = False
      flags = screen.get_flags() & ~pygame.FULLSCREEN
      self.display.setDisplaySize(self.windowed_resolution)
    else:
      self.is_fullscreen = True
      flags = screen.get_flags() | pygame.FULLSCREEN
      self.display.setDisplaySize(self.fullscreen_resolution)
    pygame.display.quit()
    pygame.display.init()
    self.surface = pygame.display.set_mode(self.display.getDisplaySize(),flags,bits)
    pygame.mouse.set_visible(not self.is_fullscreen)
    self.player.shape.updatePosition()


  def doMenu(self):
    menu_choice = pacmenu.getMenu().dialog(self.surface, self.pacjoy)
    if menu_choice == pacmenu.MENU_POWEROFF:
      confirm = pacmenu.getConfirmMenu().dialog(self.surface, self.pacjoy)
      if confirm == pacmenu.MENU_CONFIRM:
        os.system("/sbin/poweroff")


  def handleEvents(self, ticks):
    # Handle events, starting with the quit event
    for event in pygame.event.get():
      if event.type == QUIT:
        pygame.quit()
        sys.exit()

      if(INPUT_KEYBOARD in self.input_mode and event.type == KEYDOWN and event.key == K_ESCAPE):
        pygame.quit()
        sys.exit()

      if self.player.shape.in_dance(): continue # ignore all player input while in dance

      if(INPUT_JOYSTICK in self.input_mode):
        # check for joystick movement
        joy_value_y = self.pacjoy.get_joy_axis_y()
        joy_value_x = self.pacjoy.get_joy_axis_x()
        logging.debug("joystick movement = {0},{1}".format(joy_value_x, joy_value_y))
        if(joy_value_y != 0 or joy_value_x != 0):
          self.player.notIdle(ticks)
        # -1 = left, 1 = right
        if(joy_value_y == 1):  # -1 = up, down = 1
          self.player.shape.startMove(DIR_DOWN)
        elif(joy_value_y == -1):
          self.player.shape.startMove(DIR_UP)
        else:  # joy_value_y == 0
          self.player.shape.stopMove(DIR_DOWN)
          self.player.shape.stopMove(DIR_UP)
        if(joy_value_x == 1):
          self.player.shape.startMove(DIR_RIGHT)
        elif(joy_value_x == -1):
          self.player.shape.startMove(DIR_LEFT)
        else:  # joy_value_x == 0
          self.player.shape.stopMove(DIR_RIGHT)
          self.player.shape.stopMove(DIR_LEFT)
        
        # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION
        if event.type == pygame.JOYBUTTONDOWN:
          #logging.debug("Joystick button pressed.")
          self.player.notIdle(ticks)
          for i in range( self.num_buttons ):
            if(self.pacjoy.get_button(i) and not self.button_status[i]):
              self.button_status[i] = True
              logging.debug("joystick Button "+str(i+1)+" pressed.")
              if(i in self.cur_pad_map['ask']):
                self.player.shape.tryAsk()
              elif(i in self.cur_pad_map['swirl_right']):
                self.player.shape.trySwirlRight()
              elif(i in self.cur_pad_map['swirl_left']):
                self.player.shape.trySwirlLeft()
              elif(i in self.cur_pad_map['give']):
                self.player.shape.tryGive()
              elif(i in self.cur_pad_map['doswirl_up']):
                self.player.shape.activateSwirl(True)
              elif(i in self.cur_pad_map['doswirl_dn']):
                self.player.shape.activateSwirl(False)
              elif(i in self.cur_pad_map['reset']):
                self.player.shape.reset()
              elif(i in self.cur_pad_map['quit']):
                logging.info("That was RANDOM SEED {0}. Hope you had fun.".format(self.crazySeed))
                logging.debug("Quitting program.")
                pygame.quit()
                sys.exit()
          # multi-button combinations
          if(self.pacjoy.get_button(self.cur_button_map['Lshoulder']) and self.pacjoy.get_button(self.cur_button_map['Rshoulder'])
            and self.pacjoy.get_button(self.cur_button_map['Lcenter']) and self.pacjoy.get_button(self.cur_button_map['Rcenter'])):
            self.doMenu()
        
        
        if event.type == pygame.JOYBUTTONUP:
          #logging.debug("Joystick button released.")
          for i in range( self.num_buttons ):
            if(not self.pacjoy.get_button(i) and self.button_status[i]):
              self.button_status[i] = False
              #logging.debug("Button "+str(i+1)+" released.")
      # end of : input_mode == INPUT_JOYSTICK

      if(INPUT_KEYBOARD in self.input_mode):
        if event.type == KEYDOWN:
          self.player.notIdle(ticks)
          # Find which key was pressed

          if event.key == self.cur_kb_map['top']:  # "top" button
            self.player.shape.tryGive()
          elif event.key == self.cur_kb_map['left']:  # "left" button
            self.player.shape.trySwirlLeft()
          elif event.key == self.cur_kb_map['bottom']:  # "bottom" button
            self.player.shape.tryAsk()
          elif event.key == self.cur_kb_map['right']:  # "right" button
            self.player.shape.trySwirlRight()
          elif event.key == self.cur_kb_map['Lshoulder']:  # "left shoulder" button
            self.player.shape.activateSwirl(False)
          elif event.key == self.cur_kb_map['Rshoulder']:  # "right shoulder" button
            self.player.shape.activateSwirl(True)
          elif event.key == K_f:  # toggle fullscreen
            self.toggleFullscreen()
          elif event.key == K_DOWN:
            self.player.shape.startMove(DIR_DOWN)
          elif event.key == K_UP:
            self.player.shape.startMove(DIR_UP)
          elif event.key == K_RIGHT:
            self.player.shape.startMove(DIR_RIGHT)
          elif event.key == K_LEFT:
            self.player.shape.startMove(DIR_LEFT)
          elif event.key == K_t:  # NOTE: "teleport" effect - FOR DEBUG ONLY ??
            self.player.shape.reset()
          elif event.key == K_SPACE:
            # DEBUG - activate autonomosity
            nearby_shapes = self.map.nearShapes(self.player.shape.getCenter(), self.map.character_size * 1.5, self.player.shape)
            if len(nearby_shapes) > 0:
              #logging.debug("Shapes near to S#{0}: {1}".format(self.id, nearby_shapes))
              receiver = nearby_shapes[0]
              receiver.autonomous = not receiver.autonomous
              logging.debug("toggling autonomy for shape #{0}, now {1}".format(receiver.id, receiver.autonomous))
            else:
              logging.debug("no nearby shapes")
          elif event.key == K_BACKQUOTE:
            self.doMenu()
        
        if event.type == KEYUP:
          if event.key == K_DOWN:
            self.player.shape.stopMove(DIR_DOWN)
          elif event.key == K_UP:
            self.player.shape.stopMove(DIR_UP)
          elif event.key == K_RIGHT:
            self.player.shape.stopMove(DIR_RIGHT)
          elif event.key == K_LEFT:
            self.player.shape.stopMove(DIR_LEFT)
      # end of (INPUT_KEYBOARD)
    # end for (events)
    
    # movement should be smooth, so not tied to event triggers
    if(INPUT_JOYSTICK in self.input_mode and \
      ('analog_axis_y' in self.cur_pad_map.keys() and 'analog_axis_x' in self.cur_pad_map.keys())):
      fbAxis = round(self.pacjoy.get_axis(self.cur_pad_map['analog_axis_y']), 3)
      if(abs(fbAxis) > JOYSTICK_NOISE_LEVEL):
        self.player.shape.move(0, fbAxis * self.player.shape.linearSpeed)
      lrAxis = round(self.pacjoy.get_axis(self.cur_pad_map['analog_axis_x']), 3)
      if(abs(lrAxis) > JOYSTICK_NOISE_LEVEL):
        self.player.shape.move(lrAxis * self.player.shape.linearSpeed, 0)

    # after processing any pending user events, check for idle condition
    self.player.checkIdle(ticks)