Tests for deep space probes.
[super-star-trek.git] / sst.py
diff --git a/sst.py b/sst.py
index e1c447ef010f6bcb943fd7cf25f80f7f5c1efd49..e2bf5c378288421101bbd0fdce810982e122e7e0 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -131,6 +131,8 @@ class Thingy(Coord):
         self.angered = False
     def angry(self):
         self.angered = True
+    def at(self, q):
+        return (q.i, q.j) == (self.i, self.j)
 
 class Planet:
     def __init__(self):
@@ -139,7 +141,7 @@ class Planet:
         self.pclass = None     # could be ""M", "N", "O", or "destroyed"
         self.crystals = "absent"# could be "mined", "present", "absent"
         self.known = "unknown" # could be "unknown", "known", "shuttle_down"
-        self.inhabited = False # is it inhabites?
+        self.inhabited = False # is it inhabited?
     def __str__(self):
         return self.name
 
@@ -157,8 +159,10 @@ class Quadrant:
 class Page:
     def __init__(self):
        self.stars = None
-       self.starbase = None
+       self.starbase = False
        self.klingons = None
+    def __repr__(self):
+        return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)
 
 def fill2d(size, fillfun):
     "Fill an empty list in 2D."
@@ -449,19 +453,14 @@ def tryexit(enemy, look, irun):
        # avoid intruding on another commander's territory 
        if enemy.type == 'C':
             if iq in game.state.kcmdr:
-                return False
+                return []
            # refuse to leave if currently attacking starbase 
            if game.battle == game.quadrant:
-               return False
+               return []
        # don't leave if over 1000 units of energy 
        if enemy.power > 1000.0:
-           return False
-    # emit 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":
-       prout(crmena(True, enemy.type, "sector", enemy.location) + \
-              (_(" escapes to Quadrant %s (and regains strength).") % iq))
+           return []
+    oldloc = copy.copy(enemy.location)
     # handle local matters related to escape
     enemy.move(None)
     game.klhere -= 1
@@ -482,7 +481,8 @@ def tryexit(enemy, look, irun):
            if cmdr == game.quadrant:
                game.state.kcmdr.append(iq)
                break
-    return True # success 
+    # report move out of quadrant.
+    return [(True, enemy, oldloc, ibq)]
 
 # The bad-guy movement algorithm:
 # 
@@ -532,8 +532,8 @@ def movebaddy(enemy):
        nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
     else:
        nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
