X-Git-Url: https://jxself.org/git/?p=super-star-trek.git;a=blobdiff_plain;f=src%2Fsst.py;h=c082b3b4df7ed83bf73ba3980cb509ffdcbb4161;hp=8e3842f045b30132845080f2486ee6301c5b8593;hb=deed0903c0666e52bab540a39ed99131f4073970;hpb=a0718b55ad87f433723f756dfe748701c7801695 diff --git a/src/sst.py b/src/sst.py index 8e3842f..c082b3b 100644 --- a/src/sst.py +++ b/src/sst.py @@ -1,5 +1,15 @@ """ sst.py =-- Super Star Trek in Python + +Control flow of this translation is pretty much identical to the C version +(and thus like the ancestral FORTRAN) but the data structures are +radically different -- the Python code makes heavy use of objects. + +Note that the game.quad, game.snap.galaxy and game.snap.chart members +are not actually arrays but dictioaries indixed by coord tuples. Be setting +the hash of a coord equal to the hash of a literal tuple containing its +coordinate data, we ensure these can be indexed both ways. + """ import math @@ -15,6 +25,11 @@ MAXKLGAME = 127 MAXKLQUAD = 9 FOREVER = 1e30 +# These macros hide the difference between 0-origin and 1-origin addressing. +# They're a step towards de-FORTRANizing the code. +def VALID_QUADRANT(x,y): ((x)>=1 and (x)<=GALSIZE and (y)>=1 and (y)<=GALSIZE) +def VALID_SECTOR(x, y): ((x)>=1 and (x)<=QUADSIZE and (y)>=1 and (y)<=QUADSIZE) + # These types have not been dealt with yet IHQUEST = '?', IHWEB = '#', @@ -46,24 +61,30 @@ class coord: return "%d - %d" % (self.x, self.y) class feature: - "A feature in the current quadrant (ship, star, black hole, etc)." + "A feature in the current quadrant (ship, star, black hole, base, etc)." def __init__(self): self.type = None # name of feature type - self.location = None # location + self.sector = None # sector location def distance(self): - return self.location.distance(game.sector) + return self.sector.distance(game.sector) def __str__(self): + "This will be overridden by subclasses." return self.name[0] - -empty = None # Value of empty space in game.quad + def sectormove(self, dest): + "Move this feature within the current quadrant." + if self.sector: + game.quad[self.sector] = None + game.quad[dest] = self + self.sector = dest class ship(feature): - "An enemy ship in the current quadrant." - def __init__(self): + "A starship, frindly or enemy." + def __init__(self, type, power): feature.__init__(self) - self.type = None # klingon, romulan, commander, - # supercommander, tholian - self.power = None # power + self.type = type # klingon, romulan, commander, + # supercommander, tholian, + # enterprise, faerie queene. + self.power = power # power if self.type in ("Klingon", "Commander", "Super-Commander"): game.remkl += 1 elif self.type == "Romulan": @@ -73,12 +94,16 @@ class ship(feature): game.remkl -= 1 elif self.type == "Romulan": game.romrem -= 1 - def sectormove(self, dest): - "Move this ship within the current quadrant." - if self.location: - game.quad[self.location] = None - game.quad[dest] = self - self.location = dest + +class space(feature): + "Empty space. Has no state, just knows how to identify iself." + def __str__(self): + return '*' + +class star(feature): + "A star. Has no state, just knows how to identify iself." + def __str__(self): + return '*' class planet(feature): "A planet. May be inhabited or not, may hold dilithium crystals or not." @@ -88,39 +113,33 @@ class planet(feature): self.crystals = None # "absent", "present", or "mined" self.inhabited = False self.known = "unknown" # Other values: "known" and "shuttle down" + game.state.planets.append(self) + def __del__(self): + game.state.planets.remove(self) def __str__(self): if self.inhabited: return '@' else: return 'P' -class star(feature): - "A star. Has no state, just knows how to identify iself." - def __init(self): - feature.__init__(self) - def __str__(self): - return '*' - class web(feature): "A bit of Tholian web. Has no state, just knows how to identify iself." - def __init(self): - feature.__init__(self) def __str__(self): return '*' class blackhole(feature): "A black hole. Has no hair, just knows how to identify iself." - def __init(self): - feature.__init__(self) def __str__(self): - return '*' + return ' ' class starbase(feature): - "Starbases also have no features." - def __init(self): + "Starbases also have no features, just a location." + def __init(self, quadrant): feature.__init__(self) + self.quadrant = quadrant + game.state.bases.append(self) def __del__(self): - game.state.bases.remove(self.location) + game.state.bases.remove(self) def __str__(self): return 'B' @@ -158,13 +177,11 @@ class snapshot: self.remkl = None # remaining klingons self.remcom = None # remaining commanders self.nscrem = None # remaining super commanders - self.rembase = None # remaining bases self.starkl = None # destroyed stars self.basekl = None # destroyed bases self.nromrem = None # Romulans remaining - self.nplankl = None # destroyed uninhabited planets - self.nworldkl = None # destroyed inhabited planets - self.plnets = []; # List of planets known + self.nplankl = None # destroyed uninhabited planets self.nworldkl = None # destroyed inhabited planets + self.planets = []; # List of planets known self.date = None # stardate self.remres = None # remaining resources self. remtime = None # remaining time @@ -231,7 +248,7 @@ class game: self.skill = None # levels: none, novice, fair, good, # expert, emeritus # Integer nembers sart here - self.inkling = None # initial number of klingons +x self.inkling = None # initial number of klingons self.inbase = None # initial number of bases self.incom = None # initial number of commanders self.inscom = None # initial number of commanders @@ -278,463 +295,4 @@ class game: self.probeiny = None # self.height = None # height of orbit around planet -def communicating(): - "Are we in communication with Starfleet Command?" - return (not damaged("DRADIO")) or game.condition == docked - -# Code corresponding to ai.c begins here - -def tryexit(look, ship, irun): - # a bad guy attempts to bug out of the quadrant - iq = coord() - iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1 - iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1 - if not valid_quadrant(iq) or \ - game.state.galaxy[iq].supernova or \ - game.state.galaxy[iq].klingons > 8: - return False; # no can do -- neg energy, supernovae, or >8 Klingons - if ship.type == "Romulan": - return False # Romulans cannot escape - if not irun: - # avoid intruding on another commander's territory - if ship.type == "Commander": - if iq in gamestate.kcmdr: - return False - # refuse to leave if currently attacking starbase: - if game.battle == game.quadrant: - return False; - # don't leave if over 1000 units of energy - if ship.power > 1000.0: - return false; - # Print escape message and move out of quadrant. - # We know this if either short or long range sensors are working - if not damaged("DSRSENS") or not damaged("DLRSENS") or game.condition=="docked": - crmena(True, "sector", ship) - prout(" escapes to quadrant %s (and regains strength)." % iq) - # handle local matters related to escape - game.quad[ship.location] = None; - if game.condition != "docked": - newcnd() - # Handle global matters related to escape - game.state.galaxy[game.quadrant].klingons -= 1 - game.state.galaxy[iq].klingons += 1 - if ship.type == "Super-Commander": - game.ishere = False - game.iscate = False - game.ientesc = False - game.isatb = 0 - schedule("FSCMOVE", 0.2777) - unschedule("FSCDBAS") - game.state.kscmdr = iq - else: - for (n, cmdr) in enumerate(game.state.kcmdr): - if cmdr == game.quadrant: - game.state.kcmdr[n] = iq - break - game.comhere = False - return True # successful exit - -def sgn(n): n / abs(n) - -''' -Algorithm for moving bad guys: - - * Enterprise has "force" based on condition of phaser and photon torpedoes. - If both are operating full strength, force is 1000. If both are damaged, - force is -1000. Having shields down subtracts an additional 1000. - - * Enemy has forces equal to the energy of the attacker plus - 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR - 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus. - - Attacker Initial energy levels (nominal): - Klingon Romulan Commander Super-Commander - Novice 400 700 1200 - Fair 425 750 1250 - Good 450 800 1300 1750 - Expert 475 850 1350 1875 - Emeritus 500 900 1400 2000 - VARIANCE 75 200 200 200 - - Enemy vessels only move prior to their attack. In Novice - Good games - only commanders move. In Expert games, all enemy vessels move if there - is a commander present. In Emeritus games all enemy vessels move. - - * If Enterprise is not docked, an agressive action is taken if enemy - forces are 1000 greater than Enterprise. - - Agressive action on average cuts the distance between the ship and - the enemy to 1/4 the original. - - * At lower energy advantage, movement units are proportional to the - advantage with a 650 advantage being to hold ground, 800 to move forward - 1, 950 for two, 150 for back 4, etc. Variance of 100. - - If docked, is reduced by roughly 1.75*game.skill, generally forcing a - retreat, especially at high skill levels. - - * Motion is limited to skill level, except for SC hi-tailing it out. -''' - -def movebaddy(ship): - # tactical movement for the bad guys - bugout = False - # This should probably be just game.comhere + game.ishere - if game.skill >= SKILL_EXPERT: - nbaddys = int((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0) - else: - nbaddys = game.comhere + game.ishere - dist1 = ship.distance() - mdist = round(dist1 + 0.5) # Nearest integer distance - # If SC, check with spy to see if should high-tail it - if ship.type == "Super-Commander" and \ - (ship.power <= 500.0 or (game.condition==docked and not damaged("DPHOTON"))): - bugout = True; - motion = -QUADSIZE; - else: - # decide whether to advance, retreat, or hold position - forces = ship.power + 100.0*len(game.quad.enemies()) + 400*(nbaddys-1) - if not game.shldup: - forces += 1000.0 # Good for enemy if shield is down! - if not damaged("DPHASER") or not damaged("DPHOTON"): - if damaged(DPHASER): - forces += 300.0 - else: - forces -= 0.2*(game.energy - 2500.0); - if damaged("DPHOTON"): - forces += 300.0 - else: - forces -= 50.0*game.torps - else: - # phasers and photon tubes both out! - forces += 1000.0 - motion = 0; - if forces <= 1000.0 and game.condition != "docked": # Typical case - motion = ((forces+200.0*Rand())/150.0) - 5.0 - else: - if forces > 1000.0: # Very strong -- move in for kill - motion = (1.0-square(Rand()))*dist1 + 1.0 - if game.condition == "docked" and "base" in game.options: - # protected by base -- back off ! - motion -= game.skill * (2.0-Rand()**2) - if idebug: - proutn("=== MOTION = %1.2f, FORCES = %1.2f, ", motion, forces) - # don't move if no motion - if motion == 0: - return - # Limit motion according to skill - if abs(motion) > game.skill: - if motion < 0: - motion = -game.kill - else: - motion = game.skill - # calculate preferred number of steps - nsteps = abs(motion) - if motion > 0 and nsteps > mdist: # don't overshoot - nsteps = mdist - if nsteps > QUADSIZE: # This shouldn't be necessary - nsteps = QUADSIZE - if nsteps < 1: # This shouldn't be necessary - nsteps = 1 - if idebug: - proutn("NSTEPS = %d:", nsteps) - # Compute preferred values of delta X and Y - me = game.sector - com; - if 2.0 * abs(me.x) < abs(me.y): - me.x = 0; - if 2.0 * abs(me.y) < abs(game.sector.x-com.x): - me.y = 0; - if me.x != 0: me.x = sgn(me.x*motion) - if me.y != 0: me.y = sgn(me.y*motion) - next = com; - # main move loop - for ll in range(nsteps): - if idebug: - proutn(" %d", ll+1) - # Check if preferred position available - look = next + me - krawl = me.sgn() - success = False - attempts = 0 # Settle meysterious hang problem - while attempts < 20 and not success: - attempts += 1 - if look.x < 1 or look.x > QUADSIZE: - if motion < 0 and tryexit(look, ship, bugout): - return - if krawl.x == me.x or me.y == 0: - break - look.x = next.x + krawl.x - krawl.x = -krawl.x - elif look.y < 1 or look.y > QUADSIZE: - if motion < 0 and tryexit(look, ship, bugout): - return - if krawl.y == me.y or me.x == 0: - break - look.y = next.y + krawl.y - krawl.y = -krawl.y - elif "ramming" in game.options and game.quad[look] != IHDOT: - # See if we should ram ship - if game.quad[look] == game.ship and ienm in (IHC, IHS): - ram(true, ienm, com) - return - if krawl.x != me.x and me.y != 0: - look.x = next.x + krawlx - krawl.x = -krawl.x - elif krawly != me.y and me.x != 0: - look.y = next.y + krawly - krawl.y = -krawl.y - else: - break # we have failed - else: - success = True - if success: - next = look - if idebug: - proutn(str(next)) - else: - break # done early - if idebug: - prout("") - # Put ship in place within same quadrant - if next != ship.location: - # it moved - if not damaged("DSRSENS") or game.condition == "docked": - proutn("*** %s from sector %s" % (ship, ship.location)) - if ship.distance() < dist1: - prout(" advances to sector %s" % ship.location) - else: - prout(" retreats to sector %s" % ship.location) - ship.sectormove(next) - -def movcom(): - "Allow enemies to move." - for enemy in self.quad.enemies(): - if enemy.type == "Commander": - movebaddy(enemy) - break - for enemy in self.quad.enemies(): - if enemy.type == "Super-Commander": - movebaddy(enemy) - break - # Ff skill level is high, move other Klingons and Romulans too! - # Move these last so they can base their actions on what the - # commander(s) do. - if game.skill >= SKILL_EXPERT and "movebaddy" in game.options: - for enemy in self.quad.enemies(): - if enemy.type in ("Klingon", "Romulan"): - movebaddy(enemy) - break - -def movescom(ship, avoid): - # commander movement helper - global ipage - if game.state.kscmdr == game.quadrant or \ - game.state.galaxy[iq].supernova or \ - game.state.galaxy[iq].klingons > 8: - return True - if avoid: - # Avoid quadrants with bases if we want to avoid Enterprise - for base in game.state.starbases: - if base.location == ship.location: - return True - if game.justin and not game.iscate: - return True - # Super-Commander has scooted, Remove him from current quadrant. - if game.state.kscmdr == game.quadrant: - game.iscate = False - game.isatb = 0 - game.ientesc = False - unschedule("FSCDBAS") - if game.condition != "docked": - newcnd() - ship.sectormove(None) - # do the actual move - game.state.galaxy[game.state.kscmdr].klingons -= 1 - game.state.kscmdr = iq - game.state.galaxy[game.state.kscmdr].klingons += 1 - # check for a helpful planet in the destination quadrant - for planet in game.state.plnets: - if planet.location == game.state.kscmdr and planet.crystals=="present": - # destroy the planet - game.state.plnets.remove(planet) - if communicating(): - if not ipage: - pause_game(True) - ipage = true - prout("Lt. Uhura- \"Captain, Starfleet Intelligence reports") - proutn(_(" a planet in ")) - proutn(cramlc(quadrant, game.state.kscmdr)) - prout(" has been destroyed") - prout(" by the Super-commander.\"") - break - return False # looks good! - -def scom(): - # move the Super Commander - if (idebug): - prout("== SCOM") - - # Decide on being active or passive - passive = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) \ - or (game.state.date-game.indate) < 3.0) - if not game.iscate and passive: - # compute move away from Enterprise - idelta = game.state.kscmdr - game.quadrant - if distance(game.state.kscmdr) > 2.0: - # circulate in space - idelta,x = game.state.kscmdr.y-game.quadrant.y - idelta,y = game.quadrant.x-game.state.kscmdr.x - else: - if len(game.state.bases): - unschedule("FSCMOVE") - return - sc = game.state.kscmdr - # compute distances to starbases - game.starbases.sort(lambda x, y: cmp(distance(x, game.quadrant), distance(y, game.quadrant))) - # look for nearest base without a commander, no Enterprise, and - # without too many Klingons, and not already under attack. - nearest = filter(game.starbases, - lambda x: game.state.galaxy[x].supernova \ - and game.state.galaxy[x].klingons <= 8) - if game.quadrant in nearest: - nearest.remove(game.quadrant) - if game.battle in nearest: - nearest.remove(game.battle) - # if there is a commander, and no other base is appropriate, - # we will take the one with the commander - nocmd = filter(lambda x: x.location not in game.state.kcmdr, nearest) - if len(nocmd): - nearest = nocmd - ibq = nearest[0] - if len(nearest) == 0: - return # Nothing suitable -- wait until next time - # decide how to move toward base - idelta = ibq - game.state.kscmdr - # maximum movement is 1 quadrant in either or both axis - delta = delta.sgn() - # try moving in both x and y directions - iq = game.state.kscmdr + idelta - if movescom(iq, passive): - # failed -- try some other maneuvers - if ideltax==0 or ideltay==0: - # attempt angle move - if ideltax != 0: - iq.y = game.state.kscmdr.y + 1 - if movescom(iq, passive): - iq.y = game.state.kscmdr.y - 1 - movescom(iq, passive) - else: - iq.x = game.state.kscmdr.x + 1 - if movescom(iq, passive): - iq.x = game.state.kscmdr.x - 1 - movescom(iq, passive) - else: - # try moving just in x or y - iq.y = game.state.kscmdr.y - if movescom(iq, passive): - iq.y = game.state.kscmdr.y + ideltay - iq.x = game.state.kscmdr.x - movescom(iq, passive) - # check for a base - if game.state.rembase == 0: - unschedule("FSCMOVE") - else: - for ibq in game.bases: - if ibq == game.state.kscmdr and game.state.kscmdr == game.battle: - # attack the base - if passive: - return # no, don't attack base! - game.iseenit = false - game.isatb = 1 - schedule("FSCDBAS", 1.0 +2.0*Rand()) - if is_scheduled("FCDBAS"): - postpone("FSCDBAS", scheduled("FCDBAS")-game.state.date) - if not communicating(): - return # no warning - game.iseenit = True - if not ipage: - pause_game(true) - ipage = True - proutn(_("Lt. Uhura- \"Captain, the starbase in ")) - proutn(cramlc(quadrant, game.state.kscmdr)) - skip(1) - prout(" reports that it is under attack from the Klingon Super-commander.") - proutn(" It can survive until stardate %d.\"", - int(scheduled(FSCDBAS))) - if not game.resting: - return - prout("Mr. Spock- \"Captain, shall we cancel the rest period?\"") - if ja() == false: - return - game.resting = False - game.optime = 0.0 # actually finished - return - # Check for intelligence report - if (Rand() > 0.2 or not communicating() or - not game.state.galaxy[game.state.kscmdr].charted): - return - if ipage: - pause_game(true) - ipage = true - prout(_("Lt. Uhura- \"Captain, Starfleet Intelligence reports")) - proutn(_(" the Super-commander is in ")) - proutn(cramlc(quadrant, game.state.kscmdr)) - prout(".\"") - return - -def movetho(void): - "Move the Tholian (an instance of ship type pointed at by game.tholian)." - if not game.tholian or game.justin: - return - next = coord() - if game.tholian.location.x == 1 and game.tholian.location.y == 1: - next.x = 1 - next.y = QUADSIZE - elif game.tholian.location.x == 1 and game.tholian.location.y == QUADSIZE: - next.x = next.y = QUADSIZE - elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == QUADSIZE: - next.x = QUADSIZE - next.y = 1 - elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == 1: - next.x = next.y = 1 - else: - # something is wrong! - game.tholian = None - return - # Do nothing if we are blocked - if game.quad[next] != empty and not isinstance(game.quad[next]. web): - return - # Now place some web - im = (next - game.tholian.location).sgn() - if game.tholian.x != next.x: - # move in x axis - while game.tholian.location.x != next.x: - game.tholian.location.x += im.x - if game.quad[game.tholian.location] == empty: - game.quad[game.tholian.location] = web() - elif game.tholian.y != next.y: - # move in y axis - while game.tholian.y != next.y: - game.tholian.y += im.y - if game.quad[game.tholian.location] == empty: - game.quad[game.tholian.location] = web() - # web is done, move ship - game.tholian.movesector(next) - # check to see if all holes plugged - for i in range(1, QUADSIZE+1): - if (not isinstance(game.quad[(1,i)],web)) and game.quad[(1,i)]!=game.tholian: - return - if (not isinstance(game.quad[(QUADSIZE,i)],web)) and game.quad[(QUADSIZE,i)]!=game.tholian: - return - if (not isinstance(game.quad[(i,1)],web)) and game.quad[(i,1)]!=game.tholian: - return - if (not isinstance(game.quad[(i.QUADSIZE)],web)) and game.quad[(i,QUADSIZE)]!=game.tholian: - return - # All plugged up -- Tholian splits - game.quad[game.tholian] = web() - ship.movesector(None) - crmena(True, IHT, sector, game.tholian) - prout(" completes web.") - game.tholian = None - return