Tank simulated game using Class Objects and Lists.

teddies 0 Tallied Votes 309 Views Share

Hey guys. I originally wrote this program as a University assignment. It's basically a simulated game of 'tanks' roaming around a grid blowing eachother up. It isn't intended to be the most exciting game in the universe, infact when you look at the programs' output there will just be a whole heap of numbers that represent where the tanks are located on a 'grid', the tanks armour values, firepower values, etc, etc. I probably should have created a nice little GUI to go with it... but I lack time at the moment.
However, I thought I might as well post it on here. I've had a look through the code snippets and haven't found anything remotely simular to this. I believe it might give people who are beginning to delve in to the world of Object Orientated Programming a working example of how to go about it. It also illustrates how lists can be used to store object instances.
Keep in mind that I'm not very far advanced in terms of Python Programming - I've only done it for one semester at Uni. Any suggestions/feedback you can provide would be greatly appreciated.

import random, sys

#################
#----CLASSES----#
#################

# this is the dice class. It tells the tanks how many spaces to move
class Die(object):

    def roll(self):
        diceRoll=random.randrange(1,7)
        return diceRoll

# this is the directon class. It tells the tank which direction to move.
class Direction(object):

    def randomDir(self):
        directionNumber=random.randrange(1,5)
        directions={1:"up", 2:"down", 3:"left", 4:"right"}
        theDirection=directions[directionNumber]
        return theDirection

# This is the class that creates a tank. When called, an instance of Tank is
# created with the attributes defined in the __init__ method.
class Tank(object):
 
    def __init__(self, tankNo, xPos = 0, yPos = 0, Armour = 10, Firepower = 1, Name=""):
        self.tankNo=tankNo
        self.xPos = xPos
        self.yPos = yPos
        self.armour = Armour
        self.firepower = Firepower
        self.name = raw_input(">> ")
        if self.name=="":
            self.name = "Tank " + str(tankNo)
    
    # The tanks getter methods, These simply return self attribute values
    def get_XPos(self):
        return self.xPos
    
    def get_YPos(self):
        return self.yPos

    def get_Armour(self):
        return self.armour

    def get_Firepower(self):
        return self.firepower

    def get_Name(self):
        return self.name

    # The tanks setter methods. These are used to modify self attribute values.
    def set_XPos(self, leftRight):
        self.xPos += leftRight       

    def set_YPos(self, upDown):
        self.yPos += upDown
        
    def set_Armour(self, nArmour):
        self.armour += nArmour

    def set_Firepower(self, nFirepower):
        self.firepower += nFirepower
        
    # This method checks to see if self has had its' armour attribute reduced
    # below the value of 0. If it has, it removes itself from the theTanks
    # list, thus removing itself from the game.
    def deadOrAlive(self):
        if self.armour < 0:
            print "\n\t"+"+"*45
            print "\t ",self.name, "has died at coordinates: x-", self.get_XPos(), "y-", self.get_YPos()
            theTanks.remove(self)
            print "\t"+"+"*45
            
    # This method simply returns the xPos and yPos of the tank in the form of a list
    def Coords(self):
        return [self.xPos, self.yPos]
    
#################
#---FUNCTIONS---#
#################

def showMenu():
    # This is the menu function. It's functionality is limited to just that, it
    # gives the user options of what they can do with the program. There's some
    # error checking bits and pieces in here too. 
    print """
                        ------------------------
                         B A T T L E  T A N K S
                        ------------------------
                            By Scott Hayward

                          (1) Game Information
                          (2) Start Game
                          (3) Exit"""
    menuValid=False
    while menuValid==False:
        menuChoice=raw_input("\n\t\t\tPlease enter your choice: ")
        if menuChoice not in ("1", "2","3"):
            print "\n\t\t\t   Invalid menu option"
        elif menuChoice=="1":
            instructions()
        elif menuChoice=="2":
            main()
        elif menuChoice=="3":
            raw_input("\n\t\t\tPush enter to exit")
            sys.exit()

def tankNumbers():
    # This is where the user specifies the number of tanks they want in the
    # battle. There can't be any more than 9 tanks and no less than 2.
    global numberOfTanks
    validNo=False
    while validNo==False:            
        try:
            numberOfTanks = raw_input("How many tanks in the battle : ")
            numberOfTanks=int(numberOfTanks)
            if numberOfTanks > 9:
                print "\n\tNo more than 9 tanks are allowed in the battle.\n"
            elif numberOfTanks < 2:
                print "\n\tThere needs to be more than one tank in the battle.\n"
            else:
                validNo=True
        except ValueError:
            if numberOfTanks=="":
                numberOfTanks=2
                validNo=True
            else:
                print "\n\tPlease enter a number...\n"

