#!/usr/bin/python # gcset # Simple fantasy starship combat simulation # by David Turover, 1998 # Python implementation 2005 # This game is released into the public domain. # Suggested improvements: # Easier: # * Add descriptive comments and clarify existing comments # * Name the ships differently depending on what side they are on # * Allow player to refuse a fight until they get a favorable match # * Give some ships battle damage at the start of the fight # More difficult: # * Optimize repetitive parts of code or replace them with functions # * Make it more object-oriented by putting all functions inside classes # * Create a battle between more than one side # * Design a scoring system and print a score at the end of the battle # * Save a record of the battle to disk # * Allow survivors of the battle to fight a second battle # Harder: # * Make it a graphical program using a GUI library such as WxWindows # * Create additional types of ships with different max stats # * Improve the computer targeting AI (artificial intelligence) # * Improve the AI without having it look at enemy ships' stats # * Add a way for ships to lauch starfighters at each other # And of course: # * Anything you'd like! from random import randint, random from time import sleep # myGetInt() -- Prompt user for input and validate it as an integer. def myGetInt(myStr): i = raw_input(myStr) if i.isdigit(): i = int(i) else: i = 0 return i # Create a 'counter' object that holds a value that can be depleted from # a maximum amount. class Counter: value = 1 max = 1 def __init__(self, amount): self.value = amount self.max = amount # ratio(): Return proportion of value/max from 0 to 1 def ratio(self): retval = float(self.value) / float(self.max) if self.value > self.max: retval = 1 if self.value < 0: retval = 0 return retval def sub(self, amount): self.value -= amount if(self.value < 0): self.value = 0 return self.value def add(self, amount): self.value += amount if(self.value > max): self.value = max return self.value def printDiv(self): return str(self.value) + "/" + str(self.max) class Ship: def __init__(self): self.name = "Generic Cruiser" self.armour = Counter(10000) self.engines = Counter(1500) self.weapons = Counter(1500) self.reactor = Counter(2000) self.bridge = Counter(250) self.isActive = 1 def printReport(self): print "Report for ship " + self.name print "Armour: " + self.armour.printDiv() print "Engine: " + self.engines.printDiv() print "Weapons: " + self.weapons.printDiv() print "Reactor: " + self.reactor.printDiv() print "Bridge: " + self.bridge.printDiv() def fire(self, targetShip, location): if self.isActive == 0: return 0 hits = 0 # Current unassigned hits on target ship startHits = 0 # Original number of hits on target ship armourHits = 0 # Hits assigned to target ship's armour engHits = 0 # Hits assigned to target ship's engine reacHits = 0 # Hits assigned to target ship's reactor gunHits = 0 # Hits assigned to target ship's guns bridgeHits = 0 # Hits assigned to target ship's bridge dodge = 0 # Dodge ability # Calculate dodge ability dodge = targetShip.engines.ratio() * .33 # Add location modifier (some locations harder to hit) dodge += locmod(location) # Add random miss (up to 33%) dodge += random()/3.0 # Ensure 0 < dodge < 1 if dodge < 0 : dodge = 0 if dodge > 1 : dodge = 1 # Calculate number of hits hits = int(self.weapons.value * (1.0 - dodge)) # Record number of hits startHits = hits armourHits = int(targetShip.armour.ratio() * hits) hits -= armourHits if location == 2: engineHits = .70 * hits else: engineHits = .45 * hits engineHits = int(engineHits) if engineHits > targetShip.engines.value: engineHits = targetShip.engines.value hits -= engineHits if location == 3: reacHits = .70 * hits else: reacHits = .45 * hits reacHits = int(reacHits) if reacHits > targetShip.reactor.value: reacHits = targetShip.reactor.value hits -= reacHits if location == 4: gunHits = .58 * hits else: gunHits = .33 * hits gunHits = int(gunHits) if gunHits > targetShip.weapons.value: gunHits = targetShip.weapons.value hits -= gunHits if location == 5: bridgeHits = .55 * hits else: bridgeHits = .16 * hits bridgeHits = int(bridgeHits) if bridgeHits > targetShip.bridge.value: bridgeHits = targetShip.bridge.value hits -= bridgeHits # Deal remaining hits to armour armourHits += hits targetShip.armour.sub(armourHits) targetShip.engines.sub(engineHits) targetShip.reactor.sub(reacHits) targetShip.weapons.sub(gunHits) targetShip.bridge.sub(bridgeHits) print self.name + " fires at " + targetShip.name print str(armourHits) + " hits to armour" print str(engineHits) + " hits to engines" print str(reacHits) + " hits to reactor" print str(gunHits) + " hits to weapons" print str(bridgeHits) + " hits to bridge" if targetShip.engines.value < 10: targetShip.engines.value = 10 if targetShip.weapons.value < 10: targetShip.weapons.value = 10 if targetShip.reactor.value < 0 or targetShip.armour.value < 0 or targetShip.bridge.value < 2: print targetShip.name + " is destroyed!" targetShip.isActive = 0 sleep(1) class Battle: sides = [[],[]] # Array of arrays of ships minShipsPerSide = 2 maxShipsPerSide = 5 def __init__(self): for mySide in range(0, len(self.sides)): numShips = randint(self.minShipsPerSide, self.maxShipsPerSide) + 1 for i in range(1, numShips): myShip = Ship(); myShip.name = myShip.name + " " + str(i) myShip.parentSide = self.sides[mySide] self.sides[mySide].append(myShip); def manualFire (self): inputIsBad = 1 enemyShipID = 0 maxEnemyShipID = len(self.sides[1]) + 1 while inputIsBad: enemyShipID = 0 # TODO: store enemy ship ID print "Enemy ships:" for i in range(1, maxEnemyShipID): print "(" + str(i) + ") " + self.sides[1][i-1].name enemyShipID = myGetInt("Select a ship to fire upon: ") if enemyShipID in range(1, maxEnemyShipID): inputIsBad = 0 else: print str(enemyShipID) + " not in range " + str(range(len(self.sides[1]))) location = 0 print "(1) No particular target" print "(2) Engines" print "(3) Reactor Room" print "(4) Gun Emplacements" print "(5) Bridge" location = myGetInt("Target a location:") if int(location) not in [2,3,4,5]: location = 1 print "targeteting " + str(enemyShipID) + " " + str(location) enemyShipID = enemyShipID - 1 self.sides[0][0].fire(self.sides[1][enemyShipID],location) self.sides[0][0].printReport() self.sides[1][enemyShipID].printReport() sleep(1) def runRound(self): for team in range(0,len(self.sides)): for shipID in range(0,len(self.sides[team])): ship = self.sides[team][shipID] if ship.isActive != 0: # target and fire on an enemy ship if team == 0 and shipID == 0: self.manualFire() else: activeEnemy = 0; enemySide = 0; if team == 0: enemySide = 1 for i in self.sides[enemySide]: if i.isActive != 0: activeEnemy += 1 if activeEnemy > 0: targetShip = self.sides[enemySide][randint(1,len(self.sides[enemySide])) - 1] while targetShip.isActive == 0: targetShip = self.sides[enemySide][randint(1,len(self.sides[enemySide])) - 1] targetSystem = randint(1,5); ship.fire(targetShip, targetSystem) for team in range(0,len(self.sides)): shipID = 0 while shipID < len(self.sides[team]): if self.sides[team][shipID].isActive == 0: del self.sides[team][shipID] else: shipID += 1 if len(self.sides[0]) == 0: print "Your side has been eliminated" return 0 elif self.sides[0][0].name != "Your Ship": print "Your ship has been destroyed" return 0 if len(self.sides[1]) == 0: print "The enemy has been eliminated" return 0 return 1 # To-hit modifier for aiming at a location def locmod(location): result = 0 if location == 2: # targeting engines result = .06 if location == 3: # targeting reactor result = .16 if location == 4: # targeting guns result = .18 if location == 6: # targeting bridge result = .21 return result; # For attacking "no place in particular" # Main program logic # Create a battle myBattle = Battle() # Name your ship myBattle.sides[0][0].name = "Your Ship" #for i in myBattle.sides: # for j in i: # print j.name myBattle.sides[0][0].printReport() while(myBattle.runRound() == 1): noop = None