Fix bug on torpedo miss in curses mode. Add egression test for this.
[super-star-trek.git] / sst.py
diff --git a/sst.py b/sst.py
index 742e104dbc77a726455b33242193ea97a4d41803..0b99af03b120071aa807c496700777e0bc7a693a 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -273,6 +273,8 @@ class Enemy:
     def __init__(self, etype=None, loc=None, power=None):
         self.type = etype
         self.location = Coord()
+        self.kdist = None
+        self.kavgd = None
         if loc:
             self.move(loc)
         self.power = power     # enemy energy level
@@ -379,6 +381,7 @@ class Gamestate:
         self.score = 0.0       # overall score
         self.perdate = 0.0     # rate of kills
         self.idebug = False    # Debugging instrumentation enabled?
+        self.statekscmdr = None # No SuperCommander coordinates yet.
     def recompute(self):
         # Stas thinks this should be (C expression): 
         # game.state.remkl + len(game.state.kcmdr) > 0 ?
@@ -386,7 +389,7 @@ class Gamestate:
         # He says the existing expression is prone to divide-by-zero errors
         # after killing the last klingon when score is shown -- perhaps also
         # if the only remaining klingon is SCOM.
-        game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
+        self.state.remtime = self.state.remres/(self.state.remkl + 4*len(self.state.kcmdr))
 
 FWON = 0
 FDEPLETE = 1
@@ -698,8 +701,7 @@ def movescom(iq, avoid):
        unschedule(FSCDBAS)
        for enemy in game.enemies:
            if enemy.type == 'S':
-               break
-       enemy.move(None)
+               enemy.move(None)
        game.klhere -= 1
        if game.condition != "docked":
            newcnd()
@@ -1099,7 +1101,8 @@ def torpedo(origin, bearing, dispersion, number, nburst):
        if iquad == '.':
            continue
        # hit something 
-       if not damaged(DSRSENS) or game.condition == "docked":
+        setwnd(message_window)
+        if not damaged(DSRSENS) or game.condition == "docked":
            skip(1)     # start new line after text track 
        if iquad in ('E', 'F'): # Hit our ship 
            skip(1)
@@ -1111,7 +1114,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
                return hit # Cheat if on a planet 
             # In the C/FORTRAN version, dispersion was 2.5 radians, which
             # is 143 degrees, which is almost exactly 4.8 clockface units
-            displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
+            displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
             displacement.next()
             bumpto = displacement.sector()
            if not bumpto.valid_sector():
@@ -1139,40 +1142,43 @@ def torpedo(origin, bearing, dispersion, number, nburst):
                return None
             for enemy in game.enemies:
                if w == enemy.location:
-                   break
-           kp = math.fabs(enemy.power)
-           h1 = 700.0 + randrange(100) - \
-               1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
-           h1 = math.fabs(h1)
-           if kp < h1:
-               h1 = kp
-            if enemy.power < 0:
-                enemy.power -= -h1
-            else:
-                enemy.power -= h1
-           if enemy.power == 0:
-               deadkl(w, iquad, w)
-               return None
-           proutn(crmena(True, iquad, "sector", w))
-            displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
-            displacement.next()
-            bumpto = displacement.sector()
-            if not bumpto.valid_sector():
-               prout(_(" damaged but not destroyed."))
-               return
-           if game.quad[bumpto.i][bumpto.j] == ' ':
-               prout(_(" buffeted into black hole."))
-               deadkl(w, iquad, bumpto)
-           if game.quad[bumpto.i][bumpto.j] != '.':
-               prout(_(" damaged but not destroyed."))
+                    kp = math.fabs(enemy.power)
+                    h1 = 700.0 + randrange(100) - \
+                        1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
+                    h1 = math.fabs(h1)
+                    if kp < h1:
+                        h1 = kp
+                    if enemy.power < 0:
+                        enemy.power -= -h1
+                    else:
+                        enemy.power -= h1
+                    if enemy.power == 0:
+                        deadkl(w, iquad, w)
+                        return None
+                    proutn(crmena(True, iquad, "sector", w))
+                    displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
+                    displacement.next()
+                    bumpto = displacement.sector()
+                    if not bumpto.valid_sector():
+                        prout(_(" damaged but not destroyed."))
+                        return
+                    if game.quad[bumpto.i][bumpto.j] == ' ':
+                        prout(_(" buffeted into black hole."))
+                        deadkl(w, iquad, bumpto)
+                    if game.quad[bumpto.i][bumpto.j] != '.':
+                        prout(_(" damaged but not destroyed."))
+                    else:
+                        prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
+                        enemy.location = bumpto
+                        game.quad[w.i][w.j] = '.'
+                        game.quad[bumpto.i][bumpto.j] = iquad
+                        for enemy in game.enemies:
+                            enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
+                        sortenemies()
+                    break
             else:
