Friday, June 14, 2013

Map Generation - Finish Him!

I've got some working code for the Map Gen class I've been working on. There are some issues with placing rooms on top of existing stuff, and I'd like to code something to place walls on the outside of the rectangles that make up my rooms and hallways. But at least it's working now, and it's been pretty fun to work on up to this point.

Loaded the mapgen class into a pygame testbed

I know this is a crappy screenshot, but you can see that there are several rooms (the black rectangles) and hall-objects that connect them. Ignore the crappy images I used for the rooms and player objects. I know they suck, but I've been using them as my standard test images for a while now.

Currently the room generator chooses from a number of pre-loaded images and scales the image to fit the random size. I'm thinking I may change it to tile a floor pattern on the room after a size is chosen. This way I don't end up with floors that are clearly the same image, just stretched to fit. Either that, or just use the same floors and have the room generators just draw random carpets or mats or something to draw attention from the stretched image underneath...

I also want to place some generic walls on each room/hall then add random pictures, posters and windows.

For now though, you just import the class, and run genMaps() and it returns a list (or array if you prefer) of these room/hall objects. You can then use the pygame rectangle objects for collisions  and the pygame image (surface) objects to draw them to another surface.

Here's the source for this class file:
Click here to expand Source
import random, pygame

class RoomObj:
  def update(self,anchorpoint):
    self.rect.left = self.xoffset + anchorpoint[0]
    self.rect.top = self.yoffset + anchorpoint[1]
  
class Room(RoomObj):
  def __init__(self, imageset, direction=0, room_rect=None):
    #RoomObj.__init__(self)
    self.direction = random.randint(1,4) #up, down, left, right
    self.size = (random.randint(400,600),random.randint(500,700))
    self.image = pygame.Surface(self.size)
    pygame.transform.scale(random.choice(imageset), self.size, self.image)
    self.rect = self.image.get_rect()
    if direction==0:
      self.rect.top = 0
      self.rect.left = 0
    if direction==1:
      if self.direction == 2:
        self.direction = 1
      self.rect.bottom = room_rect.top
      self.rect.left = room_rect.left-(self.size[0]/2)
    if direction==2:
      if self.direction == 1:
        self.direction = 2
      self.rect.top = room_rect.bottom
      self.rect.left = room_rect.left-(self.size[0]/2)
    if direction==3:
      if self.direction == 4:
        self.direction = 3
      self.rect.top = room_rect.top-(self.size[1]/2)
      self.rect.right = room_rect.left
    if direction==4:
      if self.direction == 3:
        self.direction = 4
      self.rect.top = room_rect.top-(self.size[1]/2)
      self.rect.left = room_rect.right
    self.xoffset = self.rect.left
    self.yoffset = self.rect.top
  
  
class Hall(RoomObj):
  def __init__(self, direction, room_rect, imageset):
    #RoomObj.__init__(self)
    self.size = (80,random.randint(100,300))
    self.image = pygame.Surface(self.size)
    tmpimage = random.choice(imageset)
    if direction==1:
      self.image = pygame.transform.scale(tmpimage, self.size, self.image)
      self.rect = self.image.get_rect()
      self.rect.bottom = room_rect.top
      self.rect.left = random.randint(room_rect.left, room_rect.right-80)
      self.direction=1
    
    if direction==2:
      self.image = pygame.transform.scale(tmpimage, self.size, self.image)
      self.rect = self.image.get_rect()
      self.rect.top = room_rect.bottom
      self.rect.left = random.randint(room_rect.left, room_rect.right-80)
      self.direction=2
    
    if direction==3:
      self.size = (self.size[1],self.size[0])
      self.image = pygame.Surface(self.size)
      self.image = pygame.transform.rotate(tmpimage, 90)
      #print self.size
      #print self.image.get_size()
      self.image = pygame.transform.scale(tmpimage, self.image.get_size(), self.image)
      self.rect = self.image.get_rect()
      self.rect.top = random.randint(room_rect.top, room_rect.bottom-80)
      self.rect.right = room_rect.left
      self.direction=3
    
    if direction==4:
      self.size = (self.size[1],self.size[0])
      self.image = pygame.Surface(self.size)
      self.image = pygame.transform.rotate(tmpimage, 90)
      self.image = pygame.transform.scale(tmpimage, self.image.get_size(), self.image)
      self.rect = self.image.get_rect()
      self.rect.top = random.randint(room_rect.top, room_rect.bottom-80)
      self.rect.left = room_rect.right
      self.direction=4
    
    self.xoffset = self.rect.left
    self.yoffset = self.rect.top
  