def createTanks():
    # This function creates the 'tanks'. The number of tanks that are created
    # depends on the value stored in the numberOfTanks variable. The instances
    # of Tank are stored in the theTanks list. 
    global theTanks
    theTanks=[]
    for i in range(numberOfTanks):
        tankNumber = i+1
        print "\nPlease enter Tank " + str(tankNumber) + "'s name..."
        theTanks.append(Tank(tankNo=tankNumber))

def moveTanks():
    # This function moves the tanks around the 'grid'. The direction and number
    # if spaces they more are determined by the Die and Direction classes. 
    global theDies
    global theDirections
    theDies=[]
    theDirections=[]    
    for i in range(len(theTanks)):
        theDies.append(theDice.roll())        
        theDirections.append(theDir.randomDir())
        if theDirections[i] =="up":
            theTanks[i].set_YPos(theDies[i])
        elif theDirections[i]=="down":
            theTanks[i].set_YPos(-theDies[i])
        elif theDirections[i] =="right":
            theTanks[i].set_XPos(theDies[i])
        elif theDirections[i]=="left":
            theTanks[i].set_XPos(-theDies[i])

def determineCoords():
    # This function stops the tanks from moving outside of the 'grid'. If a tank
    # moves to gridSize or -gridSize, it will bounce back in the opposite
    # direction. For example: if a tank is at x:9, y:0 and is told to go right
    # 5 spaces, it will go to 10 (gridSize), then bounce back to the left 4
    # spaces landing on x:6 y:0.
    for i in range(len(theTanks)):
        if theTanks[i].get_XPos() > gridSize:
            theTanks[i].xPos = 2 * gridSize - theTanks[i].xPos
            print "\n\t***", theTanks[i].name, "- B O U N C E ***"    
        elif theTanks[i].get_XPos() < -gridSize:
            theTanks[i].xPos = -2 * gridSize - theTanks[i].xPos
            print "\n\t***", theTanks[i].name, "- B O U N C E ***"
        elif theTanks[i].get_YPos() > gridSize:
            theTanks[i].yPos = 2 * gridSize - theTanks[i].yPos
            print "\n\t***", theTanks[i].name, "- B O U N C E ***" 
        elif theTanks[i].get_YPos() < -gridSize:
            theTanks[i].yPos = -2 * gridSize - theTanks[i].yPos
            print "\n\t***", theTanks[i].name, "- B O U N C E ***"

def doSpecials():
    #If the tanks land on -3,-3 one is added to the firepower attribute of the tank
    #If the tanks land on 3,3 ten is added to the armour attribute of the tank
    specialSquare = 3
    for i in range(len(theTanks)):
        if theTanks[i].get_XPos() == specialSquare and theTanks[i].get_YPos() == specialSquare:
            print "\n\t+++", theTanks[i].name, "- Yipee - More Armour +++" 
            theTanks[i].set_Armour(10)

        if theTanks[i].get_XPos() == -specialSquare and theTanks[i].get_YPos() == -specialSquare:
            print "\n\t+++", theTanks[i].name, "- yipee - More Firepower +++"
            theTanks[i].set_Firepower(1)
            
def theBattle():
    # Probably a much easier way to achieve what I was after but this works
    # so it'll do! Basically a loop iterates through a list of object instances
    # (theTanks). Another loop is used to iterate through a list of coordinates
    # that the tanks are at. If the coordinates of tank are equal
    # to coord, tank's armour is reduced by the tank which coord is related to's
    # firepower.
    isBattle=0
    theCoords=[]
    for i in range(len(theTanks)):
        theCoords.append([theTanks[i].xPos, theTanks[i].yPos])   
    i=0
    x=0
    battleLocation=[]
    tanksInvolved=[]
    for tank in theTanks:
        pos=i
        theCoords[pos]=""
        i+=1
        for coord in theCoords:
            if tank.Coords()==coord:
                battleLocation.append(tank.Coords())
                tank.set_Armour(-theTanks[x].firepower)
                isBattle=1
                
            x+=1
            if x ==len(theTanks):
                x=0
                theCoords[pos]=tank.Coords()
                break          
    # Remove duplicates from battleLocation list
    fighting = []
    while battleLocation != []:
        item = battleLocation.pop(0)
        if item not in fighting:
            fighting.append(item) 
    for cord in fighting:
        print "\n\t"+"#"*45
        print "\t\tB A T T L E  @  X:",cord[0],"- Y:",cord[1]
        print "\t"+"#"*45
    return isBattle

def deadCheck():
    # Checks to see if any of the tanks are dead or not. It calls the
    # deadOrAlive method in the Tank class to get this information. 
    for i in range(len(theTanks)):
        for tank in theTanks:
            tank.deadOrAlive()
        