-                prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
-                enemy.location = bumpto
-                game.quad[w.i][w.j] = '.'
-                game.quad[bumpto.i][bumpto.j] = iquad
-                for enemy in game.enemies:
-                    enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
-                sortenemies()
+                prout("Internal error, no enemy where expected!")
+                raise SystemExit, 1
             return None
        elif iquad == 'B': # Hit a base 
            skip(1)
@@ -1268,6 +1274,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
            return None
        break
     skip(1)
+    setwnd(message_window)
     prout(_("Torpedo missed."))
     return None
 
@@ -1361,7 +1368,7 @@ def attack(torps_ok):
                continue # Don't waste the effort! 
            attempt = True # Attempt to attack 
            dustfac = randreal(0.8, 0.85)
-           hit = enemy.power*math.pow(dustfac,enemy.kavgd)
+           hit = enemy.power*math.pow(dustfac, enemy.kavgd)
            enemy.power *= 0.75
        else: # Enemy uses photon torpedo 
            # We should be able to make the bearing() method work here
@@ -1662,11 +1669,12 @@ def hittem(hits):
     "Register a phaser hit on Klingons and Romulans."
     w = Coord()
     skip(1)
-    for (kk, wham) in enumerate(hits):
-       if wham==0:
+    kk = 0
+    for wham in hits:
+       if wham == 0:
            continue
        dustfac = randreal(0.9, 1.0)
-       hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
+       hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
        kpini = game.enemies[kk].power
        kp = math.fabs(kpini)
        if PHASEFAC*hit < kp:
@@ -1707,10 +1715,16 @@ def hittem(hits):
 def phasers():
     "Fire phasers at bad guys."
     hits = []
-    kz = 0; k = 1; irec=0 # Cheating inhibitor 
-    ifast = False; no = False; itarg = True; msgflag = True; rpow=0
+    kz = 0
+    k = 1
+    irec = 0 # Cheating inhibitor 
+    ifast = False
+    no = False
+    itarg = True
+    msgflag = True
+    rpow = 0
     automode = "NOTSET"
-    key=0
+    key = 0
     skip(1)
     # SR sensors and Computer are needed for automode 
     if damaged(DSRSENS) or damaged(DCOMPTR):
@@ -1736,15 +1750,15 @@ def phasers():
        ifast = True
     # Original code so convoluted, I re-did it all
     # (That was Tom Almy talking about the C code, I think -- ESR)
-    while automode=="NOTSET":
-       key=scanner.next()
+    while automode == "NOTSET":
+       key = scanner.next()
        if key == "IHALPHA":
            if scanner.sees("manual"):
                if len(game.enemies)==0:
                    prout(_("There is no enemy present to select."))
                    scanner.chew()
                    key = "IHEOL"
-                   automode="AUTOMATIC"
+                   automode = "AUTOMATIC"
                else:
                    automode = "MANUAL"
                    key = scanner.next()
@@ -1788,18 +1802,18 @@ def phasers():
            key = scanner.next()
        if key != "IHREAL" and len(game.enemies) != 0:
            prout(_("Phasers locked on target. Energy available: %.2f")%avail)
-       irec=0
+       irec = 0
         while True:
            scanner.chew()
            if not kz:
                for i in range(len(game.enemies)):
-                   irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
-           kz=1
+                   irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
+           kz = 1
            proutn(_("%d units required. ") % irec)
            scanner.chew()
            proutn(_("Units to fire= "))
            key = scanner.next()
-           if key!="IHREAL":
+           if key != "IHREAL":
                return
            rpow = scanner.real
            if rpow > avail:
@@ -1808,11 +1822,11 @@ def phasers():
                key = "IHEOL"
             if not rpow > avail:
                 break
-       if rpow<=0:
+       if rpow <= 0:
            # chicken out 
            scanner.chew()
            return
-        key=scanner.next()
+        key = scanner.next()
        if key == "IHALPHA" and scanner.sees("no"):
            no = True
        if ifast:
@@ -1829,7 +1843,7 @@ def phasers():
                hits.append(0.0)
                if powrem <= 0:
                    continue
-               hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
+               hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
                over = randreal(1.01, 1.06) * hits[i]
                temp = powrem
                powrem -= hits[i] + over
@@ -1884,7 +1898,7 @@ def phasers():
            if key == "IHEOL":
                scanner.chew()
                if itarg and k > kz:
-                   irec=(abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) *  randreal(1.01, 1.06) + 1.0
+                   irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *       randreal(1.01, 1.06) + 1.0
                kz = k
                proutn("(")
                if not damaged(DCOMPTR):
@@ -1993,11 +2007,13 @@ def events():
     "Run through the event queue looking for things to do."
     i = 0
     fintim = game.state.date + game.optime
-    yank=0
+    yank = 0
     ictbeam = False
     istract = False
-    w = Coord(); hold = Coord()
-    ev = Event(); ev2 = Event()
+    w = Coord()
+    hold = Coord()
+    ev = Event()
+    ev2 = Event()
 
     def tractorbeam(yank):
         "Tractor-beaming cases merge here." 