def genMap(size,roomimages,hallimages):
  """
  takes 3 perameters: size (string), roomimages (array of pygame images/surfaces),
  and hallimages (also an array of pygame images)
 
  size should be small, medium or large
  the two arrays should use pygame.image.load() objects or pygame.Surface objects
  e.g. [pygame.image.load("file1.png").convert_alpha(), pygame.image.load("file2.jpg)]
 
  returns an array containing room and hall objects.
 
  each room or hall can be moved through the use of an anchor point variable.
  e.g. mapobjects[0].update(anchorpoint) where anchorpoint is the (x, y) offset
  """
  numofrooms=0
  if size=='small':
    numofrooms = random.randint(3,5)
  if size=='medium':
    numofrooms = random.randint(5,8)
  rooms = []
  map = []
  for i in range(numofrooms):
    if i == 0:
      tmproom = Room(roomimages)
      map.append(tmproom)
      tmphall = Hall(tmproom.direction, tmproom.rect, hallimages)
      map.append(tmphall)
    else:
      if i < len(range(numofrooms))-1:
        tmproom = Room(roomimages, tmphall.direction, tmphall.rect)
        map.append(tmproom)
        tmphall=Hall(tmproom.direction, tmproom.rect, hallimages)
        map.append(tmphall)
      else:
        map.append(Room(roomimages, tmphall.direction, tmphall.rect))
  return map