-    dist1 = enemy.kdist
-    mdist = int(dist1 + 0.5) # Nearest integer distance 
+    old_dist = enemy.kdist
+    mdist = int(old_dist + 0.5) # Nearest integer distance 
     # If SC, check with spy to see if should hi-tail it 
     if enemy.type == 'S' and \
        (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
@@ -561,14 +561,14 @@ def movebaddy(enemy):
            motion = ((forces + randreal(200))/150.0) - 5.0
        else:
             if forces > 1000.0: # Very strong -- move in for kill 
-               motion = (1.0 - randreal())**2 * dist1 + 1.0
+               motion = (1.0 - randreal())**2 * old_dist + 1.0
            if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
                motion -= game.skill*(2.0-randreal()**2)
        if game.idebug:
            proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
        # don't move if no motion 
        if motion == 0:
-           return
+           return []
        # Limit motion according to skill 
        if abs(motion) > game.skill:
             if motion < 0:
@@ -612,15 +612,15 @@ def movebaddy(enemy):
        while attempts < 20 and not success:
             attempts += 1
            if look.i < 0 or look.i >= QUADSIZE:
-               if motion < 0 and tryexit(enemy, look, irun):
-                   return
+                if motion < 0:
+                   return tryexit(enemy, look, irun)
                if krawli == m.i or m.j == 0:
                    break
                look.i = goto.i + krawli
                krawli = -krawli
            elif look.j < 0 or look.j >= QUADSIZE:
-               if motion < 0 and tryexit(enemy, look, irun):
-                   return
+               if motion < 0:
+                   return tryexit(enemy, look, irun)
                if krawlj == m.j or m.i == 0:
                    break
                look.j = goto.j + krawlj
@@ -630,7 +630,7 @@ def movebaddy(enemy):
                if game.quad[look.i][look.j] == game.ship and \
                    (enemy.type == 'C' or enemy.type == 'S'):
                    collision(rammed=True, enemy=enemy)
-                   return
+                   return []
                if krawli != m.i and m.j != 0:
                    look.i = goto.i + krawli
                    krawli = -krawli
@@ -649,14 +649,8 @@ def movebaddy(enemy):
            break # done early 
     if game.idebug:
        skip(1)
-    if enemy.move(goto):
-       if not damaged(DSRSENS) or game.condition == "docked":
-           proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
-           if enemy.kdist < dist1:
-               proutn(_(" advances to "))
-           else:
-               proutn(_(" retreats to "))
-           prout("Sector %s." % goto)
+    # Enemy moved, but is still in sector
+    return [(False, enemy, old_dist, goto)]
 
 def moveklings():
     "Sequence Klingon tactical movement."
@@ -664,14 +658,15 @@ def moveklings():
        prout("== MOVCOM")
     # Figure out which Klingon is the commander (or Supercommander)
     # and do move
+    tacmoves = []
     if game.quadrant in game.state.kcmdr:
         for enemy in game.enemies:
            if enemy.type == 'C':
-               movebaddy(enemy)
+               tacmoves += movebaddy(enemy)
     if game.state.kscmdr == game.quadrant:
         for enemy in game.enemies:
            if enemy.type == 'S':
-               movebaddy(enemy)
+               tacmoves += movebaddy(enemy)
                break
     # If skill level is high, move other Klingons and Romulans too!
     # Move these last so they can base their actions on what the
@@ -679,8 +674,8 @@ def moveklings():
     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
         for enemy in game.enemies:
             if enemy.type in ('K', 'R'):
-               movebaddy(enemy)
-    sortenemies()
+               tacmoves += movebaddy(enemy)
+    return tacmoves
 
 def movescom(iq, avoid):
     "Commander movement helper." 
@@ -1035,7 +1030,7 @@ def randdevice():
     return None        # we should never get here
 
 def collision(rammed, enemy):
-    "Collision handling fot rammong events."
+    "Collision handling for rammong events."
     prouts(_("***RED ALERT!  RED ALERT!"))
     skip(1)
     prout(_("***COLLISION IMMINENT."))
@@ -1186,8 +1181,8 @@ def torpedo(origin, bearing, dispersion, number, nburst):
             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
            game.quad[w.i][w.j] = '.'
            game.base.invalidate()
-           game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
-           game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
+           game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
+           game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
            game.state.basekl += 1
            newcnd()
            return None
@@ -1287,7 +1282,6 @@ def fry(hit):
     # Select devices and cause damage
     cdam = []
     while ncrit > 0:
-        ncrit -= 1
         while True:
            j = randdevice()
            # Cheat to prevent shuttle damage unless on ship 
@@ -1296,6 +1290,7 @@ def fry(hit):
        cdam.append(j)
        extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
        game.damage[j] += extradm
+        ncrit -= 1
     skipcount = 0
     for (i, j) in enumerate(cdam):
        proutn(device[j])
@@ -1332,9 +1327,26 @@ def attack(torps_ok):
        return
     # commanders get a chance to tac-move towards you 
     if (((game.quadrant in game.state.kcmdr or game.state.kscmdr == game.quadrant) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
-       moveklings()
+        for (bugout, enemy, old, goto) in  moveklings():
+            if bugout:
+                # we know about this if either short or long range
+                # sensors are working
+                if damaged(DSRSENS) and damaged(DLRSENS) \
+                       and game.condition != "docked":
+                    prout(crmena(True, enemy.type, "sector", old) + \
+                          (_(" escapes to Quadrant %s (and regains strength).") % goto))
+            else: # Enemy still in-sector
+                if enemy.move(goto):
+                    if not damaged(DSRSENS) or game.condition == "docked":
+                        proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
+                        if enemy.kdist < old:
+                            proutn(_(" advances to "))
+                        else:
+                            proutn(_(" retreats to "))
+                        prout("Sector %s." % goto)
+        sortenemies()
     # if no enemies remain after movement, we're done 
-    if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing == game.quadrant and not thing.angered):
+    if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
        return
     # set up partial hits if attack happens during shield status change 
     pfac = 1.0/game.inshld
@@ -3863,7 +3875,7 @@ def impulse():
        scanner.chew()
        return
     # Make sure enough time is left for the trip 
-    game.optime = course.dist/0.095
+    game.optime = course.distance/0.095
     if game.optime >= game.state.remtime:
        prout(_("First Officer Spock- \"Captain, our speed under impulse"))
        prout(_("power is only 0.95 sectors per stardate. Are you sure"))
@@ -3875,9 +3887,9 @@ def impulse():
     game.ididit = True
     if game.alldone:
        return
-    power = 20.0 + 100.0*course.dist
+    power = 20.0 + 100.0*course.distance
     game.energy -= power
-    game.optime = course.dist/0.095
+    game.optime = course.distance/0.095
     if game.energy <= 0:
        finish(FNRG)
     return
@@ -3913,7 +3925,7 @@ def warp(wcourse, involuntary):
            skip(1)
            prout(_("Engineering to bridge--"))
            if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
-               iwarp = (game.energy/(wcourse.dist+0.05)) ** 0.333333333
+               iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
                if iwarp <= 0:
                    prout(_("We can't do it, Captain. We don't have enough energy."))
                else:
@@ -5425,10 +5437,14 @@ def setup():
     game.instar = 0
     for i in range(GALSIZE):
        for j in range(GALSIZE):
-           k = randrange(1, QUADSIZE**2/10+1)
+            # Can't have more stars per quadrant than fit in one decimal digit,
+            # if we do the chart representation will break. 
+           k = randrange(1, min(10, QUADSIZE**2/10))
            game.instar += k
            game.state.galaxy[i][j].stars = k
     # Locate star bases in galaxy
+    if game.idebug:
+        prout("=== Allocating %d bases" % game.inbase)
     for i in range(game.inbase):
         while True:
             while True:
@@ -5451,6 +5467,8 @@ def setup():
                        prout("=== Saving base #%d, close to #%d" % (i, j))
             if not contflag:
                 break
+        if game.idebug:
+            prout("=== Placing base #%d in quadrant %s" % (i, w))
        game.state.baseq.append(w)
        game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
     # Position ordinary Klingon Battle Cruisers
@@ -5783,7 +5801,7 @@ def newqad():
            prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
     # Put in THING if needed
     if thing == game.quadrant:
-        Enemy(type='?', loc=dropin(),
+        Enemy(etype='?', loc=dropin(),
                   power=randreal(6000,6500.0)+250.0*game.skill)
         if not damaged(DSRSENS):
             skip(1)
@@ -5800,7 +5818,7 @@ def newqad():
                w.j = withprob(0.5) * (QUADSIZE-1)
                 if game.quad[w.i][w.j] == '.':
                     break
-            game.tholian = Enemy(type='T', loc=w,
+            game.tholian = Enemy(etype='T', loc=w,
                                  power=randrange(100, 500) + 25.0*game.skill)
            # Reserve unoccupied corners 
            if game.quad[0][0]=='.':