@@ -2039,8 +2055,8 @@ def events():
             else:
                 prout(_("(Shields not currently useable.)"))
         newqad()
-        # Adjust finish time to time of tractor beaming 
-        fintim = game.state.date+game.optime
+        # Adjust finish time to time of tractor beaming? 
+        fintim = game.state.date+game.optime
         attack(torps_ok=False)
         if not game.state.kcmdr:
             unschedule(FTBEAM)
@@ -2119,7 +2135,7 @@ def events():
            finish(FDEPLETE)
            return
        # Any crew left alive? 
-       if game.state.crew <=0:
+       if game.state.crew <= 0:
            finish(FCREW)
            return
        # Is life support adequate? 
@@ -2163,7 +2179,7 @@ def events():
        elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
            if game.state.nscrem == 0 or \
                ictbeam or istract or \
-                game.condition=="docked" or game.isatb==1 or game.iscate:
+                game.condition == "docked" or game.isatb == 1 or game.iscate:
                return
            if game.ientesc or \
                (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
@@ -2315,7 +2331,7 @@ def events():
            # tell the captain about it if we can 
            if communicating():
                prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
-                        % (q.planet, `w`))
+                        % (q.planet, repr(w)))
                prout(_("by a Klingon invasion fleet."))
                if cancelrest():
                    return
@@ -2346,7 +2362,7 @@ def events():
            if q.klingons <= 0:
                q.status = "secure"
                continue
-           if game.state.remkl >=MAXKLGAME:
+           if game.state.remkl >= MAXKLGAME:
                continue                # full right now 
            # reproduce one Klingon 
            w = ev.quadrant
@@ -2457,7 +2473,7 @@ def nova(nov):
         start = hits.pop()
         for offset.i in range(-1, 1+1):
             for offset.j in range(-1, 1+1):
-                if offset.j==0 and offset.i==0:
+                if offset.j == 0 and offset.i == 0:
                     continue
                 neighbor = start + offset
                 if not neighbor.valid_sector():
@@ -2484,7 +2500,7 @@ def nova(nov):
                     if iquad == 'P':
                         game.state.nplankl += 1
                     else:
-                        game.state.worldkl += 1
+                        game.state.nworldkl += 1
                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
                     game.iplnet.pclass = "destroyed"
                     game.iplnet = None
@@ -3176,7 +3192,8 @@ def pause_game():
         global linecount
         sys.stdout.write('\n')
         proutn(prompt)
-        raw_input()
+        if not replayfp:
+            raw_input()
         sys.stdout.write('\n' * rows)
         linecount = 0
 
@@ -3205,6 +3222,9 @@ def proutn(line):
        if curwnd == message_window and y >= my - 2:
            pause_game()
            clrscr()
+        # Uncomment this to debug curses problems
+        if logfp:
+            logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(line)))
        curwnd.addstr(line)
        curwnd.refresh()
     else:
@@ -3254,8 +3274,31 @@ def setwnd(wnd):
     "Change windows -- OK for this to be a no-op in tty mode."
     global curwnd
     if game.options & OPTION_CURSES:
+        # Uncomment this to debug curses problems
+        if logfp:
+            if wnd == fullscreen_window:
+                legend = "fullscreen"
+            elif wnd == srscan_window:
+                legend = "srscan"
+            elif wnd == report_window:
+                legend = "report"
+            elif wnd == status_window:
+                legend = "status"
+            elif wnd == lrscan_window:
+                legend = "lrscan"
+            elif wnd == message_window:
+                legend = "message"
+            elif wnd == prompt_window:
+                legend = "prompt"
+            else:
+                legend = "unknown"
+            logfp.write("#curses: setwnd(%s)\n" % legend)
         curwnd = wnd
-        curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
+        # Some curses implementations get confused when you try this.
+        try:
+            curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
+        except curses.error:
+            pass
 
 def clreol():
     "Clear to end of line -- can be a no-op in tty mode" 
@@ -6310,6 +6353,7 @@ if __name__ == '__main__':
             game.options |= OPTION_TTY
         seed = int(time.time())
         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
+        replay = False
         for (switch, val) in options:
             if switch == '-r':
                 try:
@@ -6319,11 +6363,12 @@ if __name__ == '__main__':
                     raise SystemExit, 1
                 try:
                     line = replayfp.readline().strip()
-                    (leader, key, seed) = line.split()
+                    (leader, __, seed) = line.split()
                     seed = eval(seed)
                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
                     line = replayfp.readline().strip()
                     arguments += line.split()[2:]
+                    replay = True
                 except ValueError:
                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
                     raise SystemExit(1)
@@ -6372,6 +6417,8 @@ if __name__ == '__main__':
                     game.alldone = False
                 else:
                     makemoves()
+                if replay:
+                    break
                 skip(1)
                 stars()
                 skip(1)