*Edited - 6-21-2013* I decided to go through the class file and make a few minor updates. I added some checking to see if there was an existing object where the new room was going to be placed. I also changed the logic a bit in the loop that created the Rooms/Halls. I also went through and commented it all. (Except the Room subclass; I figured it was close enough to the Hall, I didn't need to comment both)
In any case, here's the cleaned up class file:

Click here to expand the cleaned up Source
import random, pygame

class RoomObj:
  def __init__(self, wallimgset):
    if self.type == 'room':
      #create walls for a room
      pass
    if self.type == 'hall':
      #create walls for a room
      pass
  def update(self,anchorpoint):
    self.rect.left = self.xoffset + anchorpoint[0]
    self.rect.top = self.yoffset + anchorpoint[1]
  
class Room(RoomObj):
  def __init__(self, imageset, direction=0, room_rect=None, wallimgset=[]):
    #main room rectangle
    self.type = 'room'
    self.direction = random.randint(1,4) #up, down, left, right
    self.size = (random.randint(400,600),random.randint(500,700))
    self.image = pygame.Surface(self.size)
    pygame.transform.scale(random.choice(imageset), self.size, self.image)
    self.rect = self.image.get_rect()
    if direction==0:
      self.rect.top = 0
      self.rect.left = 0
    if direction==1:
      if self.direction == 2:
        self.direction = 1
      self.rect.bottom = room_rect.top
      self.rect.left = room_rect.left-(self.size[0]/2)
    if direction==2:
      if self.direction == 1:
        self.direction = 2
      self.rect.top = room_rect.bottom
      self.rect.left = room_rect.left-(self.size[0]/2)
    if direction==3:
      if self.direction == 4:
        self.direction = 3
      self.rect.top = room_rect.top-(self.size[1]/2)
      self.rect.right = room_rect.left
    if direction==4:
      if self.direction == 3:
        self.direction = 4
      self.rect.top = room_rect.top-(self.size[1]/2)
      self.rect.left = room_rect.right
    self.xoffset = self.rect.left
    self.yoffset = self.rect.top
    RoomObj.__init__(self,wallimgset)
   
   
class Hall(RoomObj):
  #initialize our hall object
  def __init__(self, direction, room_rect, imageset, wallimgset=[]):
    self.type = 'hall'
    #size may want to be changed to be relative to screen size
    self.size = (80,random.randint(100,300))
    #will be surface that is drawn to screen
    self.image = pygame.Surface(self.size)
    #will be drawn to self.image surface
    tmpimage = random.choice(imageset)
    #hall will be facing up and down
    if direction==1:
      self.image = pygame.transform.scale(tmpimage, self.size, self.image)
      self.rect = self.image.get_rect()
      self.rect.bottom = room_rect.top
      self.rect.left = random.randint(room_rect.left, room_rect.right-80)
      self.direction=1
    #hall will be up and down 
    if direction==2:
      self.image = pygame.transform.scale(tmpimage, self.size, self.image)
      self.rect = self.image.get_rect()
      self.rect.top = room_rect.bottom
      self.rect.left = random.randint(room_rect.left, room_rect.right-80)
      self.direction=2
    #hall will be left to right
    if direction==3:
      self.size = (self.size[1],self.size[0])
      self.image = pygame.Surface(self.size)
      self.image = pygame.transform.rotate(tmpimage, 90)
      self.image = pygame.transform.scale(tmpimage, self.image.get_size(), self.image)
      self.rect = self.image.get_rect()
      self.rect.top = random.randint(room_rect.top, room_rect.bottom-80)
      self.rect.right = room_rect.left
      self.direction=3
    #hall will be left to right
    if direction==4:
      self.size = (self.size[1],self.size[0])
      self.image = pygame.Surface(self.size)
      self.image = pygame.transform.rotate(tmpimage, 90)
      self.image = pygame.transform.scale(tmpimage, self.image.get_size(), self.image)
      self.rect = self.image.get_rect()
      self.rect.top = random.randint(room_rect.top, room_rect.bottom-80)
      self.rect.left = room_rect.right
      self.direction=4
    #set offset to use with the update function
    self.xoffset = self.rect.left
    self.yoffset = self.rect.top
    #init the parent class to create the walls and draw the floors
    RoomObj.__init__(self, wallimgset)

def checkForExisting(room, maplist):
  for i in maplist:
    if room.rect.contains(i.rect):
      return True
  return False
   
def genMap(size,roomimages,hallimages):
  """
  takes 3 perameters: size (string), roomimages (array of pygame images/surfaces),
  and hallimages (also an array of pygame images)
 
  size should be small, medium or large
  the two arrays should use pygame.image.load() objects or pygame.Surface objects
  e.g. [pygame.image.load("file1.png").convert_alpha(), pygame.image.load("file2.jpg)]
 
  returns an array containing room and hall objects.
 
  each room or hall can be moved through the use of an anchor point variable.
  e.g. mapobjects[0].update(anchorpoint) where anchorpoint is the (x, y) offset
  """
  #num of rooms will be number of nodes to create
  numofrooms=0
  if size=='small':
    numofrooms = random.randint(3,5)
  if size=='medium':
    numofrooms = random.randint(5,8)
  #add a few to account for joining hall nodes
  numofrooms = numofrooms+(numofrooms-1)
  rooms = []
  map = []
  #while map array length is less than total number of desired nodes...
  while len(map) < numofrooms:
    #first create a room and a hall
    if len(map)==0:
      tmproom = Room(roomimages)
      map.append(tmproom)
      tmphall = Hall(tmproom.direction, tmproom.rect, hallimages)
      map.append(tmphall)
    #create all other nodes
    else:
      # if there are at least 2 spots left on the list, create a room, then a hall
      if len(map) < numofrooms-2:
        tmproom = Room(roomimages, tmphall.direction, tmphall.rect)
        #loop several times to ensure the new room isn't put on top of an old one
        #counter is to ensure we don't end up with an endless loop
        counter = 0
        while checkForExisting(tmproom, map):
          tmproom = Room(roomimages, tmphall.direction, tmphall.rect)
          counter+=1
          #we tried 10 times and couldn't place the room, knock off the last
          #hall and room, and try again
          if counter >10:
            map = map[:-2]
        map.append(tmproom)
        tmphall=Hall(tmproom.direction, tmproom.rect, hallimages)
        map.append(tmphall)
      #only one spot left on our list? create a room
      if len(map) == numofrooms-1:
        tmproom = Room(roomimages, tmphall.direction, tmphall.rect)
        counter=0
        while checkForExisting(tmproom, map):
          tmproom = Room(roomimages, tmphall.direction, tmphall.rect)
          counter+=1
          if counter >10:
            map = map[:-2]
        #map.append(Room(roomimages, tmphall.direction, tmphall.rect))
        map.append(tmproom)
  return map

Tuesday, June 11, 2013

Fill Out Web Forms - Python

I've been doing quite a bit lately with games an pygame. I thought I'd take a break and do something useful. Actually, I found some online drawing that I wanted to win, and they allowed you to submit an entry every day over the course of a month. Obviously I didn't want to log in every day, and even if I did have that level of enthusiasm, my memory would fail at some point and I'd miss a few days here or there.

So the only real solution was to script something and set up a cron job to do it for me every morning. Now, I've never done a script to crawl web pages or anything, so I did a bit of digging and found there are actually quite a few options open for python. Since I was more concerned with just getting something together quickly, I went for what looked to be the easiest option: Mechanize.

Mechanize is a library that can search a URL for existing forms and then allows you to edit form contents and follow links or submit forms. Perfect for what I wanted.

Here's an example script:

#! /usr/bin/env python
import os
from mechanize import Browser

fname="submitResults.txt"
f = open(fname, 'w')

br = Browser()
br.set_proxies({"http":"5.5.5.5:3128"})
br.open("http://www.site.com/giveaway/")
br.select_form(nr=1)
br['fvFirst']='firstName'
br['fvLast']='lastName'
f.write('submitted to site\n')
br.submit()
br.close()
f.close()

Nothing special here. I found out the form and field names ahead of time, by loading up the python interpreter and creating the browser object. Then I used Browser.forms to list the available forms. Then I hard coded the form names into the script. If you really wanted to automate it better, you could set up a function or two to handle that step for you. Have it parse for the fields and look for keywords to make sure you fill in the right values.

If you noticed, I have it write a little message to a text file. That was more for me to validate that cron was running every day. If I checked the text file modification date, it would reflect the last time the script ran successfully. If I had thought about it, I would've added some exceptions that would write errors to the log as well.

In any case, this worked out great. I set up the script with it's own directory and set up a cron job to run every morning around 1am. Still waiting on the results for the giveaway, but at least I know I've entered every day possible. And I learned how to use the mechanize library for web scraping and automated form submissions.

-Newt

Monday, June 10, 2013

Map Generation - Ready, Fight!

Okay, I've started work on the map generation code. I had some time to think it over and talk through the logic a bit more, and I made some changes to the way it's going to work. Here's the code I have so far:

import random, pygame

class RoomObj:
    def update(self,anchorpoint):
      self.rect.left = self.xoffset + anchorpoint[0]
      self.rect.top = self.yoffset + anchorpoint[1]

class Room(RoomObj):
    def __init__(self, x,y, imageset):
      #RoomObj.__init__(self)
      self.direction = random.randint(1,4) #up, down, left, right
      self.size = (random.randint(400,600),random.randint(500,700))
      self.image = pygame.Surface(self.size)
      pygame.transform.scale(random.choice(imageset), self.size, self.image)
      self.rect = self.image.get_rect()
      self.rect.topleft = [x,y]
      self.xoffset = x
      self.yoffset = y


class Hall(RoomObj):
    def __init__(self, direction, room_rect):
      #RoomObj.__init__(self)
      if direction==1:
      self.size = (80,random.randint(100,300))
      self.rect.bottom = room_rect.top
      self.rect.left = random.randint(room_rect.left, room_rect.right-80)

def genMap(size,roomimages,hallimages):
    if size=='small':
      numofrooms = random.randint(3,5)
    if size=='medium':
      numofrooms = random.randint(5,8)
      rooms = []
      map = []
      startpoint = [0,0]
      for i in range(numofrooms):
        if i < len(numberofrooms)-1:
          tmproom = Room(startpoint, roomimages)
          #up
          if tmproom.direction == 1:
            map.append(tmproom)
            map.append(Hall(tmproom.direction, tmproom.rect))
    return map

Basically I have a parent class called RoomObj that has the update functions. You may be wondering why a stationary object needs an update function, but for this large of a map, i'm going to have it scroll as the player moves. If you're interested in how I've done the scrolling, check out this article. I'm applying forces to the player object, which move the player sprite around. When the player moves to the edges of an invisible radius around the center of the screen, these forces are transferred to other objects making them scroll. I do this by giving each object an anchor point as an argument. This anchor point is simply a coordinate that I transfer the forces to when the player is moving outside of their center radius. I'm sure I could explain that better, but like I said, if you really care, just check out the blog post. I have the source code up that should explain it better than I could.

Then I have two other classes that inherit from the RoomObj. I created a Room object and a Hall object. I still need to flesh them out a bit more in order to accurately place everything and check for collisions and some other general stuff. I guess I could've created a single class with overloaded init functions, but I've never really been a fan of overloaded classes. They just get kind of confusing after a while. Using inheritance, I can expand the base class or add other children classes fairly easily.

The genMap function is where I made the real changes to how everything is created. If you check out my last blog post, I was going to have it create all the rooms, then loop through the objects to place connecting hall objects. After a great deal of thought, I changed it to create a room, then create a hall, then create a room, and so on. I believe this will allow for much more complex maps. In the future I'd like to add a bit to allow for multiple Hallways to branch off, but for now, I just want to get this working.

Like I said, it's still a work in progress. I know I need to rework the room and hall objects a bit, and I still need to figure out how I want to handle the placement for them. As of right now, I'm thinking I'll use the room object to choose a random direction and then have the hallway get it's x/y coordinates from that. I'm thinking that I'll also need to add wall rectangles to each room object for player collisions later on.

I'm also thinking that the images for the floors will have to be different from those used for the walls. If I'm creating wall objects for each room, those will be scaled to fit the pseudo-random size. I'd like to get some sort of tile for the floors though. I think it'll just look nicer if the floors are all uniform for each map. In any case, I think this should do the trick if I ever finish up the Room and Hall classes. I just don't have time lately to really work on anything. I can dream though...

-Newt

Wednesday, June 5, 2013

Map Gen Pseudo Code

I know there are a ton of map generators out there already, but I was considering trying my hand at it as well. More as a learning experience though, and not as an attempt to improve on them in any way. Lord knows I'm not that good.

But I was thinking about it and I think I may be able to pull it off. I threw together some pseudo code to help me out with the logistics, and I wanted to see if anyone had any improvements or maybe some comments.

Here's what I've got so far:

class RoomObj:
  init(type): #types can be hallways or rooms
    attributes, size, type, image etc.
    randomize image based on type given
  update:
    pass

class Room(RoomObj):
  create room type=Room

class Hall(RoomObj):
  create hall type=Hall

def genMap():
  numofrooms = random number of rooms from range
  listofrooms = []
  listofmapobj = []
  for i in range(numofrooms):
    listofrooms.append(Room())
  for i in range(len(listofrooms)):
    create room objects with a "hallway" type
    if i is not the last iterate:
      listofmapobj.append(Hall(listofrooms[i], listofrooms[i+1]))
    listofmapobj.append(listofrooms[i])
  return listofmapobj

I still need to figure out exactly what parameters the room and hallway classes need to be given, but the rooms will initially just create random sized rectangular rooms, then choose a random image from a pre-loaded set. The room placement would have to check to ensure there are no collisions with other room rectangles. And finally the hallway objects would base their size and shape on the locations of the two rooms given as a parameter.

There are a lot of other particulars I still need to figure out, but I think it is doable. and it should be an interesting project to work on.

I'll have to post whatever I end up with at the end of this little adventure.

-newt