def gameOver():
    # This function checks to see if the game is over or not. 
    if len(theTanks)==0:
        print "\n\n\n\t\t"+"#"*45
        print "\t\t\t B A T T L E  R E S U L T S"
        print "\t\t"+"#"*45
        print "\n\tThe tanks killed eachother at the same time. Unfortunate that..."
    elif len(theTanks)==1:
        print "\n\n\n\t\t"+"#"*45
        print "\t\t\t B A T T L E  R E S U L T S"
        print "\t\t"+"#"*45
        print "\n\t\t",theTanks[0].name, "is the winner! Congratulations", theTanks[0].name

def instructions():
    # Just instructions 
    print "\nThis game was originally written as a University assignment. The original \nversion only had two tanks. However after writing and submitting the assignment I was compelled to modify the program to allow for a user-defined number of \ntanks. In doing so it has aided my knowledge and understanding of Object \nOrientated Programming to the extent that I believe it may prove to be a good \nexample to others that are just being introduced to the world of OOP."
    print "\nAlbeit not a very 'exciting' program, it illustrates how 'lists' can be used to hold class object instances. It also illustrates the effective use of class \ngetter and setter methods."
    print "\nThe basic concept of the game is to have a user-defined number of 'tanks' \nroaming around a grid that goes from 10 to -10 on the X axis and 10 to -10 \non the Y axis. Each tank starts with the following attributes: X and \nY coordinates of 0 (so each tank starts the battle on 0,0), an Armour attribute of 10 and a Firepower attribute of 1."
    print "\nThe tanks move around the grid by responding to randomly generated numbers that indicate the amount of spaces to move, in which direction. There are two special squares on the grid; one is located at coordinates 3,3 which adds 10 to the \ntanks Armour attribute when landed on. The other is located at coordinates -3, \n-3 which adds 1 to the tanks firepower attribute when landed on." 
    print "\nWhen two or more tanks land on the same square a battle occurs. Each tank's \nArmour attribute is reduced by the other tank's firepower attribute. The game \ncontinues until there is only one tank left in the game. Or until there are no \ntanks left in the game (this indicates a draw, it's pretty rare for this to \nhappen)."
    print "\nAt the beginning of the game the user will be asked to enter the number of \ntanks in the battle. I have limitted this to 9, simply because having 10 or \nmore tanks in the game screws up the data tables and it looks stupid."
    raw_input("\nWell that's about it! Please note that this game wasn't supposed to be the most enjoyable game ever created. It was simply made to improve my knowledge and \nunderstanding of OOP. I hope it helps anyone else that is looking to dive in to the world of OOP.\n\nPush enter to return to the menu.")
    showMenu()
                
def main():
    counter=0
    global theDice
    global theDir
    theDice=Die()
    theDir=Direction()  
    print "\n"+"-"*73
    tankNumbers()
    createTanks()                   
    while len(theTanks) >= 2:
        print "\n" + "-" * 73
        print "        Die", "\tDir", "\t\txPos", "\tyPos", "\t\tArmour", "\tFirepower" 
        print "-" * 73
        moveTanks()
        determineCoords()
        doSpecials()    
        battle=theBattle()
        counter+=1 
        deadCheck()
        for i in range(len(theTanks)):
            print "\n", theTanks[i].name, "\t",theDies[i], "\t", theDirections[i], "\t\t", theTanks[i].get_XPos(), "\t", theTanks[i].get_YPos(), "\t\t", theTanks[i].get_Armour(), "\t", theTanks[i].get_Firepower(), "\n"
        if battle==1:
            raw_input()
        gameOver()
    print "\t\t\t   Number of moves: ", counter
    playAgain=raw_input("\n\t\tPress enter to return to the main menu...\n\t\tor type 'again' to play again: ")
    del theDies[:]
    del theDirections[:]
    del theTanks[:]
    if playAgain=="again":
        main()
    else:
        showMenu()
        
# This variable determines the size of the 'grid'. For example, with the default
# value of 10, the grid goes from 10 to -10 on both X and Y axis. 
gridSize=10

if __name__=="__main__":
    
    showMenu()
Murtan 317 Practically a Master Poster

Generally, I like it. (You're right, a GUI would help a lot to follow what's going on. )

Just a couple of nit-picky items:

I don't think it is good form to have the Tank's __init__ method prompt the user for a tank name. I'd rather see the prompt in createTanks() and have it passed to the Tank's __init__

I have a small issue with the tank's setter methods, they don't 'SET' they modify, making the name misleading. It works fine for this application, but from the name, I would have expected set_XPos(5) to set the Tank's X coordinate to 5, and not for it to move the tank 5 positions to the right.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.