Reset the translation, we're going to redo it with ctopy.
[super-star-trek.git] / src / sst.py
index 8e3842f045b30132845080f2486ee6301c5b8593..c082b3b4df7ed83bf73ba3980cb509ffdcbb4161 100644 (file)
@@ -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