The sgn correction again.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python2
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 from __future__ import print_function, division
15
16 import os, sys, math, curses, time, pickle, copy, gettext, getpass
17 import getopt, socket, locale
18
19 # This import only works on Unixes.  The intention is to enable
20 # Ctrl-P, Ctrl-N, and friends in Cmd.
21 try:
22     import readline
23 except ImportError:
24     pass
25
26 # Prevent lossage under Python 3
27 try:
28     my_input = raw_input
29 except NameError:
30     my_input = input
31
32 version = "2.4"
33
34 docpath         = (".", "doc/", "/usr/share/doc/sst/")
35
36 def _(st):
37     return gettext.gettext(st)
38
39 # Rolling our own LCG because Python changed its incompatibly in 3.2.
40 # Thus, we need to have our own to be 2/3 polyglot, which will also
41 # be helpful when we forwrard-port.
42
43 class randomizer:
44     # LCG PRNG parameters tested against
45     # Knuth vol. 2. by the authors of ADVENT 
46     LCG_A = 1093
47     LCG_C = 221587
48     LCG_M = 1048576
49
50     @staticmethod
51     def random():
52         old_x = game.lcg_x
53         game.lcg_x = (randomizer.LCG_A * game.lcg_x + randomizer.LCG_C) % randomizer.LCG_M
54         return old_x / randomizer.LCG_M;
55
56     @staticmethod
57     def withprob(p):
58         v = randomizer.random()
59         #if logfp:
60         #    logfp.write("#withprob(%.2f) -> %s\n" % (p, v < p))
61         return v < p
62
63     @staticmethod
64     def integer(*args):
65         v = randomizer.random()
66         if len(args) == 1:
67             v = int(v * args[0])
68         else:
69             v = args[0] + int(v * (args[1] - args[0]))
70         #if logfp:
71         #    logfp.write("#integer%s -> %s\n" % (args, v))
72         return int(v)
73
74     @staticmethod
75     def real(*args):
76         v = randomizer.random()
77         if len(args) == 1:
78             v *= args[0]                 # from [0, args[0])
79         elif len(args) == 2:
80             v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
81         #if logfp:
82         #    logfp.write("#real%s -> %f\n" % (args, v))
83         return v
84
85     @staticmethod
86     def seed(n):
87         #if logfp:
88         #    logfp.write("#seed(%d)\n" % n)
89         game.lcg_x = n % randomizer.LCG_M
90
91 GALSIZE         = 8             # Galaxy size in quadrants
92 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
93 MAXUNINHAB      = 10            # Maximum uninhabited worlds
94 QUADSIZE        = 10            # Quadrant size in sectors
95 BASEMIN         = 2                             # Minimum starbases
96 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
97 MAXKLGAME       = 127           # Maximum Klingons per game
98 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
99 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
100 FOREVER         = 1e30          # Time for the indefinite future
101 MAXBURST        = 3             # Max # of torps you can launch in one turn
102 MINCMDR         = 10            # Minimum number of Klingon commanders
103 DOCKFAC         = 0.25          # Repair faster when docked
104 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
105
106 ALGERON         = 2311          # Date of the Treaty of Algeron
107
108
109 DEFAULT      = -1
110 BLACK        = 0
111 BLUE         = 1
112 GREEN        = 2
113 CYAN         = 3
114 RED          = 4
115 MAGENTA      = 5
116 BROWN        = 6
117 LIGHTGRAY    = 7
118 DARKGRAY     = 8
119 LIGHTBLUE    = 9
120 LIGHTGREEN   = 10
121 LIGHTCYAN    = 11
122 LIGHTRED     = 12
123 LIGHTMAGENTA = 13
124 YELLOW       = 14
125 WHITE        = 15
126
127 class TrekError(Exception):
128     pass
129
130 class JumpOut(Exception):
131     pass
132
133 class Coord:
134     def __init__(self, x=None, y=None):
135         self.i = x      # Row
136         self.j = y      # Column
137     def valid_quadrant(self):
138         return self.i >= 0 and self.i < GALSIZE and self.j >= 0 and self.j < GALSIZE
139     def valid_sector(self):
140         return self.i >= 0 and self.i < QUADSIZE and self.j >= 0 and self.j < QUADSIZE
141     def invalidate(self):
142         self.i = self.j = None
143     def __eq__(self, other):
144         return other is not None and self.i == other.i and self.j == other.j
145     def __ne__(self, other):
146         return other is None or self.i != other.i or self.j != other.j
147     def __add__(self, other):
148         return Coord(self.i+other.i, self.j+other.j)
149     def __sub__(self, other):
150         return Coord(self.i-other.i, self.j-other.j)
151     def __mul__(self, other):
152         return Coord(self.i*other, self.j*other)
153     def __rmul__(self, other):
154         return Coord(self.i*other, self.j*other)
155     def __div__(self, other):
156         return Coord(self.i/other, self.j/other)
157     def __truediv__(self, other):
158         return Coord(self.i/other, self.j/other)
159     def __floordiv__(self, other):
160         return Coord(self.i//other, self.j//other)
161     def __mod__(self, other):
162         return Coord(self.i % other, self.j % other)
163     def __rtruediv__(self, other):
164         return Coord(self.i/other, self.j/other)
165     def __rfloordiv__(self, other):
166         return Coord(self.i//other, self.j//other)
167     def roundtogrid(self):
168         return Coord(int(round(self.i)), int(round(self.j)))
169     def distance(self, other=None):
170         if not other:
171             other = Coord(0, 0)
172         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
173     def bearing(self):
174         return 1.90985*math.atan2(self.j, self.i)
175     def sgn(self):
176         s = Coord()
177         if self.i == 0:
178             s.i = 0
179         elif self.i < 0:
180             s.i = -1
181         else:
182             s.i = 1
183         if self.j == 0:
184             s.j = 0
185         elif self.j < 0:
186             s.j = -1
187         else:
188             s.j = 1
189         return s
190     def quadrant(self):
191         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
192         return self.roundtogrid() // QUADSIZE
193     def sector(self):
194         return self.roundtogrid() % QUADSIZE
195     def scatter(self):
196         s = Coord()
197         s.i = self.i + rnd.range(-1, 2)
198         s.j = self.j + rnd.range(-1, 2)
199         return s
200     def __str__(self):
201         if self.i is None or self.j is None:
202             return "Nowhere"
203         return "%s - %s" % (self.i+1, self.j+1)
204     __repr__ = __str__
205
206 class Thingy(Coord):
207     "Do not anger the Space Thingy!"
208     def __init__(self):
209         Coord.__init__(self)
210         self.angered = False
211     def angry(self):
212         self.angered = True
213     def at(self, q):
214         return (q.i, q.j) == (self.i, self.j)
215
216 class Planet:
217     def __init__(self):
218         self.name = None        # string-valued if inhabited
219         self.quadrant = Coord()        # quadrant located
220         self.pclass = None        # could be ""M", "N", "O", or "destroyed"
221         self.crystals = "absent"# could be "mined", "present", "absent"
222         self.known = "unknown"        # could be "unknown", "known", "shuttle_down"
223         self.inhabited = False        # is it inhabited?
224     def __str__(self):
225         return self.name
226
227 class Quadrant:
228     def __init__(self):
229         self.stars = 0
230         self.planet = None
231         self.starbase = False
232         self.klingons = 0
233         self.romulans = 0
234         self.supernova = False
235         self.charted = False
236         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
237     def __str__(self):
238         return "<Quadrant: %(klingons)d>" % self.__dict__
239     __repr__ = __str__
240
241 class Page:
242     def __init__(self):
243         self.stars = None
244         self.starbase = False
245         self.klingons = None
246     def __repr__(self):
247         return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)
248
249 def fill2d(size, fillfun):
250     "Fill an empty list in 2D."
251     lst = []
252     for i in range(size):
253         lst.append([])
254         for j in range(size):
255             lst[i].append(fillfun(i, j))
256     return lst
257
258 class Snapshot:
259     def __init__(self):
260         self.snap = False       # snapshot taken
261         self.crew = 0           # crew complement
262         self.nscrem = 0         # remaining super commanders
263         self.starkl = 0         # destroyed stars
264         self.basekl = 0         # destroyed bases
265         self.nromrem = 0        # Romulans remaining
266         self.nplankl = 0        # destroyed uninhabited planets
267         self.nworldkl = 0        # destroyed inhabited planets
268         self.planets = []        # Planet information
269         self.date = 0.0           # stardate
270         self.remres = 0         # remaining resources
271         self.remtime = 0        # remaining time
272         self.baseq = []         # Base quadrant coordinates
273         self.kcmdr = []         # Commander quadrant coordinates
274         self.kscmdr = Coord()        # Supercommander quadrant coordinates
275         # the galaxy
276         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
277         # the starchart
278         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
279     def traverse(self):
280         for i in range(GALSIZE):
281             for j in range(GALSIZE):
282                 yield (i, j, self.galaxy[i][j])
283
284 class Event:
285     def __init__(self):
286         self.date = None        # A real number
287         self.quadrant = None        # A coord structure
288
289 # game options
290 OPTION_ALL        = 0xffffffff
291 OPTION_TTY        = 0x00000001        # old interface
292 OPTION_CURSES     = 0x00000002        # new interface
293 OPTION_IOMODES    = 0x00000003        # cover both interfaces
294 OPTION_PLANETS    = 0x00000004        # planets and mining
295 OPTION_THOLIAN    = 0x00000008        # Tholians and their webs (UT 1979 version)
296 OPTION_THINGY     = 0x00000010        # Space Thingy can shoot back (Stas, 2005)
297 OPTION_PROBE      = 0x00000020        # deep-space probes (DECUS version, 1980)
298 OPTION_SHOWME     = 0x00000040        # bracket Enterprise in chart
299 OPTION_RAMMING    = 0x00000080        # enemies may ram Enterprise (Almy)
300 OPTION_MVBADDY    = 0x00000100        # more enemies can move (Almy)
301 OPTION_BLKHOLE    = 0x00000200        # black hole may timewarp you (Stas, 2005)
302 OPTION_BASE       = 0x00000400        # bases have good shields (Stas, 2005)
303 OPTION_WORLDS     = 0x00000800        # logic for inhabited worlds (ESR, 2006)
304 OPTION_AUTOSCAN   = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
305 OPTION_CAPTURE    = 0x00002000        # Enable BSD-Trek capture (Almy, 2013).
306 OPTION_CLOAK      = 0x80004000        # Enable BSD-Trek capture (Almy, 2013).
307 OPTION_PLAIN      = 0x01000000        # user chose plain game
308 OPTION_ALMY       = 0x02000000        # user chose Almy variant
309 OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
310
311 # Define devices
312 DSRSENS         = 0
313 DLRSENS         = 1
314 DPHASER         = 2
315 DPHOTON         = 3
316 DLIFSUP         = 4
317 DWARPEN         = 5
318 DIMPULS         = 6
319 DSHIELD         = 7
320 DRADIO          = 8
321 DSHUTTL         = 9
322 DCOMPTR         = 10
323 DNAVSYS         = 11
324 DTRANSP         = 12
325 DSHCTRL         = 13
326 DDRAY           = 14
327 DDSP            = 15
328 DCLOAK          = 16
329 NDEVICES        = 17        # Number of devices
330
331 SKILL_NONE      = 0
332 SKILL_NOVICE    = 1
333 SKILL_FAIR      = 2
334 SKILL_GOOD      = 3
335 SKILL_EXPERT    = 4
336 SKILL_EMERITUS  = 5
337
338 def damaged(dev):
339     return (game.damage[dev] != 0.0)
340 def communicating():
341     return not damaged(DRADIO) or game.condition=="docked"
342
343 # Define future events
344 FSPY    = 0        # Spy event happens always (no future[] entry)
345                    # can cause SC to tractor beam Enterprise
346 FSNOVA  = 1        # Supernova
347 FTBEAM  = 2        # Commander tractor beams Enterprise
348 FSNAP   = 3        # Snapshot for time warp
349 FBATTAK = 4        # Commander attacks base
350 FCDBAS  = 5        # Commander destroys base
351 FSCMOVE = 6        # Supercommander moves (might attack base)
352 FSCDBAS = 7        # Supercommander destroys base
353 FDSPROB = 8        # Move deep space probe
354 FDISTR  = 9        # Emit distress call from an inhabited world
355 FENSLV  = 10       # Inhabited word is enslaved
356 FREPRO  = 11       # Klingons build a ship in an enslaved system
357 NEVENTS = 12
358
359 # Abstract out the event handling -- underlying data structures will change
360 # when we implement stateful events
361 def findevent(evtype):
362     return game.future[evtype]
363
364 class Enemy:
365     def __init__(self, etype=None, loc=None, power=None):
366         self.type = etype
367         self.location = Coord()
368         self.kdist = None
369         self.kavgd = None
370         if loc:
371             self.move(loc)
372         self.power = power        # enemy energy level
373         game.enemies.append(self)
374     def move(self, loc):
375         motion = (loc != self.location)
376         if self.location.i is not None and self.location.j is not None:
377             if motion:
378                 if self.type == 'T':
379                     game.quad[self.location.i][self.location.j] = '#'
380                 else:
381                     game.quad[self.location.i][self.location.j] = '.'
382         if loc:
383             self.location = copy.copy(loc)
384             game.quad[self.location.i][self.location.j] = self.type
385             self.kdist = self.kavgd = (game.sector - loc).distance()
386         else:
387             self.location = Coord()
388             self.kdist = self.kavgd = None
389             # Guard prevents failure on Tholian or thingy
390             if self in game.enemies:
391                 game.enemies.remove(self)
392         return motion
393     def __repr__(self):
394         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
395
396 class Gamestate:
397     def __init__(self):
398         self.options = None        # Game options
399         self.state = Snapshot()        # A snapshot structure
400         self.snapsht = Snapshot()        # Last snapshot taken for time-travel purposes
401         self.quad = None        # contents of our quadrant
402         self.damage = [0.0] * NDEVICES        # damage encountered
403         self.future = []        # future events
404         i = NEVENTS
405         while i > 0:
406             i -= 1
407             self.future.append(Event())
408         self.passwd  = None        # Self Destruct password
409         self.enemies = []
410         self.quadrant = None        # where we are in the large
411         self.sector = None        # where we are in the small
412         self.tholian = None        # Tholian enemy object
413         self.base = None        # position of base in current quadrant
414         self.battle = None        # base coordinates being attacked
415         self.plnet = None        # location of planet in quadrant
416         self.gamewon = False        # Finished!
417         self.ididit = False        # action taken -- allows enemy to attack
418         self.alive = False        # we are alive (not killed)
419         self.justin = False        # just entered quadrant
420         self.shldup = False        # shields are up
421         self.shldchg = False        # shield is changing (affects efficiency)
422         self.iscate = False        # super commander is here
423         self.ientesc = False        # attempted escape from supercommander
424         self.resting = False        # rest time
425         self.icraft = False        # Kirk in Galileo
426         self.landed = False        # party on planet (true), on ship (false)
427         self.alldone = False        # game is now finished
428         self.neutz = False        # Romulan Neutral Zone
429         self.isarmed = False        # probe is armed
430         self.inorbit = False        # orbiting a planet
431         self.imine = False        # mining
432         self.icrystl = False        # dilithium crystals aboard
433         self.iseenit = False        # seen base attack report
434         self.thawed = False        # thawed game
435         self.condition = None        # "green", "yellow", "red", "docked", "dead"
436         self.iscraft = None        # "onship", "offship", "removed"
437         self.skill = SKILL_NONE        # Player skill level
438         self.inkling = 0        # initial number of klingons
439         self.inbase = 0                # initial number of bases
440         self.incom = 0                # initial number of commanders
441         self.inscom = 0                # initial number of commanders
442         self.inrom = 0                # initial number of commanders
443         self.instar = 0                # initial stars
444         self.intorps = 0        # initial/max torpedoes
445         self.torps = 0                # number of torpedoes
446         self.ship = 0                # ship type -- 'E' is Enterprise
447         self.abandoned = 0        # count of crew abandoned in space
448         self.length = 0                # length of game
449         self.klhere = 0                # klingons here
450         self.casual = 0                # causalties
451         self.nhelp = 0                # calls for help
452         self.nkinks = 0                # count of energy-barrier crossings
453         self.iplnet = None        # planet # in quadrant
454         self.inplan = 0                # initial planets
455         self.irhere = 0                # Romulans in quadrant
456         self.isatb = 0                # =2 if super commander is attacking base
457         self.tourn = None        # tournament number
458         self.nprobes = 0        # number of probes available
459         self.inresor = 0.0        # initial resources
460         self.intime = 0.0        # initial time
461         self.inenrg = 0.0        # initial/max energy
462         self.inshld = 0.0        # initial/max shield
463         self.inlsr = 0.0        # initial life support resources
464         self.indate = 0.0        # initial date
465         self.energy = 0.0        # energy level
466         self.shield = 0.0        # shield level
467         self.warpfac = 0.0        # warp speed
468         self.lsupres = 0.0        # life support reserves
469         self.optime = 0.0        # time taken by current operation
470         self.damfac = 0.0        # damage factor
471         self.lastchart = 0.0        # time star chart was last updated
472         self.cryprob = 0.0        # probability that crystal will work
473         self.probe = None        # object holding probe course info
474         self.height = 0.0        # height of orbit around planet
475         self.score = 0.0        # overall score
476         self.perdate = 0.0        # rate of kills
477         self.idebug = False        # Debugging instrumentation enabled?
478         self.cdebug = False        # Debugging instrumentation for curses enabled?
479         self.statekscmdr = None # No SuperCommander coordinates yet.
480         self.brigcapacity = 400     # Enterprise brig capacity
481         self.brigfree = 400       # How many klingons can we put in the brig?
482         self.kcaptured = 0      # Total Klingons captured, for scoring.
483         self.iscloaked = False  # Cloaking device on?
484         self.ncviol = 0         # Algreon treaty violations
485         self.isviolreported = False # We have been warned
486         self.lcg_x = 0          # LCG generator value
487     def remkl(self):
488         return sum([q.klingons for (_i, _j, q) in list(self.state.traverse())])
489     def recompute(self):
490         # Stas thinks this should be (C expression):
491         # game.remkl() + len(game.state.kcmdr) > 0 ?
492         #        game.state.remres/(game.remkl() + 4*len(game.state.kcmdr)) : 99
493         # He says the existing expression is prone to divide-by-zero errors
494         # after killing the last klingon when score is shown -- perhaps also
495         # if the only remaining klingon is SCOM.
496         self.state.remtime = self.state.remres/(self.remkl() + 4*len(self.state.kcmdr))
497     def unwon(self):
498         "Are there Klingons remaining?"
499         return self.remkl()
500
501 FWON = 0
502 FDEPLETE = 1
503 FLIFESUP = 2
504 FNRG = 3
505 FBATTLE = 4
506 FNEG3 = 5
507 FNOVA = 6
508 FSNOVAED = 7
509 FABANDN = 8
510 FDILITHIUM = 9
511 FMATERIALIZE = 10
512 FPHASER = 11
513 FLOST = 12
514 FMINING = 13
515 FDPLANET = 14
516 FPNOVA = 15
517 FSSC = 16
518 FSTRACTOR = 17
519 FDRAY = 18
520 FTRIBBLE = 19
521 FHOLE = 20
522 FCREW = 21
523 FCLOAK = 22
524
525 # Code from ai.c begins here
526
527 def welcoming(iq):
528     "Would this quadrant welcome another Klingon?"
529     return iq.valid_quadrant() and \
530         not game.state.galaxy[iq.i][iq.j].supernova and \
531         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
532
533 def tryexit(enemy, look, irun):
534     "A bad guy attempts to bug out."
535     iq = Coord()
536     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))//QUADSIZE - 1
537     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))//QUADSIZE - 1
538     if not welcoming(iq):
539         return False
540     if enemy.type == 'R':
541         return False # Romulans cannot escape!
542     if not irun:
543         # avoid intruding on another commander's territory
544         if enemy.type == 'C':
545             if iq in game.state.kcmdr:
546                 return []
547             # refuse to leave if currently attacking starbase
548             if game.battle == game.quadrant:
549                 return []
550         # don't leave if over 1000 units of energy
551         if enemy.power > 1000.0:
552             return []
553     oldloc = copy.copy(enemy.location)
554     # handle local matters related to escape
555     enemy.move(None)
556     game.klhere -= 1
557     if game.condition != "docked":
558         newcnd()
559     # Handle global matters related to escape
560     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
561     game.state.galaxy[iq.i][iq.j].klingons += 1
562     if enemy.type == 'S':
563         game.iscate = False
564         game.ientesc = False
565         game.isatb = 0
566         schedule(FSCMOVE, 0.2777)
567         unschedule(FSCDBAS)
568         game.state.kscmdr = iq
569     else:
570         for cmdr in game.state.kcmdr:
571             if cmdr == game.quadrant:
572                 game.state.kcmdr.append(iq)
573                 break
574     # report move out of quadrant.
575     return [(True, enemy, oldloc, iq)]
576
577 # The bad-guy movement algorithm:
578 #
579 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
580 # If both are operating full strength, force is 1000. If both are damaged,
581 # force is -1000. Having shields down subtracts an additional 1000.
582 #
583 # 2. Enemy has forces equal to the energy of the attacker plus
584 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
585 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
586 #
587 # Attacker Initial energy levels (nominal):
588 # Klingon   Romulan   Commander   Super-Commander
589 # Novice    400        700        1200
590 # Fair      425        750        1250
591 # Good      450        800        1300        1750
592 # Expert    475        850        1350        1875
593 # Emeritus  500        900        1400        2000
594 # VARIANCE   75        200         200         200
595 #
596 # Enemy vessels only move prior to their attack. In Novice - Good games
597 # only commanders move. In Expert games, all enemy vessels move if there
598 # is a commander present. In Emeritus games all enemy vessels move.
599 #
600 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
601 # forces are 1000 greater than Enterprise.
602 #
603 # Agressive action on average cuts the distance between the ship and
604 # the enemy to 1/4 the original.
605 #
606 # 4.  At lower energy advantage, movement units are proportional to the
607 # advantage with a 650 advantage being to hold ground, 800 to move forward
608 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
609 #
610 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
611 # retreat, especially at high skill levels.
612 #
613 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
614
615 def movebaddy(enemy):
616     "Tactical movement for the bad guys."
617     goto = Coord()
618     look = Coord()
619     irun = False
620     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
621     if game.skill >= SKILL_EXPERT:
622         nbaddys = int(((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
623     else:
624         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
625     old_dist = enemy.kdist
626     mdist = int(old_dist + 0.5) # Nearest integer distance
627     # If SC, check with spy to see if should hi-tail it
628     if enemy.type == 'S' and \
629         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
630         irun = True
631         motion = -QUADSIZE
632     else:
633         # decide whether to advance, retreat, or hold position
634         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
635         if not game.shldup:
636             forces += 1000 # Good for enemy if shield is down!
637         if not damaged(DPHASER) or not damaged(DPHOTON):
638             if damaged(DPHASER): # phasers damaged
639                 forces += 300.0
640             else:
641                 forces -= 0.2*(game.energy - 2500.0)
642             if damaged(DPHOTON): # photon torpedoes damaged
643                 forces += 300.0
644             else:
645                 forces -= 50.0*game.torps
646         else:
647             # phasers and photon tubes both out!
648             forces += 1000.0
649         motion = 0
650         if forces <= 1000.0 and game.condition != "docked": # Typical situation
651             motion = ((forces + rnd.real(200))/150.0) - 5.0
652         else:
653             if forces > 1000.0: # Very strong -- move in for kill
654                 motion = (1.0 - rnd.real())**2 * old_dist + 1.0
655             if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off !
656                 motion -= game.skill*(2.0-rnd.real()**2)
657         if game.idebug:
658             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
659         # don't move if no motion
660         if motion == 0:
661             return []
662         # Limit motion according to skill
663         if abs(motion) > game.skill:
664             if motion < 0:
665                 motion = -game.skill
666             else:
667                 motion = game.skill
668     # calculate preferred number of steps
669     nsteps = abs(int(motion))
670     if motion > 0 and nsteps > mdist:
671         nsteps = mdist # don't overshoot
672     if nsteps > QUADSIZE:
673         nsteps = QUADSIZE # This shouldn't be necessary
674     if nsteps < 1:
675         nsteps = 1 # This shouldn't be necessary
676     if game.idebug:
677         proutn("NSTEPS = %d:" % nsteps)
678     # Compute preferred values of delta X and Y
679     m = game.sector - enemy.location
680     if 2.0 * abs(m.i) < abs(m.j):
681         m.i = 0
682     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
683         m.j = 0
684     m = (motion * m).sgn()
685     goto = enemy.location
686     # main move loop
687     for ll in range(nsteps):
688         if game.idebug:
689             proutn(" %d" % (ll+1))
690         # Check if preferred position available
691         look = goto + m
692         if m.i < 0:
693             krawli = 1
694         else:
695             krawli = -1
696         if m.j < 0:
697             krawlj = 1
698         else:
699             krawlj = -1
700         success = False
701         attempts = 0 # Settle mysterious hang problem
702         while attempts < 20 and not success:
703             attempts += 1
704             if look.i < 0 or look.i >= QUADSIZE:
705                 if motion < 0:
706                     return tryexit(enemy, look, irun)
707                 if krawli == m.i or m.j == 0:
708                     break
709                 look.i = goto.i + krawli
710                 krawli = -krawli
711             elif look.j < 0 or look.j >= QUADSIZE:
712                 if motion < 0:
713                     return tryexit(enemy, look, irun)
714                 if krawlj == m.j or m.i == 0:
715                     break
716                 look.j = goto.j + krawlj
717                 krawlj = -krawlj
718             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
719                 # See if enemy should ram ship
720                 if game.quad[look.i][look.j] == game.ship and \
721                     (enemy.type == 'C' or enemy.type == 'S'):
722                     collision(rammed=True, enemy=enemy)
723                     return []
724                 if krawli != m.i and m.j != 0:
725                     look.i = goto.i + krawli
726                     krawli = -krawli
727                 elif krawlj != m.j and m.i != 0:
728                     look.j = goto.j + krawlj
729                     krawlj = -krawlj
730                 else:
731                     break # we have failed
732             else:
733                 success = True
734         if success:
735             goto = look
736             if game.idebug:
737                 proutn(repr(goto))
738         else:
739             break # done early
740     if game.idebug:
741         skip(1)
742     # Enemy moved, but is still in sector
743     return [(False, enemy, old_dist, goto)]
744
745 def moveklings():
746     "Sequence Klingon tactical movement."
747     if game.idebug:
748         prout("== MOVCOM")
749     # Figure out which Klingon is the commander (or Supercommander)
750     # and do move
751     tacmoves = []
752     if game.quadrant in game.state.kcmdr:
753         for enemy in game.enemies:
754             if enemy.type == 'C':
755                 tacmoves += movebaddy(enemy)
756     if game.state.kscmdr == game.quadrant:
757         for enemy in game.enemies:
758             if enemy.type == 'S':
759                 tacmoves += movebaddy(enemy)
760                 break
761     # If skill level is high, move other Klingons and Romulans too!
762     # Move these last so they can base their actions on what the
763     # commander(s) do.
764     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
765         for enemy in game.enemies:
766             if enemy.type in ('K', 'R'):
767                 tacmoves += movebaddy(enemy)
768     return tacmoves
769
770 def movescom(iq, avoid):
771     "Supercommander movement helper."
772     # Avoid quadrants with bases if we want to avoid Enterprise
773     if not welcoming(iq) or (avoid and iq in game.state.baseq):
774         return False
775     if game.justin and not game.iscate:
776         return False
777     # do the move
778     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
779     game.state.kscmdr = iq
780     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
781     if game.state.kscmdr == game.quadrant:
782         # SC has scooted, remove him from current quadrant
783         game.iscate = False
784         game.isatb = 0
785         game.ientesc = False
786         unschedule(FSCDBAS)
787         for enemy in game.enemies:
788             if enemy.type == 'S':
789                 enemy.move(None)
790         game.klhere -= 1
791         if game.condition != "docked":
792             newcnd()
793         sortenemies()
794     # check for a helpful planet
795     for i in range(game.inplan):
796         if game.state.planets[i].quadrant == game.state.kscmdr and \
797             game.state.planets[i].crystals == "present":
798             # destroy the planet
799             game.state.planets[i].pclass = "destroyed"
800             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
801             if communicating():
802                 announce()
803                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
804                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
805                 prout(_("   by the Super-commander.\""))
806             break
807     return True # looks good!
808
809 def supercommander():
810     "Move the Super Commander."
811     iq = Coord()
812     sc = Coord()
813     ibq = Coord()
814     idelta = Coord()
815     basetbl = []
816     if game.idebug:
817         prout("== SUPERCOMMANDER")
818     # Decide on being active or passive
819     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.remkl())/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
820             (game.state.date-game.indate) < 3.0)
821     if not game.iscate and avoid:
822         # compute move away from Enterprise
823         idelta = game.state.kscmdr-game.quadrant
824         if idelta.distance() > 2.0:
825             # circulate in space
826             idelta.i = game.state.kscmdr.j-game.quadrant.j
827             idelta.j = game.quadrant.i-game.state.kscmdr.i
828     else:
829         # compute distances to starbases
830         if not game.state.baseq:
831             # nothing left to do
832             unschedule(FSCMOVE)
833             return
834         sc = game.state.kscmdr
835         for (i, base) in enumerate(game.state.baseq):
836             basetbl.append((i, (base - sc).distance()))
837         if game.state.baseq > 1:
838             basetbl.sort(key=lambda x: x[1])
839         # look for nearest base without a commander, no Enterprise, and
840         # without too many Klingons, and not already under attack.
841         ifindit = iwhichb = 0
842         for (i2, base) in enumerate(game.state.baseq):
843             i = basetbl[i2][0]        # bug in original had it not finding nearest
844             if base == game.quadrant or base == game.battle or not welcoming(base):
845                 continue
846             # if there is a commander, and no other base is appropriate,
847             # we will take the one with the commander
848             for cmdr in game.state.kcmdr:
849                 if base == cmdr and ifindit != 2:
850                     ifindit = 2
851                     iwhichb = i
852                     break
853             else:        # no commander -- use this one
854                 ifindit = 1
855                 iwhichb = i
856                 break
857         if ifindit == 0:
858             return # Nothing suitable -- wait until next time
859         ibq = game.state.baseq[iwhichb]
860         # decide how to move toward base
861         idelta = ibq - game.state.kscmdr
862     # Maximum movement is 1 quadrant in either or both axes
863     idelta = idelta.sgn()
864     # try moving in both x and y directions
865     # there was what looked like a bug in the Almy C code here,
866     # but it might be this translation is just wrong.
867     iq = game.state.kscmdr + idelta
868     if not movescom(iq, avoid):
869         # failed -- try some other maneuvers
870         if idelta.i == 0 or idelta.j == 0:
871             # attempt angle move
872             if idelta.i != 0:
873                 iq.j = game.state.kscmdr.j + 1
874                 if not movescom(iq, avoid):
875                     iq.j = game.state.kscmdr.j - 1
876                     movescom(iq, avoid)
877             elif idelta.j != 0:
878                 iq.i = game.state.kscmdr.i + 1
879                 if not movescom(iq, avoid):
880                     iq.i = game.state.kscmdr.i - 1
881                     movescom(iq, avoid)
882         else:
883             # try moving just in x or y
884             iq.j = game.state.kscmdr.j
885             if not movescom(iq, avoid):
886                 iq.j = game.state.kscmdr.j + idelta.j
887                 iq.i = game.state.kscmdr.i
888                 movescom(iq, avoid)
889     # check for a base
890     if len(game.state.baseq) == 0:
891         unschedule(FSCMOVE)
892     else:
893         for ibq in game.state.baseq:
894             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
895                 # attack the base
896                 if avoid:
897                     return # no, don't attack base!
898                 game.iseenit = False
899                 game.isatb = 1
900                 schedule(FSCDBAS, rnd.real(1.0, 3.0))
901                 if is_scheduled(FCDBAS):
902                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
903                 if not communicating():
904                     return # no warning
905                 game.iseenit = True
906                 announce()
907                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
908                       % game.state.kscmdr)
909                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
910                 proutn(_("   It can survive until stardate %d.\"") \
911                        % int(scheduled(FSCDBAS)))
912                 if not game.resting:
913                     return
914                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
915                 if not ja():
916                     return
917                 game.resting = False
918                 game.optime = 0.0 # actually finished
919                 return
920     # Check for intelligence report
921     if not game.idebug and \
922         (rnd.withprob(0.8) or \
923          (not communicating()) or \
924          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
925         return
926     announce()
927     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
928     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
929     return
930
931 def movetholian():
932     "Move the Tholian."
933     if not game.tholian or game.justin:
934         return
935     tid = Coord()
936     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
937         tid.i = 0
938         tid.j = QUADSIZE-1
939     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
940         tid.i = QUADSIZE-1
941         tid.j = QUADSIZE-1
942     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
943         tid.i = QUADSIZE-1
944         tid.j = 0
945     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
946         tid.i = 0
947         tid.j = 0
948     else:
949         # something is wrong!
950         game.tholian.move(None)
951         prout("***Internal error: Tholian in a bad spot.")
952         return
953     print("Tholian moving from %s to %s" % (game.tholian.location, tid))
954     # do nothing if we are blocked
955     if game.quad[tid.i][tid.j] not in ('.', '#'):
956         return
957     here = copy.copy(game.tholian.location)
958     delta = (tid - game.tholian.location).sgn()
959     # move in x axis
960     while here.i != tid.i:
961         here.i += delta.i
962         if game.quad[here.i][here.j] == '.':
963             game.tholian.move(here)
964     # move in y axis
965     while here.j != tid.j:
966         here.j += delta.j
967         if game.quad[here.i][here.j] == '.':
968             game.tholian.move(here)
969     # check to see if all holes plugged
970     for i in range(QUADSIZE):
971         if game.quad[0][i] != '#' and game.quad[0][i] != 'T':
972             return
973         if game.quad[QUADSIZE-1][i] != '#' and game.quad[QUADSIZE-1][i] != 'T':
974             return
975         if game.quad[i][0] != '#' and game.quad[i][0] != 'T':
976             return
977         if game.quad[i][QUADSIZE-1] != '#' and game.quad[i][QUADSIZE-1] != 'T':
978             return
979     # All plugged up -- Tholian splits
980     game.quad[game.tholian.location.i][game.tholian.location.j] = '#'
981     dropin(' ')
982     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
983     game.tholian.move(None)
984     return
985
986 # Code from battle.c begins here
987
988 def cloak():
989     "Change cloaking-device status."
990     if game.ship == 'F':
991         prout(_("Ye Faerie Queene hath no cloaking device."))
992         return
993
994     key = scanner.nexttok()
995
996     if key == "IHREAL":
997         huh()
998         return
999
1000     action = None
1001     if key == "IHALPHA":
1002         if scanner.sees("on"):
1003             if game.iscloaked:
1004                 prout(_("The cloaking device has already been switched on."))
1005                 return
1006             action = "CLON"
1007         elif scanner.sees("off"):
1008             if not game.iscloaked:
1009                 prout(_("The cloaking device has already been switched off."))
1010                 return
1011             action = "CLOFF"
1012         else:
1013             huh()
1014             return
1015     else:
1016         if not game.iscloaked:
1017             proutn(_("Switch cloaking device on? "))
1018             if not ja():
1019                 return
1020             action = "CLON"
1021         else:
1022             proutn(_("Switch cloaking device off? "))
1023             if not ja():
1024                 return
1025             action = "CLOFF"
1026     if action is None:
1027         return
1028
1029     if action == "CLOFF":
1030         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1031             prout(_("Spock- \"Captain, the Treaty of Algeron is in effect.\n   Are you sure this is wise?\""))
1032             if not ja():
1033                 return
1034         prout("Engineer Scott- \"Aye, Sir.\"")
1035         game.iscloaked = False
1036         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1037             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
1038             game.ncviol += 1
1039             game.isviolreported = True
1040
1041             #if (neutz and game.state.date >= ALGERON) finish(FCLOAK);
1042             return
1043
1044     if action == "CLON":
1045         if damaged(DCLOAK):
1046             prout(_("Engineer Scott- \"The cloaking device is damaged, Sir.\""))
1047             return
1048
1049         if game.condition == "docked":
1050             prout(_("You cannot cloak while docked."))
1051
1052         if game.state.date >= ALGERON and not game.isviolreported:
1053             prout(_("Spock- \"Captain, using the cloaking device is a violation"))
1054             prout(_("  of the Treaty of Algeron. Considering the alternatives,"))
1055             proutn(_("  are you sure this is wise? "))
1056             if not ja():
1057                 return
1058         prout(_("Engineer Scott- \"Cloaking device has engaging, Sir...\""))
1059         attack(True)
1060         prout(_("Engineer Scott- \"Cloaking device has engaged, Sir.\""))
1061         game.iscloaked = True
1062
1063         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1064             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
1065             game.ncviol += 1
1066             game.isviolreported = True
1067
1068 def doshield(shraise):
1069     "Change shield status."
1070     action = "NONE"
1071     game.ididit = False
1072     if shraise:
1073         action = "SHUP"
1074     else:
1075         key = scanner.nexttok()
1076         if key == "IHALPHA":
1077             if scanner.sees("transfer"):
1078                 action = "NRG"
1079             else:
1080                 if damaged(DSHIELD):
1081                     prout(_("Shields damaged and down."))
1082                     return
1083                 if scanner.sees("up"):
1084                     action = "SHUP"
1085                 elif scanner.sees("down"):
1086                     action = "SHDN"
1087         if action == "NONE":
1088             proutn(_("Do you wish to change shield energy? "))
1089             if ja():
1090                 action = "NRG"
1091             elif damaged(DSHIELD):
1092                 prout(_("Shields damaged and down."))
1093                 return
1094             elif game.shldup:
1095                 proutn(_("Shields are up. Do you want them down? "))
1096                 if ja():
1097                     action = "SHDN"
1098                 else:
1099                     scanner.chew()
1100                     return
1101             else:
1102                 proutn(_("Shields are down. Do you want them up? "))
1103                 if ja():
1104                     action = "SHUP"
1105                 else:
1106                     scanner.chew()
1107                     return
1108     if action == "SHUP": # raise shields
1109         if game.shldup:
1110             prout(_("Shields already up."))
1111             return
1112         game.shldup = True
1113         game.shldchg = True
1114         if game.condition != "docked":
1115             game.energy -= 50.0
1116         prout(_("Shields raised."))
1117         if game.energy <= 0:
1118             skip(1)
1119             prout(_("Shields raising uses up last of energy."))
1120             finish(FNRG)
1121             return
1122         game.ididit = True
1123         return
1124     elif action == "SHDN":
1125         if not game.shldup:
1126             prout(_("Shields already down."))
1127             return
1128         game.shldup = False
1129         game.shldchg = True
1130         prout(_("Shields lowered."))
1131         game.ididit = True
1132         return
1133     elif action == "NRG":
1134         while scanner.nexttok() != "IHREAL":
1135             scanner.chew()
1136             proutn(_("Energy to transfer to shields- "))
1137         nrg = scanner.real
1138         scanner.chew()
1139         if nrg == 0:
1140             return
1141         if nrg > game.energy:
1142             prout(_("Insufficient ship energy."))
1143             return
1144         game.ididit = True
1145         if game.shield+nrg >= game.inshld:
1146             prout(_("Shield energy maximized."))
1147             if game.shield+nrg > game.inshld:
1148                 prout(_("Excess energy requested returned to ship energy"))
1149             game.energy -= game.inshld-game.shield
1150             game.shield = game.inshld
1151             return
1152         if nrg < 0.0 and game.energy-nrg > game.inenrg:
1153             # Prevent shield drain loophole
1154             skip(1)
1155             prout(_("Engineering to bridge--"))
1156             prout(_("  Scott here. Power circuit problem, Captain."))
1157             prout(_("  I can't drain the shields."))
1158             game.ididit = False
1159             return
1160         if game.shield+nrg < 0:
1161             prout(_("All shield energy transferred to ship."))
1162             game.energy += game.shield
1163             game.shield = 0.0
1164             return
1165         proutn(_("Scotty- \""))
1166         if nrg > 0:
1167             prout(_("Transferring energy to shields.\""))
1168         else:
1169             prout(_("Draining energy from shields.\""))
1170         game.shield += nrg
1171         game.energy -= nrg
1172         return
1173
1174 def randdevice():
1175     "Choose a device to damage, at random."
1176     weights = (
1177         105,       # DSRSENS: short range scanners         10.5%
1178         105,       # DLRSENS: long range scanners          10.5%
1179         120,       # DPHASER: phasers                      12.0%
1180         120,       # DPHOTON: photon torpedoes             12.0%
1181         25,        # DLIFSUP: life support                  2.5%
1182         65,        # DWARPEN: warp drive                    6.5%
1183         70,        # DIMPULS: impulse engines               6.5%
1184         135,       # DSHIELD: deflector shields            13.5%
1185         30,        # DRADIO:  subspace radio                3.0%
1186         45,        # DSHUTTL: shuttle                       4.5%
1187         15,        # DCOMPTR: computer                      1.5%
1188         20,        # NAVCOMP: navigation system             2.0%
1189         75,        # DTRANSP: transporter                   7.5%
1190         20,        # DSHCTRL: high-speed shield controller  2.0%
1191         10,        # DDRAY: death ray                       1.0%
1192         30,        # DDSP: deep-space probes                3.0%
1193         10,        # DCLOAK: the cloaking device            1.0
1194     )
1195     assert(sum(weights) == 1000)
1196     idx = rnd.integer(1000)
1197     wsum = 0
1198     for (i, w) in enumerate(weights):
1199         wsum += w
1200         if idx < wsum:
1201             return i
1202     return None        # we should never get here
1203
1204 def collision(rammed, enemy):
1205     "Collision handling for rammong events."
1206     prouts(_("***RED ALERT!  RED ALERT!"))
1207     skip(1)
1208     prout(_("***COLLISION IMMINENT."))
1209     skip(2)
1210     proutn("***")
1211     proutn(crmshp())
1212     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1213     if rammed:
1214         proutn(_(" rammed by "))
1215     else:
1216         proutn(_(" rams "))
1217     proutn(crmena(False, enemy.type, "sector", enemy.location))
1218     if rammed:
1219         proutn(_(" (original position)"))
1220     skip(1)
1221     deadkl(enemy.location, enemy.type, game.sector)
1222     proutn("***" + crmshp() + " heavily damaged.")
1223     icas = rnd.integer(10, 30)
1224     prout(_("***Sickbay reports %d casualties") % icas)
1225     game.casual += icas
1226     game.state.crew -= icas
1227     # In the pre-SST2K version, all devices got equiprobably damaged,
1228     # which was silly.  Instead, pick up to half the devices at
1229     # random according to our weighting table,
1230     ncrits = rnd.integer(NDEVICES//2)
1231     while ncrits > 0:
1232         ncrits -= 1
1233         dev = randdevice()
1234         if game.damage[dev] < 0:
1235             continue
1236         extradm = (10.0*hardness*rnd.real()+1.0)*game.damfac
1237         # Damage for at least time of travel!
1238         game.damage[dev] += game.optime + extradm
1239     game.shldup = False
1240     prout(_("***Shields are down."))
1241     if game.unwon():
1242         announce()
1243         damagereport()
1244     else:
1245         finish(FWON)
1246     return
1247
1248 def torpedo(origin, bearing, dispersion, number, nburst):
1249     "Let a photon torpedo fly"
1250     if not damaged(DSRSENS) or game.condition == "docked":
1251         setwnd(srscan_window)
1252     else:
1253         setwnd(message_window)
1254     ac = bearing + 0.25*dispersion        # dispersion is a random variable
1255     bullseye = (15.0 - bearing)*0.5235988
1256     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin))
1257     bumpto = Coord(0, 0)
1258     # Loop to move a single torpedo
1259     setwnd(message_window)
1260     for step in range(1, QUADSIZE*2):
1261         if not track.nexttok():
1262             break
1263         w = track.sector()
1264         if not w.valid_sector():
1265             break
1266         iquad = game.quad[w.i][w.j]
1267         tracktorpedo(w, step, number, nburst, iquad)
1268         if iquad == '.':
1269             continue
1270         # hit something
1271         setwnd(message_window)
1272         if not damaged(DSRSENS) or game.condition == "docked":
1273             skip(1)        # start new line after text track
1274         if iquad in ('E', 'F'): # Hit our ship
1275             skip(1)
1276             prout(_("Torpedo hits %s.") % crmshp())
1277             hit = 700.0 + rnd.real(100) - \
1278                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1279             newcnd() # we're blown out of dock
1280             if game.landed or game.condition == "docked":
1281                 return hit # Cheat if on a planet
1282             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1283             # is 143 degrees, which is almost exactly 4.8 clockface units
1284             displacement = course(track.bearing+rnd.real(-2.4, 2.4), distance=2**0.5)
1285             displacement.nexttok()
1286             bumpto = displacement.sector()
1287             if not bumpto.valid_sector():
1288                 return hit
1289             if game.quad[bumpto.i][bumpto.j] == ' ':
1290                 finish(FHOLE)
1291                 return hit
1292             if game.quad[bumpto.i][bumpto.j] != '.':
1293                 # can't move into object
1294                 return hit
1295             game.sector = bumpto
1296             proutn(crmshp())
1297             game.quad[w.i][w.j] = '.'
1298             game.quad[bumpto.i][bumpto.j] = iquad
1299             prout(_(" displaced by blast to Sector %s ") % bumpto)
1300             for enemy in game.enemies:
1301                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1302             sortenemies()
1303             return None
1304         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy
1305             # find the enemy
1306             if iquad in ('C', 'S') and rnd.withprob(0.05):
1307                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1308                 prout(_("   torpedo neutralized."))
1309                 return None
1310             for enemy in game.enemies:
1311                 if w == enemy.location:
1312                     kp = math.fabs(enemy.power)
1313                     h1 = 700.0 + rnd.integer(100) - \
1314                         1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1315                     h1 = math.fabs(h1)
1316                     if kp < h1:
1317                         h1 = kp
1318                     if enemy.power < 0:
1319                         enemy.power -= -h1
1320                     else:
1321                         enemy.power -= h1
1322                     if enemy.power == 0:
1323                         deadkl(w, iquad, w)
1324                         return None
1325                     proutn(crmena(True, iquad, "sector", w))
1326                     displacement = course(track.bearing+rnd.real(-2.4, 2.4), distance=2**0.5)
1327                     displacement.nexttok()
1328                     bumpto = displacement.sector()
1329                     if not bumpto.valid_sector():
1330                         prout(_(" damaged but not destroyed."))
1331                         return
1332                     if game.quad[bumpto.i][bumpto.j] == ' ':
1333                         prout(_(" buffeted into black hole."))
1334                         deadkl(w, iquad, bumpto)
1335                     if game.quad[bumpto.i][bumpto.j] != '.':
1336                         prout(_(" damaged but not destroyed."))
1337                     else:
1338                         prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1339                         enemy.location = bumpto
1340                         game.quad[w.i][w.j] = '.'
1341                         game.quad[bumpto.i][bumpto.j] = iquad
1342                         for tenemy in game.enemies:
1343                             tenemy.kdist = tenemy.kavgd = (game.sector-tenemy.location).distance()
1344                         sortenemies()
1345                     break
1346             else:
1347                 prout("Internal error, no enemy where expected!")
1348                 raise SystemExit(1)
1349             return None
1350         elif iquad == 'B': # Hit a base
1351             skip(1)
1352             prout(_("***STARBASE DESTROYED.."))
1353             game.state.baseq = [x for x in game.state.baseq if x != game.quadrant]
1354             game.quad[w.i][w.j] = '.'
1355             game.base.invalidate()
1356             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
1357             game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
1358             game.state.basekl += 1
1359             newcnd()
1360             return None
1361         elif iquad == 'P': # Hit a planet
1362             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1363             game.state.nplankl += 1
1364             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1365             game.iplnet.pclass = "destroyed"
1366             game.iplnet = None
1367             game.plnet.invalidate()
1368             game.quad[w.i][w.j] = '.'
1369             if game.landed:
1370                 # captain perishes on planet
1371                 finish(FDPLANET)
1372             return None
1373         elif iquad == '@': # Hit an inhabited world -- very bad!
1374             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1375             game.state.nworldkl += 1
1376             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1377             game.iplnet.pclass = "destroyed"
1378             game.iplnet = None
1379             game.plnet.invalidate()
1380             game.quad[w.i][w.j] = '.'
1381             if game.landed:
1382                 # captain perishes on planet
1383                 finish(FDPLANET)
1384             prout(_("The torpedo destroyed an inhabited planet."))
1385             return None
1386         elif iquad == '*': # Hit a star
1387             if rnd.withprob(0.9):
1388                 nova(w)
1389             else:
1390                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1391             return None
1392         elif iquad == '?': # Hit a thingy
1393             if not (game.options & OPTION_THINGY) or rnd.withprob(0.3):
1394                 skip(1)
1395                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1396                 skip(1)
1397                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1398                 skip(1)
1399                 proutn(_("Mr. Spock-"))
1400                 prouts(_("  \"Fascinating!\""))
1401                 skip(1)
1402                 deadkl(w, iquad, w)
1403             else:
1404                 # Stas Sergeev added the possibility that
1405                 # you can shove the Thingy and piss it off.
1406                 # It then becomes an enemy and may fire at you.
1407                 thing.angry()
1408             return None
1409         elif iquad == ' ': # Black hole
1410             skip(1)
1411             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1412             return None
1413         elif iquad == '#': # hit the web
1414             skip(1)
1415             prout(_("***Torpedo absorbed by Tholian web."))
1416             return None
1417         elif iquad == 'T':  # Hit a Tholian
1418             h1 = 700.0 + rnd.integer(100) - \
1419                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1420             h1 = math.fabs(h1)
1421             if h1 >= 600:
1422                 game.quad[w.i][w.j] = '.'
1423                 deadkl(w, iquad, w)
1424                 game.tholian = None
1425                 return None
1426             skip(1)
1427             proutn(crmena(True, 'T', "sector", w))
1428             if rnd.withprob(0.05):
1429                 prout(_(" survives photon blast."))
1430                 return None
1431             prout(_(" disappears."))
1432             game.tholian.move(None)
1433             game.quad[w.i][w.j] = '#'
1434             dropin(' ')
1435             return None
1436         else: # Problem!
1437             skip(1)
1438             proutn("Don't know how to handle torpedo collision with ")
1439             proutn(crmena(True, iquad, "sector", w))
1440             skip(1)
1441             return None
1442         break
1443     skip(1)
1444     setwnd(message_window)
1445     prout(_("Torpedo missed."))
1446     return None
1447
1448 def fry(hit):
1449     "Critical-hit resolution."
1450     if hit < (275.0-25.0*game.skill)*rnd.real(1.0, 1.5):
1451         return
1452     ncrit = int(1.0 + hit/(500.0+rnd.real(100)))
1453     proutn(_("***CRITICAL HIT--"))
1454     # Select devices and cause damage
1455     cdam = []
1456     while ncrit > 0:
1457         while True:
1458             j = randdevice()
1459             # Cheat to prevent shuttle damage unless on ship
1460             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship") or (j == DCLOAK and game.ship != 'E' or j == DDRAY)):
1461                 break
1462         cdam.append(j)
1463         extradm = (hit*game.damfac)/(ncrit*rnd.real(75, 100))
1464         game.damage[j] += extradm
1465         ncrit -= 1
1466     skipcount = 0
1467     for (i, j) in enumerate(cdam):
1468         proutn(device[j])
1469         if skipcount % 3 == 2 and i < len(cdam)-1:
1470             skip(1)
1471         skipcount += 1
1472         if i < len(cdam)-1:
1473             proutn(_(" and "))
1474     prout(_(" damaged."))
1475     if damaged(DSHIELD) and game.shldup:
1476         prout(_("***Shields knocked down."))
1477         game.shldup = False
1478     if damaged(DCLOAK) and game.iscloaked:
1479         prout(_("***Cloaking device rendered inoperative."))
1480         game.iscloaked = False
1481
1482 def attack(torps_ok):
1483     # bad guy attacks us
1484     # torps_ok == False forces use of phasers in an attack
1485     if game.iscloaked:
1486         return
1487     # game could be over at this point, check
1488     if game.alldone:
1489         return
1490     attempt = False
1491     ihurt = False
1492     hitmax = 0.0
1493     hittot = 0.0
1494     chgfac = 1.0
1495     where = "neither"
1496     if game.idebug:
1497         prout("=== ATTACK!")
1498     # Tholian gets to move before attacking
1499     if game.tholian:
1500         movetholian()
1501     # if you have just entered the RNZ, you'll get a warning
1502     if game.neutz: # The one chance not to be attacked
1503         game.neutz = False
1504         return
1505     # commanders get a chance to tac-move towards you
1506     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:
1507         for (bugout, enemy, old, goto) in  moveklings():
1508             if bugout:
1509                 # we know about this if either short or long range
1510                 # sensors are working
1511                 if damaged(DSRSENS) and damaged(DLRSENS) \
1512                        and game.condition != "docked":
1513                     prout(crmena(True, enemy.type, "sector", old) + \
1514                           (_(" escapes to Quadrant %s (and regains strength).") % goto))
1515             else: # Enemy still in-sector
1516                 if enemy.move(goto):
1517                     if not damaged(DSRSENS) or game.condition == "docked":
1518                         proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
1519                         if enemy.kdist < old:
1520                             proutn(_(" advances to "))
1521                         else:
1522                             proutn(_(" retreats to "))
1523                         prout("Sector %s." % goto)
1524         sortenemies()
1525     # if no enemies remain after movement, we're done
1526     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
1527         return
1528     # set up partial hits if attack happens during shield status change
1529     pfac = 1.0/game.inshld
1530     if game.shldchg:
1531         chgfac = 0.25 + rnd.real(0.5)
1532     skip(1)
1533     # message verbosity control
1534     if game.skill <= SKILL_FAIR:
1535         where = "sector"
1536     for enemy in game.enemies:
1537         if enemy.power < 0:
1538             continue        # too weak to attack
1539         # compute hit strength and diminish shield power
1540         r = rnd.real()
1541         # Increase chance of photon torpedos if docked or enemy energy is low
1542         if game.condition == "docked":
1543             r *= 0.25
1544         if enemy.power < 500:
1545             r *= 0.25
1546         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1547             continue
1548         # different enemies have different probabilities of throwing a torp
1549         usephasers = not torps_ok or \
1550             (enemy.type == 'K' and r > 0.0005) or \
1551             (enemy.type == 'C' and r > 0.015) or \
1552             (enemy.type == 'R' and r > 0.3) or \
1553             (enemy.type == 'S' and r > 0.07) or \
1554             (enemy.type == '?' and r > 0.05)
1555         if usephasers:            # Enemy uses phasers
1556             if game.condition == "docked":
1557                 continue # Don't waste the effort!
1558             attempt = True # Attempt to attack
1559             dustfac = rnd.real(0.8, 0.85)
1560             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1561             enemy.power *= 0.75
1562         else: # Enemy uses photon torpedo
1563             # We should be able to make the bearing() method work here
1564             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1565             hit = 0
1566             proutn(_("***TORPEDO INCOMING"))
1567             if not damaged(DSRSENS):
1568                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1569             attempt = True
1570             prout("  ")
1571             dispersion = (rnd.real()+rnd.real())*0.5 - 0.5
1572             dispersion += 0.002*enemy.power*dispersion
1573             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1574             if game.unwon() == 0:
1575                 finish(FWON) # Klingons did themselves in!
1576             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1577                 return # Supernova or finished
1578             if hit is None:
1579                 continue
1580         # incoming phaser or torpedo, shields may dissipate it
1581         if game.shldup or game.shldchg or game.condition == "docked":
1582             # shields will take hits
1583             propor = pfac * game.shield
1584             if game.condition == "docked":
1585                 propor *= 2.1
1586             if propor < 0.1:
1587                 propor = 0.1
1588             hitsh = propor*chgfac*hit+1.0
1589             absorb = 0.8*hitsh
1590             if absorb > game.shield:
1591                 absorb = game.shield
1592             game.shield -= absorb
1593             hit -= hitsh
1594             # taking a hit blasts us out of a starbase dock
1595             if game.condition == "docked":
1596                 dock(False)
1597             # but the shields may take care of it
1598             if propor > 0.1 and hit < 0.005*game.energy:
1599                 continue
1600         # hit from this opponent got through shields, so take damage
1601         ihurt = True
1602         proutn(_("%d unit hit") % int(hit))
1603         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1604             proutn(_(" on the ") + crmshp())
1605         if not damaged(DSRSENS) and usephasers:
1606             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1607         skip(1)
1608         # Decide if hit is critical
1609         if hit > hitmax:
1610             hitmax = hit
1611         hittot += hit
1612         fry(hit)
1613         game.energy -= hit
1614     if game.energy <= 0:
1615         # Returning home upon your shield, not with it...
1616         finish(FBATTLE)
1617         return
1618     if not attempt and game.condition == "docked":
1619         prout(_("***Enemies decide against attacking your ship."))
1620     percent = 100.0*pfac*game.shield+0.5
1621     if not ihurt:
1622         # Shields fully protect ship
1623         proutn(_("Enemy attack reduces shield strength to "))
1624     else:
1625         # Emit message if starship suffered hit(s)
1626         skip(1)
1627         proutn(_("Energy left %2d    shields ") % int(game.energy))
1628         if game.shldup:
1629             proutn(_("up "))
1630         elif not damaged(DSHIELD):
1631             proutn(_("down "))
1632         else:
1633             proutn(_("damaged, "))
1634     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1635     # Check if anyone was hurt
1636     if hitmax >= 200 or hittot >= 500:
1637         icas = rnd.integer(int(hittot * 0.015))
1638         if icas >= 2:
1639             skip(1)
1640             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1641             prout(_("   in that last attack.\""))
1642             game.casual += icas
1643             game.state.crew -= icas
1644     # After attack, reset average distance to enemies
1645     for enemy in game.enemies:
1646         enemy.kavgd = enemy.kdist
1647     sortenemies()
1648     return
1649
1650 def deadkl(w, etype, mv):
1651     "Kill a Klingon, Tholian, Romulan, or Thingy."
1652     # Added mv to allow enemy to "move" before dying
1653     proutn(crmena(True, etype, "sector", mv))
1654     # Decide what kind of enemy it is and update appropriately
1655     if etype == 'R':
1656         # Chalk up a Romulan
1657         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1658         game.irhere -= 1
1659         game.state.nromrem -= 1
1660     elif etype == 'T':
1661         # Killed a Tholian
1662         game.tholian = None
1663     elif etype == '?':
1664         # Killed a Thingy
1665         global thing
1666         thing = None
1667     else:
1668         # Killed some type of Klingon
1669         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1670         game.klhere -= 1
1671         if etype == 'C':
1672             game.state.kcmdr.remove(game.quadrant)
1673             unschedule(FTBEAM)
1674             if game.state.kcmdr:
1675                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1676             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1677                 unschedule(FCDBAS)
1678         elif etype ==  'K':
1679             pass
1680         elif etype ==  'S':
1681             game.state.nscrem -= 1
1682             game.state.kscmdr.invalidate()
1683             game.isatb = 0
1684             game.iscate = False
1685             unschedule(FSCMOVE)
1686             unschedule(FSCDBAS)
1687     # For each kind of enemy, finish message to player
1688     prout(_(" destroyed."))
1689     if game.unwon() == 0:
1690         return
1691     game.recompute()
1692     # Remove enemy ship from arrays describing local conditions
1693     for e in game.enemies:
1694         if e.location == w:
1695             e.move(None)
1696             break
1697     return
1698
1699 def targetcheck(w):
1700     "Return None if target is invalid, otherwise return a course angle."
1701     if not w.valid_sector():
1702         huh()
1703         return None
1704     delta = Coord()
1705     # C code this was translated from is wacky -- why the sign reversal?
1706     delta.j = (w.j - game.sector.j)
1707     delta.i = (game.sector.i - w.i)
1708     if delta == Coord(0, 0):
1709         skip(1)
1710         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1711         prout(_("  I recommend an immediate review of"))
1712         prout(_("  the Captain's psychological profile.\""))
1713         scanner.chew()
1714         return None
1715     return delta.bearing()
1716
1717 def torps():
1718     "Launch photon torpedo salvo."
1719     tcourse = []
1720     game.ididit = False
1721     if damaged(DPHOTON):
1722         prout(_("Photon tubes damaged."))
1723         scanner.chew()
1724         return
1725     if game.torps == 0:
1726         prout(_("No torpedoes left."))
1727         scanner.chew()
1728         return
1729     # First, get torpedo count
1730     while True:
1731         scanner.nexttok()
1732         if scanner.token == "IHALPHA":
1733             huh()
1734             return
1735         elif scanner.token == "IHEOL" or not scanner.waiting():
1736             prout(_("%d torpedoes left.") % game.torps)
1737             scanner.chew()
1738             proutn(_("Number of torpedoes to fire- "))
1739             continue        # Go back around to get a number
1740         else: # key == "IHREAL"
1741             n = scanner.int()
1742             if n <= 0: # abort command
1743                 scanner.chew()
1744                 return
1745             if n > MAXBURST:
1746                 scanner.chew()
1747                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1748                 return
1749             if n > game.torps:
1750                 scanner.chew()        # User requested more torps than available
1751                 continue        # Go back around
1752             break        # All is good, go to next stage
1753     # Next, get targets
1754     target = []
1755     for i in range(n):
1756         key = scanner.nexttok()
1757         if i == 0 and key == "IHEOL":
1758             break        # no coordinate waiting, we will try prompting
1759         if i == 1 and key == "IHEOL":
1760             # direct all torpedoes at one target
1761             while i < n:
1762                 target.append(target[0])
1763                 tcourse.append(tcourse[0])
1764                 i += 1
1765             break
1766         scanner.push(scanner.token)
1767         target.append(scanner.getcoord())
1768         if target[-1] is None:
1769             return
1770         tcourse.append(targetcheck(target[-1]))
1771         if tcourse[-1] is None:
1772             return
1773     scanner.chew()
1774     if len(target) == 0:
1775         # prompt for each one
1776         for i in range(n):
1777             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1778             scanner.chew()
1779             target.append(scanner.getcoord())
1780             if target[-1] is None:
1781                 return
1782             tcourse.append(targetcheck(target[-1]))
1783             if tcourse[-1] is None:
1784                 return
1785     game.ididit = True
1786     # Loop for moving <n> torpedoes
1787     for i in range(n):
1788         if game.condition != "docked":
1789             game.torps -= 1
1790         dispersion = (rnd.real()+rnd.real())*0.5 -0.5
1791         if math.fabs(dispersion) >= 0.47:
1792             # misfire!
1793             dispersion *= rnd.real(1.2, 2.2)
1794             if n > 0:
1795                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1796             else:
1797                 prouts(_("***TORPEDO MISFIRES."))
1798             skip(1)
1799             if i < n:
1800                 prout(_("  Remainder of burst aborted."))
1801             if rnd.withprob(0.2):
1802                 prout(_("***Photon tubes damaged by misfire."))
1803                 game.damage[DPHOTON] = game.damfac * rnd.real(1.0, 3.0)
1804             break
1805         if game.iscloaked:
1806             dispersion *= 1.2
1807         elif game.shldup or game.condition == "docked":
1808             dispersion *= 1.0 + 0.0001*game.shield
1809         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1810         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1811             return
1812     if game.unwon()<=0:
1813         finish(FWON)
1814
1815 def overheat(rpow):
1816     "Check for phasers overheating."
1817     if rpow > 1500:
1818         checkburn = (rpow-1500.0)*0.00038
1819         if rnd.withprob(checkburn):
1820             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1821             game.damage[DPHASER] = game.damfac* rnd.real(1.0, 2.0) * (1.0+checkburn)
1822
1823 def checkshctrl(rpow):
1824     "Check shield control."
1825     skip(1)
1826     if rnd.withprob(0.998):
1827         prout(_("Shields lowered."))
1828         return False
1829     # Something bad has happened
1830     prouts(_("***RED ALERT!  RED ALERT!"))
1831     skip(2)
1832     hit = rpow*game.shield/game.inshld
1833     game.energy -= rpow+hit*0.8
1834     game.shield -= hit*0.2
1835     if game.energy <= 0.0:
1836         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1837         skip(1)
1838         stars()
1839         finish(FPHASER)
1840         return True
1841     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1842     skip(2)
1843     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1844     icas = rnd.integer(int(hit*0.012))
1845     skip(1)
1846     fry(0.8*hit)
1847     if icas:
1848         skip(1)
1849         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1850         prout(_("  %d casualties so far.\"") % icas)
1851         game.casual += icas
1852         game.state.crew -= icas
1853     skip(1)
1854     prout(_("Phaser energy dispersed by shields."))
1855     prout(_("Enemy unaffected."))
1856     overheat(rpow)
1857     return True
1858
1859 def hittem(hits):
1860     "Register a phaser hit on Klingons and Romulans."
1861     w = Coord()
1862     skip(1)
1863     kk = 0
1864     for wham in hits:
1865         if wham == 0:
1866             continue
1867         dustfac = rnd.real(0.9, 1.0)
1868         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1869         kpini = game.enemies[kk].power
1870         kp = math.fabs(kpini)
1871         if PHASEFAC*hit < kp:
1872             kp = PHASEFAC*hit
1873         if game.enemies[kk].power < 0:
1874             game.enemies[kk].power -= -kp
1875         else:
1876             game.enemies[kk].power -= kp
1877         kpow = game.enemies[kk].power
1878         w = game.enemies[kk].location
1879         if hit > 0.005:
1880             if not damaged(DSRSENS):
1881                 boom(w)
1882             proutn(_("%d unit hit on ") % int(hit))
1883         else:
1884             proutn(_("Very small hit on "))
1885         ienm = game.quad[w.i][w.j]
1886         if ienm == '?':
1887             thing.angry()
1888         proutn(crmena(False, ienm, "sector", w))
1889         skip(1)
1890         if kpow == 0:
1891             deadkl(w, ienm, w)
1892             if game.unwon()==0:
1893                 finish(FWON)
1894             if game.alldone:
1895                 return
1896             continue
1897         else: # decide whether or not to emasculate klingon
1898             if kpow > 0 and rnd.withprob(0.9) and kpow <= rnd.real(0.4, 0.8)*kpini:
1899                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1900                 prout(_("   has just lost its firepower.\""))
1901                 game.enemies[kk].power = -kpow
1902         kk += 1
1903     return
1904
1905 def phasers():
1906     "Fire phasers at bad guys."
1907     hits = []
1908     kz = 0
1909     k = 1
1910     irec = 0 # Cheating inhibitor
1911     ifast = False
1912     no = False
1913     itarg = True
1914     msgflag = True
1915     rpow = 0.0
1916     automode = "NOTSET"
1917     key = ""
1918     skip(1)
1919     # SR sensors and Computer are needed for automode
1920     if damaged(DSRSENS) or damaged(DCOMPTR):
1921         itarg = False
1922     if game.condition == "docked":
1923         prout(_("Phasers can't be fired through base shields."))
1924         scanner.chew()
1925         return
1926     if damaged(DPHASER):
1927         prout(_("Phaser control damaged."))
1928         scanner.chew()
1929         return
1930     if game.shldup:
1931         if damaged(DSHCTRL):
1932             prout(_("High speed shield control damaged."))
1933             scanner.chew()
1934             return
1935         if game.energy <= 200.0:
1936             prout(_("Insufficient energy to activate high-speed shield control."))
1937             scanner.chew()
1938             return
1939         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1940         ifast = True
1941     # Original code so convoluted, I re-did it all
1942     # (That was Tom Almy talking about the C code, I think -- ESR)
1943     while automode == "NOTSET":
1944         key = scanner.nexttok()
1945         if key == "IHALPHA":
1946             if scanner.sees("manual"):
1947                 if len(game.enemies)==0:
1948                     prout(_("There is no enemy present to select."))
1949                     scanner.chew()
1950                     key = "IHEOL"
1951                     automode = "AUTOMATIC"
1952                 else:
1953                     automode = "MANUAL"
1954                     key = scanner.nexttok()
1955             elif scanner.sees("automatic"):
1956                 if (not itarg) and len(game.enemies) != 0:
1957                     automode = "FORCEMAN"
1958                 else:
1959                     if len(game.enemies)==0:
1960                         prout(_("Energy will be expended into space."))
1961                     automode = "AUTOMATIC"
1962                     key = scanner.nexttok()
1963             elif scanner.sees("no"):
1964                 no = True
1965             else:
1966                 huh()
1967                 return
1968         elif key == "IHREAL":
1969             if len(game.enemies)==0:
1970                 prout(_("Energy will be expended into space."))
1971                 automode = "AUTOMATIC"
1972             elif not itarg:
1973                 automode = "FORCEMAN"
1974             else:
1975                 automode = "AUTOMATIC"
1976         else:
1977             # "IHEOL"
1978             if len(game.enemies)==0:
1979                 prout(_("Energy will be expended into space."))
1980                 automode = "AUTOMATIC"
1981             elif not itarg:
1982                 automode = "FORCEMAN"
1983             else:
1984                 proutn(_("Manual or automatic? "))
1985                 scanner.chew()
1986     avail = game.energy
1987     if ifast:
1988         avail -= 200.0
1989     if automode == "AUTOMATIC":
1990         if key == "IHALPHA" and scanner.sees("no"):
1991             no = True
1992             key = scanner.nexttok()
1993         if key != "IHREAL" and len(game.enemies) != 0:
1994             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1995         irec = 0
1996         while True:
1997             scanner.chew()
1998             if not kz:
1999                 for i in range(len(game.enemies)):
2000                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*rnd.real(1.01, 1.06) + 1.0
2001             kz = 1
2002             proutn(_("%d units required. ") % irec)
2003             scanner.chew()
2004             proutn(_("Units to fire= "))
2005             key = scanner.nexttok()
2006             if key != "IHREAL":
2007                 return
2008             rpow = scanner.real
2009             if rpow > avail:
2010                 proutn(_("Energy available= %.2f") % avail)
2011                 skip(1)
2012                 key = "IHEOL"
2013             if not rpow > avail:
2014                 break
2015         if rpow <= 0:
2016             # chicken out
2017             scanner.chew()
2018             return
2019         key = scanner.nexttok()
2020         if key == "IHALPHA" and scanner.sees("no"):
2021             no = True
2022         if ifast:
2023             game.energy -= 200 # Go and do it!
2024             if checkshctrl(rpow):
2025                 return
2026         scanner.chew()
2027         game.energy -= rpow
2028         extra = rpow
2029         if len(game.enemies):
2030             extra = 0.0
2031             powrem = rpow
2032             for i in range(len(game.enemies)):
2033                 hits.append(0.0)
2034                 if powrem <= 0:
2035                     continue
2036                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
2037                 over = rnd.real(1.01, 1.06) * hits[i]
2038                 temp = powrem
2039                 powrem -= hits[i] + over
2040                 if powrem <= 0 and temp < hits[i]:
2041                     hits[i] = temp
2042                 if powrem <= 0:
2043                     over = 0.0
2044                 extra += over
2045             if powrem > 0.0:
2046                 extra += powrem
2047             hittem(hits)
2048             game.ididit = True
2049         if extra > 0 and not game.alldone:
2050             if game.tholian:
2051                 proutn(_("*** Tholian web absorbs "))
2052                 if len(game.enemies)>0:
2053                     proutn(_("excess "))
2054                 prout(_("phaser energy."))
2055             else:
2056                 prout(_("%d expended on empty space.") % int(extra))
2057     elif automode == "FORCEMAN":
2058         scanner.chew()
2059         key = "IHEOL"
2060         if damaged(DCOMPTR):
2061             prout(_("Battle computer damaged, manual fire only."))
2062         else:
2063             skip(1)
2064             prouts(_("---WORKING---"))
2065             skip(1)
2066             prout(_("Short-range-sensors-damaged"))
2067             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2068             prout(_("Manual-fire-must-be-used"))
2069             skip(1)
2070     elif automode == "MANUAL":
2071         rpow = 0.0
2072         for k in range(len(game.enemies)):
2073             aim = game.enemies[k].location
2074             ienm = game.quad[aim.i][aim.j]
2075             if msgflag:
2076                 proutn(_("Energy available= %.2f") % (avail-0.006))
2077                 skip(1)
2078                 msgflag = False
2079                 rpow = 0.0
2080             if damaged(DSRSENS) and \
2081                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
2082                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2083                 scanner.chew()
2084                 key = "IHEOL"
2085                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
2086                 k += 1
2087                 continue
2088             if key == "IHEOL":
2089                 scanner.chew()
2090                 if itarg and k > kz:
2091                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        rnd.real(1.01, 1.06) + 1.0
2092                 kz = k
2093                 proutn("(")
2094                 if not damaged(DCOMPTR):
2095                     proutn("%d" % irec)
2096                 else:
2097                     proutn("??")
2098                 proutn(")  ")
2099                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
2100                 key = scanner.nexttok()
2101             if key == "IHALPHA" and scanner.sees("no"):
2102                 no = True
2103                 key = scanner.nexttok()
2104                 continue
2105             if key == "IHALPHA":
2106                 huh()
2107                 return
2108             if key == "IHEOL":
2109                 if k == 1: # Let me say I'm baffled by this
2110                     msgflag = True
2111                 continue
2112             if scanner.real < 0:
2113                 # abort out
2114                 scanner.chew()
2115                 return
2116             hits[k] = scanner.real
2117             rpow += scanner.real
2118             # If total requested is too much, inform and start over
2119             if rpow > avail:
2120                 prout(_("Available energy exceeded -- try again."))
2121                 scanner.chew()
2122                 return
2123             key = scanner.nexttok() # scan for next value
2124             k += 1
2125         if rpow == 0.0:
2126             # zero energy -- abort
2127             scanner.chew()
2128             return
2129         if key == "IHALPHA" and scanner.sees("no"):
2130             no = True
2131         game.energy -= rpow
2132         scanner.chew()
2133         if ifast:
2134             game.energy -= 200.0
2135             if checkshctrl(rpow):
2136                 return
2137         hittem(hits)
2138         game.ididit = True
2139      # Say shield raised or malfunction, if necessary
2140     if game.alldone:
2141         return
2142     if ifast:
2143         skip(1)
2144         if no == 0:
2145             if rnd.withprob(0.01):
2146                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2147                 prouts(_("         CLICK   CLICK   POP  . . ."))
2148                 prout(_(" No response, sir!"))
2149                 game.shldup = False
2150             else:
2151                 prout(_("Shields raised."))
2152         else:
2153             game.shldup = False
2154     overheat(rpow)
2155
2156
2157 def capture():
2158     game.ididit = False # Nothing if we fail
2159     game.optime = 0.0
2160
2161     # Make sure there is room in the brig
2162     if game.brigfree == 0:
2163         prout(_("Security reports the brig is already full."))
2164         return
2165
2166     if damaged(DRADIO):
2167         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2168         return
2169
2170     if damaged(DTRANSP):
2171         prout(_("Scotty- \"Transporter damaged, sir.\""))
2172         return
2173
2174     # find out if there are any at all
2175     if game.klhere < 1:
2176         prout(_("Uhura- \"Getting no response, sir.\""))
2177         return
2178
2179     # if there is more than one Klingon, find out which one
2180     #   Cruddy, just takes one at random.  Should ask the captain.
2181     #   Nah, just select the weakest one since it is most likely to
2182     #   surrender (Tom Almy mod)
2183     klingons = [e for e in game.enemies if e.type == 'K']
2184     weakest = sorted(klingons, key=lambda e: e.power)[0]
2185     game.optime = 0.05          # This action will take some time
2186     game.ididit = True #  So any others can strike back
2187
2188     # check out that Klingon
2189     # The algorithm isn't that great and could use some more
2190     # intelligent design
2191     # x = 300 + 25*skill;
2192     x = game.energy / (weakest.power * len(klingons))
2193     #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
2194     #      % (game.energy, weakest.power, len(klingons)))
2195     x *= 2.5    # would originally have been equivalent of 1.4,
2196                 # but we want command to work more often, more humanely
2197     #prout(_("Prob = %.4f" % x))
2198     #   x = 100; // For testing, of course!
2199     if x < rnd.real(100):
2200         # guess what, he surrendered!!!
2201         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2202         i = rnd.real(200)
2203         if i > 0:
2204             prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
2205         if i > game.brigfree:
2206             prout(_("%d Klingons die because there is no room for them in the brig.") % (i-game.brigfree))
2207             i = game.brigfree
2208         game.brigfree -= i
2209         prout(_("%d captives taken") % i)
2210         deadkl(weakest.location, weakest.type, game.sector)
2211         if game.unwon()<=0:
2212             finish(FWON)
2213         return
2214
2215         # big surprise, he refuses to surrender
2216     prout(_("Fat chance, captain!"))
2217
2218 # Code from events.c begins here.
2219
2220 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2221 # event of each type active at any given time.  Mostly these means we can
2222 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2223 # BSD Trek, from which we swiped the idea, can have up to 5.
2224
2225 def unschedule(evtype):
2226     "Remove an event from the schedule."
2227     game.future[evtype].date = FOREVER
2228     return game.future[evtype]
2229
2230 def is_scheduled(evtype):
2231     "Is an event of specified type scheduled."
2232     return game.future[evtype].date != FOREVER
2233
2234 def scheduled(evtype):
2235     "When will this event happen?"
2236     return game.future[evtype].date
2237
2238 def schedule(evtype, offset):
2239     "Schedule an event of specified type."
2240     game.future[evtype].date = game.state.date + offset
2241     return game.future[evtype]
2242
2243 def postpone(evtype, offset):
2244     "Postpone a scheduled event."
2245     game.future[evtype].date += offset
2246
2247 def cancelrest():
2248     "Rest period is interrupted by event."
2249     if game.resting:
2250         skip(1)
2251         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2252         if ja():
2253             game.resting = False
2254             game.optime = 0.0
2255             return True
2256     return False
2257
2258 def events():
2259     "Run through the event queue looking for things to do."
2260     i = 0
2261     fintim = game.state.date + game.optime
2262     yank = 0
2263     ictbeam = False
2264     istract = False
2265     w = Coord()
2266     hold = Coord()
2267     ev = Event()
2268     ev2 = Event()
2269
2270     def tractorbeam(yank):
2271         "Tractor-beaming cases merge here."
2272         announce()
2273         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2274         skip(1)
2275         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2276         # If Kirk & Co. screwing around on planet, handle
2277         atover(True) # atover(true) is Grab
2278         if game.alldone:
2279             return
2280         if game.icraft: # Caught in Galileo?
2281             finish(FSTRACTOR)
2282             return
2283         # Check to see if shuttle is aboard
2284         if game.iscraft == "offship":
2285             skip(1)
2286             if rnd.withprob(0.5):
2287                 prout(_("Galileo, left on the planet surface, is captured"))
2288                 prout(_("by aliens and made into a flying McDonald's."))
2289                 game.damage[DSHUTTL] = -10
2290                 game.iscraft = "removed"
2291             else:
2292                 prout(_("Galileo, left on the planet surface, is well hidden."))
2293         if evcode == FSPY:
2294             game.quadrant = game.state.kscmdr
2295         else:
2296             game.quadrant = game.state.kcmdr[i]
2297         game.sector = randplace(QUADSIZE)
2298         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2299                % (game.quadrant, game.sector))
2300         if game.resting:
2301             prout(_("(Remainder of rest/repair period cancelled.)"))
2302             game.resting = False
2303         if not game.shldup:
2304             if not damaged(DSHIELD) and game.shield > 0:
2305                 doshield(shraise=True) # raise shields
2306                 game.shldchg = False
2307             else:
2308                 prout(_("(Shields not currently useable.)"))
2309         newqad()
2310         # Adjust finish time to time of tractor beaming?
2311         # fintim = game.state.date+game.optime
2312         attack(torps_ok=False)
2313         if not game.state.kcmdr:
2314             unschedule(FTBEAM)
2315         else:
2316             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2317
2318     def destroybase():
2319         "Code merges here for any commander destroying a starbase."
2320         # Not perfect, but will have to do
2321         # Handle case where base is in same quadrant as starship
2322         if game.battle == game.quadrant:
2323             game.state.chart[game.battle.i][game.battle.j].starbase = False
2324             game.quad[game.base.i][game.base.j] = '.'
2325             game.base.invalidate()
2326             newcnd()
2327             skip(1)
2328             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2329         elif game.state.baseq and communicating():
2330             # Get word via subspace radio
2331             announce()
2332             skip(1)
2333             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2334             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2335             if game.isatb == 2:
2336                 prout(_("the Klingon Super-Commander"))
2337             else:
2338                 prout(_("a Klingon Commander"))
2339             game.state.chart[game.battle.i][game.battle.j].starbase = False
2340         # Remove Starbase from galaxy
2341         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2342         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2343         if game.isatb == 2:
2344             # reinstate a commander's base attack
2345             game.battle = hold
2346             game.isatb = 0
2347         else:
2348             game.battle.invalidate()
2349     if game.idebug:
2350         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2351         for i in range(1, NEVENTS):
2352             if   i == FSNOVA:  proutn("=== Supernova       ")
2353             elif i == FTBEAM:  proutn("=== T Beam          ")
2354             elif i == FSNAP:   proutn("=== Snapshot        ")
2355             elif i == FBATTAK: proutn("=== Base Attack     ")
2356             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2357             elif i == FSCMOVE: proutn("=== SC Move         ")
2358             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2359             elif i == FDSPROB: proutn("=== Probe Move      ")
2360             elif i == FDISTR:  proutn("=== Distress Call   ")
2361             elif i == FENSLV:  proutn("=== Enslavement     ")
2362             elif i == FREPRO:  proutn("=== Klingon Build   ")
2363             if is_scheduled(i):
2364                 prout("%.2f" % (scheduled(i)))
2365             else:
2366                 prout("never")
2367     radio_was_broken = damaged(DRADIO)
2368     hold.i = hold.j = 0
2369     while True:
2370         # Select earliest extraneous event, evcode==0 if no events
2371         evcode = FSPY
2372         if game.alldone:
2373             return
2374         datemin = fintim
2375         for l in range(1, NEVENTS):
2376             if game.future[l].date < datemin:
2377                 evcode = l
2378                 if game.idebug:
2379                     prout("== Event %d fires" % evcode)
2380                 datemin = game.future[l].date
2381         xtime = datemin-game.state.date
2382         if game.iscloaked:
2383             game.energy -= xtime*500.0
2384             if game.energy <= 0:
2385                 finish(FNRG)
2386                 return
2387         game.state.date = datemin
2388         # Decrement Federation resources and recompute remaining time
2389         game.state.remres -= (game.remkl()+4*len(game.state.kcmdr))*xtime
2390         game.recompute()
2391         if game.state.remtime <= 0:
2392             finish(FDEPLETE)
2393             return
2394         # Any crew left alive?
2395         if game.state.crew <= 0:
2396             finish(FCREW)
2397             return
2398         # Is life support adequate?
2399         if damaged(DLIFSUP) and game.condition != "docked":
2400             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2401                 finish(FLIFESUP)
2402                 return
2403             game.lsupres -= xtime
2404             if game.damage[DLIFSUP] <= xtime:
2405                 game.lsupres = game.inlsr
2406         # Fix devices
2407         repair = xtime
2408         if game.condition == "docked":
2409             repair /= DOCKFAC
2410         # Don't fix Deathray here
2411         for l in range(NDEVICES):
2412             if game.damage[l] > 0.0 and l != DDRAY:
2413                 if game.damage[l]-repair > 0.0:
2414                     game.damage[l] -= repair
2415                 else:
2416                     game.damage[l] = 0.0
2417         # If radio repaired, update star chart and attack reports
2418         if radio_was_broken and not damaged(DRADIO):
2419             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2420             prout(_("   surveillance reports are coming in."))
2421             skip(1)
2422             if not game.iseenit:
2423                 attackreport(False)
2424                 game.iseenit = True
2425             rechart()
2426             prout(_("   The star chart is now up to date.\""))
2427             skip(1)
2428         # Cause extraneous event EVCODE to occur
2429         game.optime -= xtime
2430         if evcode == FSNOVA: # Supernova
2431             announce()
2432             supernova(None)
2433             schedule(FSNOVA, expran(0.5*game.intime))
2434             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2435                 return
2436         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2437             if game.state.nscrem == 0 or game.iscloaked or \
2438                 ictbeam or istract or \
2439                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2440                 return
2441             if game.ientesc or \
2442                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2443                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2444                 (damaged(DSHIELD) and \
2445                  (game.energy < 2500 or damaged(DPHASER)) and \
2446                  (game.torps < 5 or damaged(DPHOTON))):
2447                 # Tractor-beam her!
2448                 istract = ictbeam = True
2449                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2450             else:
2451                 return
2452         elif evcode == FTBEAM: # Tractor beam
2453             if not game.state.kcmdr:
2454                 unschedule(FTBEAM)
2455                 continue
2456             i = rnd.integer(len(game.state.kcmdr))
2457             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2458             if istract or game.condition == "docked" or game.iscloaked or yank == 0:
2459                 # Drats! Have to reschedule
2460                 schedule(FTBEAM,
2461                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2462                 continue
2463             ictbeam = True
2464             tractorbeam(yank)
2465         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2466             game.snapsht = copy.deepcopy(game.state)
2467             game.state.snap = True
2468             schedule(FSNAP, expran(0.5 * game.intime))
2469         elif evcode == FBATTAK: # Commander attacks starbase
2470             if not game.state.kcmdr or not game.state.baseq:
2471                 # no can do
2472                 unschedule(FBATTAK)
2473                 unschedule(FCDBAS)
2474                 continue
2475             ibq = None  # Force battle location to persist past loop
2476             try:
2477                 for ibq in game.state.baseq:
2478                     for cmdr in game.state.kcmdr:
2479                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2480                             raise JumpOut
2481                 # no match found -- try later
2482                 schedule(FBATTAK, expran(0.3*game.intime))
2483                 unschedule(FCDBAS)
2484                 continue
2485             except JumpOut:
2486                 pass
2487             # commander + starbase combination found -- launch attack
2488             game.battle = ibq
2489             schedule(FCDBAS, rnd.real(1.0, 4.0))
2490             if game.isatb: # extra time if SC already attacking
2491                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2492             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2493             game.iseenit = False
2494             if not communicating():
2495                 continue # No warning :-(
2496             game.iseenit = True
2497             announce()
2498             skip(1)
2499             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2500             prout(_("   reports that it is under attack and that it can"))
2501             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2502             if cancelrest():
2503                 return
2504         elif evcode == FSCDBAS: # Supercommander destroys base
2505             unschedule(FSCDBAS)
2506             game.isatb = 2
2507             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2508                 continue # WAS RETURN!
2509             hold = game.battle
2510             game.battle = game.state.kscmdr
2511             destroybase()
2512         elif evcode == FCDBAS: # Commander succeeds in destroying base
2513             if evcode == FCDBAS:
2514                 unschedule(FCDBAS)
2515                 if not game.state.baseq() \
2516                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2517                     game.battle.invalidate()
2518                     continue
2519                 # find the lucky pair
2520                 for cmdr in game.state.kcmdr:
2521                     if cmdr == game.battle:
2522                         break
2523                 else:
2524                     # No action to take after all
2525                     continue
2526             destroybase()
2527         elif evcode == FSCMOVE: # Supercommander moves
2528             schedule(FSCMOVE, 0.2777)
2529             if not game.ientesc and not istract and game.isatb != 1 and \
2530                    (not game.iscate or not game.justin):
2531                 supercommander()
2532         elif evcode == FDSPROB: # Move deep space probe
2533             schedule(FDSPROB, 0.01)
2534             if not game.probe.nexttok():
2535                 if not game.probe.quadrant().valid_quadrant() or \
2536                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2537                     # Left galaxy or ran into supernova
2538                     if communicating():
2539                         announce()
2540                         skip(1)
2541                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2542                         if not game.probe.quadrant().valid_quadrant():
2543                             prout(_("has left the galaxy.\""))
2544                         else:
2545                             prout(_("is no longer transmitting.\""))
2546                     unschedule(FDSPROB)
2547                     continue
2548                 if communicating():
2549                     #announce()
2550                     skip(1)
2551                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2552             pquad = game.probe.quadrant()
2553             pdest = game.state.galaxy[pquad.i][pquad.j]
2554             if communicating():
2555                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2556                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2557                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2558                 pdest.charted = True
2559             game.probe.moves -= 1 # One less to travel
2560             if game.probe.arrived() and game.isarmed and pdest.stars:
2561                 supernova(game.probe)                # fire in the hole!
2562                 unschedule(FDSPROB)
2563                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2564                     return
2565         elif evcode == FDISTR: # inhabited system issues distress call
2566             unschedule(FDISTR)
2567             # try a whole bunch of times to find something suitable
2568             for i in range(100):
2569                 # need a quadrant which is not the current one,
2570                 # which has some stars which are inhabited and
2571                 # not already under attack, which is not
2572                 # supernova'ed, and which has some Klingons in it
2573                 w = randplace(GALSIZE)
2574                 q = game.state.galaxy[w.i][w.j]
2575                 if not (game.quadrant == w or q.planet is None or \
2576                       not q.planet.inhabited or \
2577                       q.supernova or q.status!="secure" or q.klingons<=0):
2578                     break
2579             else:
2580                 # can't seem to find one; ignore this call
2581                 if game.idebug:
2582                     prout("=== Couldn't find location for distress event.")
2583                 continue
2584             # got one!!  Schedule its enslavement
2585             ev = schedule(FENSLV, expran(game.intime))
2586             ev.quadrant = w
2587             q.status = "distressed"
2588             # tell the captain about it if we can
2589             if communicating():
2590                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2591                         % (q.planet, repr(w)))
2592                 prout(_("by a Klingon invasion fleet."))
2593                 if cancelrest():
2594                     return
2595         elif evcode == FENSLV:                # starsystem is enslaved
2596             ev = unschedule(FENSLV)
2597             # see if current distress call still active
2598             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2599             if q.klingons <= 0:
2600                 q.status = "secure"
2601                 continue
2602             q.status = "enslaved"
2603
2604             # play stork and schedule the first baby
2605             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2606             ev2.quadrant = ev.quadrant
2607
2608             # report the disaster if we can
2609             if communicating():
2610                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2611                         q.planet)
2612                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2613         elif evcode == FREPRO:                # Klingon reproduces
2614             # If we ever switch to a real event queue, we'll need to
2615             # explicitly retrieve and restore the x and y.
2616             ev = schedule(FREPRO, expran(1.0 * game.intime))
2617             # see if current distress call still active
2618             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2619             if q.klingons <= 0:
2620                 q.status = "secure"
2621                 continue
2622             if game.remkl() >= MAXKLGAME:
2623                 continue                # full right now
2624             # reproduce one Klingon
2625             w = ev.quadrant
2626             m = Coord()
2627             if game.klhere >= MAXKLQUAD:
2628                 try:
2629                     # this quadrant not ok, pick an adjacent one
2630                     for m.i in range(w.i - 1, w.i + 2):
2631                         for m.j in range(w.j - 1, w.j + 2):
2632                             if not m.valid_quadrant():
2633                                 continue
2634                             q = game.state.galaxy[m.i][m.j]
2635                             # check for this quad ok (not full & no snova)
2636                             if q.klingons >= MAXKLQUAD or q.supernova:
2637                                 continue
2638                             raise JumpOut
2639                     # search for eligible quadrant failed
2640                     continue
2641                 except JumpOut:
2642                     w = m
2643             # deliver the child
2644             q.klingons += 1
2645             if game.quadrant == w:
2646                 game.klhere += 1
2647                 newkling() # also adds it to game.enemies
2648             # recompute time left
2649             game.recompute()
2650             if communicating():
2651                 if game.quadrant == w:
2652                     prout(_("Spock- sensors indicate the Klingons have"))
2653                     prout(_("launched a warship from %s.") % q.planet)
2654                 else:
2655                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2656                     if q.planet is not None:
2657                         proutn(_("near %s ") % q.planet)
2658                     prout(_("in Quadrant %s.") % w)
2659
2660 def wait():
2661     "Wait on events."
2662     game.ididit = False
2663     while True:
2664         key = scanner.nexttok()
2665         if key  != "IHEOL":
2666             break
2667         proutn(_("How long? "))
2668         scanner.chew()
2669     if key != "IHREAL":
2670         huh()
2671         return
2672     origTime = delay = scanner.real
2673     if delay <= 0.0:
2674         return
2675     if delay >= game.state.remtime or len(game.enemies) != 0:
2676         proutn(_("Are you sure? "))
2677         if not ja():
2678             return
2679     # Alternate resting periods (events) with attacks
2680     game.resting = True
2681     while True:
2682         if delay <= 0:
2683             game.resting = False
2684         if not game.resting:
2685             prout(_("%d stardates left.") % int(game.state.remtime))
2686             return
2687         temp = game.optime = delay
2688         if len(game.enemies):
2689             rtime = rnd.real(1.0, 2.0)
2690             if rtime < temp:
2691                 temp = rtime
2692             game.optime = temp
2693         if game.optime < delay:
2694             attack(torps_ok=False)
2695         if game.alldone:
2696             return
2697         events()
2698         game.ididit = True
2699         if game.alldone:
2700             return
2701         delay -= temp
2702         # Repair Deathray if long rest at starbase
2703         if origTime-delay >= 9.99 and game.condition == "docked":
2704             game.damage[DDRAY] = 0.0
2705         # leave if quadrant supernovas
2706         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2707             break
2708     game.resting = False
2709     game.optime = 0.0
2710
2711 def nova(nov):
2712     "Star goes nova."
2713     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2714     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2715     if rnd.withprob(0.05):
2716         # Wow! We've supernova'ed
2717         supernova(game.quadrant)
2718         return
2719     # handle initial nova
2720     game.quad[nov.i][nov.j] = '.'
2721     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2722     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2723     game.state.starkl += 1
2724     # Set up queue to recursively trigger adjacent stars
2725     hits = [nov]
2726     kount = 0
2727     while hits:
2728         offset = Coord()
2729         start = hits.pop()
2730         for offset.i in range(-1, 1+1):
2731             for offset.j in range(-1, 1+1):
2732                 if offset.j == 0 and offset.i == 0:
2733                     continue
2734                 neighbor = start + offset
2735                 if not neighbor.valid_sector():
2736                     continue
2737                 iquad = game.quad[neighbor.i][neighbor.j]
2738                 # Empty space ends reaction
2739                 if iquad in ('.', '?', ' ', 'T', '#'):
2740                     pass
2741                 elif iquad == '*': # Affect another star
2742                     if rnd.withprob(0.05):
2743                         # This star supernovas
2744                         supernova(game.quadrant)
2745                         return
2746                     else:
2747                         hits.append(neighbor)
2748                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2749                         game.state.starkl += 1
2750                         proutn(crmena(True, '*', "sector", neighbor))
2751                         prout(_(" novas."))
2752                         game.quad[neighbor.i][neighbor.j] = '.'
2753                         kount += 1
2754                 elif iquad in ('P', '@'): # Destroy planet
2755                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2756                     if iquad == 'P':
2757                         game.state.nplankl += 1
2758                     else:
2759                         game.state.nworldkl += 1
2760                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2761                     game.iplnet.pclass = "destroyed"
2762                     game.iplnet = None
2763                     game.plnet.invalidate()
2764                     if game.landed:
2765                         finish(FPNOVA)
2766                         return
2767                     game.quad[neighbor.i][neighbor.j] = '.'
2768                 elif iquad == 'B': # Destroy base
2769                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2770                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2771                     game.base.invalidate()
2772                     game.state.basekl += 1
2773                     newcnd()
2774                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2775                     game.quad[neighbor.i][neighbor.j] = '.'
2776                 elif iquad in ('E', 'F'): # Buffet ship
2777                     prout(_("***Starship buffeted by nova."))
2778                     if game.shldup:
2779                         if game.shield >= 2000.0:
2780                             game.shield -= 2000.0
2781                         else:
2782                             diff = 2000.0 - game.shield
2783                             game.energy -= diff
2784                             game.shield = 0.0
2785                             game.shldup = False
2786                             prout(_("***Shields knocked out."))
2787                             game.damage[DSHIELD] += 0.005*game.damfac*rnd.real()*diff
2788                     else:
2789                         game.energy -= 2000.0
2790                     if game.energy <= 0:
2791                         finish(FNOVA)
2792                         return
2793                     # add in course nova contributes to kicking starship
2794                     if hits:
2795                         bump += (game.sector-hits[-1]).sgn()
2796                 elif iquad == 'K': # kill klingon
2797                     deadkl(neighbor, iquad, neighbor)
2798                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2799                     target = None
2800                     for ll in range(len(game.enemies)):
2801                         if game.enemies[ll].location == neighbor:
2802                             target = game.enemies[ll]
2803                             break
2804                     if target is not None:
2805                         target.power -= 800.0 # If firepower is lost, die
2806                         if target.power <= 0.0:
2807                             deadkl(neighbor, iquad, neighbor)
2808                             continue    # neighbor loop
2809                         # Else enemy gets flung by the blast wave
2810                         newc = neighbor + neighbor - start
2811                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2812                         if not newc.valid_sector():
2813                             # can't leave quadrant
2814                             skip(1)
2815                             continue
2816                         iquad1 = game.quad[newc.i][newc.j]
2817                         if iquad1 == ' ':
2818                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2819                             skip(1)
2820                             deadkl(neighbor, iquad, newc)
2821                             continue
2822                         if iquad1 != '.':
2823                             # can't move into something else
2824                             skip(1)
2825                             continue
2826                         proutn(_(", buffeted to Sector %s") % newc)
2827                         game.quad[neighbor.i][neighbor.j] = '.'
2828                         game.quad[newc.i][newc.j] = iquad
2829                         target.move(newc)
2830     # Starship affected by nova -- kick it away.
2831     dist = kount*0.1
2832     direc = ncourse[3*(bump.i+1)+bump.j+2]
2833     if direc == 0.0:
2834         dist = 0.0
2835     if dist == 0.0:
2836         return
2837     scourse = course(bearing=direc, distance=dist)
2838     game.optime = scourse.time(w=4)
2839     skip(1)
2840     prout(_("Force of nova displaces starship."))
2841     imove(scourse, noattack=True)
2842     game.optime = scourse.time(w=4)
2843     return
2844
2845 def supernova(w):
2846     "Star goes supernova."
2847     num = 0; npdead = 0
2848     if w is not None:
2849         nq = copy.copy(w)
2850     else:
2851         # Scheduled supernova -- select star at random.
2852         nstars = 0
2853         nq = Coord()
2854         for nq.i in range(GALSIZE):
2855             for nq.j in range(GALSIZE):
2856                 nstars += game.state.galaxy[nq.i][nq.j].stars
2857         if stars == 0:
2858             return # nothing to supernova exists
2859         num = rnd.integer(nstars) + 1
2860         for nq.i in range(GALSIZE):
2861             for nq.j in range(GALSIZE):
2862                 num -= game.state.galaxy[nq.i][nq.j].stars
2863                 if num <= 0:
2864                     break
2865             if num <=0:
2866                 break
2867         if game.idebug:
2868             proutn("=== Super nova here?")
2869             if ja():
2870                 nq = game.quadrant
2871     if not nq == game.quadrant or game.justin:
2872         # it isn't here, or we just entered (treat as enroute)
2873         if communicating():
2874             skip(1)
2875             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2876             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2877     else:
2878         ns = Coord()
2879         # we are in the quadrant!
2880         num = rnd.integer(game.state.galaxy[nq.i][nq.j].stars) + 1
2881         for ns.i in range(QUADSIZE):
2882             for ns.j in range(QUADSIZE):
2883                 if game.quad[ns.i][ns.j]=='*':
2884                     num -= 1
2885                     if num==0:
2886                         break
2887             if num==0:
2888                 break
2889         skip(1)
2890         prouts(_("***RED ALERT!  RED ALERT!"))
2891         skip(1)
2892         prout(_("***Incipient supernova detected at Sector %s") % ns)
2893         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2894             proutn(_("Emergency override attempts t"))
2895             prouts("***************")
2896             skip(1)
2897             stars()
2898             game.alldone = True
2899     # destroy any Klingons in supernovaed quadrant
2900     game.state.galaxy[nq.i][nq.j].klingons = 0
2901     if nq == game.state.kscmdr:
2902         # did in the Supercommander!
2903         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2904         game.iscate = False
2905         unschedule(FSCMOVE)
2906         unschedule(FSCDBAS)
2907     # Changing this to [w for w in game.state.kcmdr if w != nq]
2908     # causes regression-test failure
2909     survivors = list(filter(lambda w: w != nq, game.state.kcmdr))
2910     #comkills = len(game.state.kcmdr) - len(survivors)
2911     game.state.kcmdr = survivors
2912     if not game.state.kcmdr:
2913         unschedule(FTBEAM)
2914     # destroy Romulans and planets in supernovaed quadrant
2915     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2916     game.state.galaxy[nq.i][nq.j].romulans = 0
2917     game.state.nromrem -= nrmdead
2918     # Destroy planets
2919     for loop in range(game.inplan):
2920         if game.state.planets[loop].quadrant == nq:
2921             game.state.planets[loop].pclass = "destroyed"
2922             npdead += 1
2923     # Destroy any base in supernovaed quadrant
2924     game.state.baseq = [x for x in game.state.baseq if x != nq]
2925     # If starship caused supernova, tally up destruction
2926     if w is not None:
2927         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2928         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2929         game.state.nplankl += npdead
2930     # mark supernova in galaxy and in star chart
2931     if game.quadrant == nq or communicating():
2932         game.state.galaxy[nq.i][nq.j].supernova = True
2933     # If supernova destroys last Klingons give special message
2934     if game.unwon()==0 and not nq == game.quadrant:
2935         skip(2)
2936         if w is None:
2937             prout(_("Lucky you!"))
2938         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2939         finish(FWON)
2940         return
2941     # if some Klingons remain, continue or die in supernova
2942     if game.alldone:
2943         finish(FSNOVAED)
2944     return
2945
2946 # Code from finish.c ends here.
2947
2948 def selfdestruct():
2949     "Self-destruct maneuver. Finish with a BANG!"
2950     scanner.chew()
2951     if damaged(DCOMPTR):
2952         prout(_("Computer damaged; cannot execute destruct sequence."))
2953         return
2954     prouts(_("---WORKING---")); skip(1)
2955     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2956     prouts("   10"); skip(1)
2957     prouts("       9"); skip(1)
2958     prouts("          8"); skip(1)
2959     prouts("             7"); skip(1)
2960     prouts("                6"); skip(1)
2961     skip(1)
2962     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2963     skip(1)
2964     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2965     skip(1)
2966     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2967     skip(1)
2968     scanner.nexttok()
2969     if game.passwd != scanner.token:
2970         prouts(_("PASSWORD-REJECTED;"))
2971         skip(1)
2972         prouts(_("CONTINUITY-EFFECTED"))
2973         skip(2)
2974         return
2975     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2976     prouts("                   5"); skip(1)
2977     prouts("                      4"); skip(1)
2978     prouts("                         3"); skip(1)
2979     prouts("                            2"); skip(1)
2980     prouts("                              1"); skip(1)
2981     if rnd.withprob(0.15):
2982         prouts(_("GOODBYE-CRUEL-WORLD"))
2983         skip(1)
2984     kaboom()
2985
2986 def kaboom():
2987     stars()
2988     if game.ship=='E':
2989         prouts("***")
2990     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2991     skip(1)
2992     stars()
2993     skip(1)
2994     if len(game.enemies) != 0:
2995         whammo = 25.0 * game.energy
2996         for e in game.enemies[::-1]:
2997             if e.power*e.kdist <= whammo:
2998                 deadkl(e.location, game.quad[e.location.i][e.location.j], e.location)
2999     finish(FDILITHIUM)
3000
3001 def killrate():
3002     "Compute our rate of kils over time."
3003     elapsed = game.state.date - game.indate
3004     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
3005         return 0
3006     else:
3007         starting = (game.inkling + game.incom + game.inscom)
3008         remaining = game.unwon()
3009         return (starting - remaining)/elapsed
3010
3011 def badpoints():
3012     "Compute demerits."
3013     badpt = 5.0*game.state.starkl + \
3014             game.casual + \
3015             10.0*game.state.nplankl + \
3016             300*game.state.nworldkl + \
3017             45.0*game.nhelp +\
3018             100.0*game.state.basekl +\
3019             3.0*game.abandoned +\
3020             100*game.ncviol
3021     if game.ship == 'F':
3022         badpt += 100.0
3023     elif game.ship is None:
3024         badpt += 200.0
3025     return badpt
3026
3027 def finish(ifin):
3028     # end the game, with appropriate notifications
3029     igotit = False
3030     game.alldone = True
3031     skip(3)
3032     prout(_("It is stardate %.1f.") % game.state.date)
3033     skip(1)
3034     if ifin == FWON: # Game has been won
3035         if game.state.nromrem != 0:
3036             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
3037                   game.state.nromrem)
3038
3039         prout(_("You have smashed the Klingon invasion fleet and saved"))
3040         prout(_("the Federation."))
3041         if game.alive and game.brigcapacity-game.brigfree > 0:
3042             game.kcaptured += game.brigcapacity-game.brigfree
3043             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
3044         game.gamewon = True
3045         if game.alive:
3046             badpt = badpoints()
3047             if badpt < 100.0:
3048                 badpt = 0.0        # Close enough!
3049             # killsPerDate >= RateMax
3050             if game.state.date-game.indate < 5.0 or \
3051                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
3052                 skip(1)
3053                 prout(_("In fact, you have done so well that Starfleet Command"))
3054                 if game.skill == SKILL_NOVICE:
3055                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
3056                 elif game.skill == SKILL_FAIR:
3057                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
3058                 elif game.skill == SKILL_GOOD:
3059                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
3060                 elif game.skill == SKILL_EXPERT:
3061                     prout(_("promotes you to Commodore Emeritus."))
3062                     skip(1)
3063                     prout(_("Now that you think you're really good, try playing"))
3064                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3065                 elif game.skill == SKILL_EMERITUS:
3066                     skip(1)
3067                     proutn(_("Computer-  "))
3068                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3069                     skip(2)
3070                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
3071                     skip(1)
3072                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3073                     skip(1)
3074                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3075                     skip(1)
3076                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3077                     skip(1)
3078                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
3079                     skip(2)
3080                     prout(_("Now you can retire and write your own Star Trek game!"))
3081                     skip(1)
3082                 elif game.skill >= SKILL_EXPERT:
3083                     if game.thawed and not game.idebug:
3084                         prout(_("You cannot get a citation, so..."))
3085                     else:
3086                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3087                         scanner.chew()
3088                         if ja():
3089                             igotit = True
3090             # Only grant long life if alive (original didn't!)
3091             skip(1)
3092             prout(_("LIVE LONG AND PROSPER."))
3093         score()
3094         if igotit:
3095             plaque()
3096         return
3097     elif ifin == FDEPLETE: # Federation Resources Depleted
3098         prout(_("Your time has run out and the Federation has been"))
3099         prout(_("conquered.  Your starship is now Klingon property,"))
3100         prout(_("and you are put on trial as a war criminal.  On the"))
3101         proutn(_("basis of your record, you are "))
3102         if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
3103             prout(_("acquitted."))
3104             skip(1)
3105             prout(_("LIVE LONG AND PROSPER."))
3106         else:
3107             prout(_("found guilty and"))
3108             prout(_("sentenced to death by slow torture."))
3109             game.alive = False
3110         score()
3111         return
3112     elif ifin == FLIFESUP:
3113         prout(_("Your life support reserves have run out, and"))
3114         prout(_("you die of thirst, starvation, and asphyxiation."))
3115         prout(_("Your starship is a derelict in space."))
3116     elif ifin == FNRG:
3117         prout(_("Your energy supply is exhausted."))
3118         skip(1)
3119         prout(_("Your starship is a derelict in space."))
3120     elif ifin == FBATTLE:
3121         prout(_("The %s has been destroyed in battle.") % crmshp())
3122         skip(1)
3123         prout(_("Dulce et decorum est pro patria mori."))
3124     elif ifin == FNEG3:
3125         prout(_("You have made three attempts to cross the negative energy"))
3126         prout(_("barrier which surrounds the galaxy."))
3127         skip(1)
3128         prout(_("Your navigation is abominable."))
3129         score()
3130     elif ifin == FNOVA:
3131         prout(_("Your starship has been destroyed by a nova."))
3132         prout(_("That was a great shot."))
3133         skip(1)
3134     elif ifin == FSNOVAED:
3135         prout(_("The %s has been fried by a supernova.") % crmshp())
3136         prout(_("...Not even cinders remain..."))
3137     elif ifin == FABANDN:
3138         prout(_("You have been captured by the Klingons. If you still"))
3139         prout(_("had a starbase to be returned to, you would have been"))
3140         prout(_("repatriated and given another chance. Since you have"))
3141         prout(_("no starbases, you will be mercilessly tortured to death."))
3142     elif ifin == FDILITHIUM:
3143         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3144     elif ifin == FMATERIALIZE:
3145         prout(_("Starbase was unable to re-materialize your starship."))
3146         prout(_("Sic transit gloria mundi"))
3147     elif ifin == FPHASER:
3148         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3149     elif ifin == FLOST:
3150         prout(_("You and your landing party have been"))
3151         prout(_("converted to energy, dissipating through space."))
3152     elif ifin == FMINING:
3153         prout(_("You are left with your landing party on"))
3154         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3155         skip(1)
3156         prout(_("They are very fond of \"Captain Kirk\" soup."))
3157         skip(1)
3158         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3159     elif ifin == FDPLANET:
3160         prout(_("You and your mining party perish."))
3161         skip(1)
3162         prout(_("That was a great shot."))
3163         skip(1)
3164     elif ifin == FSSC:
3165         prout(_("The Galileo is instantly annihilated by the supernova."))
3166         prout(_("You and your mining party are atomized."))
3167         skip(1)
3168         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3169         prout(_("joins the Romulans, wreaking terror on the Federation."))
3170     elif ifin == FPNOVA:
3171         prout(_("You and your mining party are atomized."))
3172         skip(1)
3173         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3174         prout(_("joins the Romulans, wreaking terror on the Federation."))
3175     elif ifin == FSTRACTOR:
3176         prout(_("The shuttle craft Galileo is also caught,"))
3177         prout(_("and breaks up under the strain."))
3178         skip(1)
3179         prout(_("Your debris is scattered for millions of miles."))
3180         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3181     elif ifin == FDRAY:
3182         prout(_("The mutants attack and kill Spock."))
3183         prout(_("Your ship is captured by Klingons, and"))
3184         prout(_("your crew is put on display in a Klingon zoo."))
3185     elif ifin == FTRIBBLE:
3186         prout(_("Tribbles consume all remaining water,"))
3187         prout(_("food, and oxygen on your ship."))
3188         skip(1)
3189         prout(_("You die of thirst, starvation, and asphyxiation."))
3190         prout(_("Your starship is a derelict in space."))
3191     elif ifin == FHOLE:
3192         prout(_("Your ship is drawn to the center of the black hole."))
3193         prout(_("You are crushed into extremely dense matter."))
3194     elif ifin == FCLOAK:
3195         game.ncviol += 1
3196         prout(_("You have violated the Treaty of Algeron."))
3197         prout(_("The Romulan Empire can never trust you again."))
3198     elif ifin == FCREW:
3199         prout(_("Your last crew member has died."))
3200     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3201         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3202         prout(_("You may have missed some warning messages."))
3203         skip(1)
3204     if game.ship == 'F':
3205         game.ship = None
3206     elif game.ship == 'E':
3207         game.ship = 'F'
3208     game.alive = False
3209     if game.unwon() != 0:
3210         goodies = game.state.remres/game.inresor
3211         baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3212         if goodies/baddies >= rnd.real(1.0, 1.5):
3213             prout(_("As a result of your actions, a treaty with the Klingon"))
3214             prout(_("Empire has been signed. The terms of the treaty are"))
3215             if goodies/baddies >= rnd.real(3.0):
3216                 prout(_("favorable to the Federation."))
3217                 skip(1)
3218                 prout(_("Congratulations!"))
3219             else:
3220                 prout(_("highly unfavorable to the Federation."))
3221         else:
3222             prout(_("The Federation will be destroyed."))
3223     else:
3224         prout(_("Since you took the last Klingon with you, you are a"))
3225         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3226         prout(_("statue in your memory. Rest in peace, and try not"))
3227         prout(_("to think about pigeons."))
3228         game.gamewon = True
3229     score()
3230     scanner.chew()      # Clean up leftovers
3231
3232 def score():
3233     "Compute player's score."
3234     timused = game.state.date - game.indate
3235     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3236         timused = 5.0
3237     game.perdate = killrate()
3238     ithperd = 500*game.perdate + 0.5
3239     iwon = 0
3240     if game.gamewon:
3241         iwon = 100*game.skill
3242     if game.ship == 'E':
3243         klship = 0
3244     elif game.ship == 'F':
3245         klship = 1
3246     else:
3247         klship = 2
3248     dead_ordinaries= game.inkling - game.remkl() + len(game.state.kcmdr) + game.state.nscrem
3249     game.score = 10*(dead_ordinaries)\
3250              + 50*(game.incom - len(game.state.kcmdr)) \
3251              + ithperd + iwon \
3252              + 20*(game.inrom - game.state.nromrem) \
3253              + 200*(game.inscom - game.state.nscrem) \
3254                  - game.state.nromrem \
3255              + 3 * game.kcaptured \
3256              - badpoints()
3257     if not game.alive:
3258         game.score -= 200
3259     skip(2)
3260     prout(_("Your score --"))
3261     if game.inrom - game.state.nromrem:
3262         prout(_("%6d Romulans destroyed                 %5d") %
3263               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3264     if game.state.nromrem and game.gamewon:
3265         prout(_("%6d Romulans captured                  %5d") %
3266               (game.state.nromrem, game.state.nromrem))
3267     if dead_ordinaries:
3268         prout(_("%6d ordinary Klingons destroyed        %5d") %
3269               (dead_ordinaries, 10*dead_ordinaries))
3270     if game.incom - len(game.state.kcmdr):
3271         prout(_("%6d Klingon commanders destroyed       %5d") %
3272               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3273     if game.kcaptured:
3274         prout(_("%d Klingons captured                   %5d") %
3275               (game.kcaptured, 3 * game.kcaptured))
3276     if game.inscom - game.state.nscrem:
3277         prout(_("%6d Super-Commander destroyed          %5d") %
3278               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3279     if ithperd:
3280         prout(_("%6.2f Klingons per stardate              %5d") %
3281               (game.perdate, ithperd))
3282     if game.state.starkl:
3283         prout(_("%6d stars destroyed by your action     %5d") %
3284               (game.state.starkl, -5*game.state.starkl))
3285     if game.state.nplankl:
3286         prout(_("%6d planets destroyed by your action   %5d") %
3287               (game.state.nplankl, -10*game.state.nplankl))
3288     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3289         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3290               (game.state.nworldkl, -300*game.state.nworldkl))
3291     if game.state.basekl:
3292         prout(_("%6d bases destroyed by your action     %5d") %
3293               (game.state.basekl, -100*game.state.basekl))
3294     if game.nhelp:
3295         prout(_("%6d calls for help from starbase       %5d") %
3296               (game.nhelp, -45*game.nhelp))
3297     if game.casual:
3298         prout(_("%6d casualties incurred                %5d") %
3299               (game.casual, -game.casual))
3300     if game.abandoned:
3301         prout(_("%6d crew abandoned in space            %5d") %
3302               (game.abandoned, -3*game.abandoned))
3303     if klship:
3304         prout(_("%6d ship(s) lost or destroyed          %5d") %
3305               (klship, -100*klship))
3306     if game.ncviol > 0:
3307         if game.ncviol == 1:
3308             prout(_("1 Treaty of Algeron violation          -100"))
3309         else:
3310             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3311                   (game.ncviol, -100*game.ncviol))
3312     if not game.alive:
3313         prout(_("Penalty for getting yourself killed        -200"))
3314     if game.gamewon:
3315         proutn(_("Bonus for winning "))
3316         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3317         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3318         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3319         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3320         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3321         prout("           %5d" % iwon)
3322     skip(1)
3323     prout(_("TOTAL SCORE                               %5d") % game.score)
3324
3325 def plaque():
3326     "Emit winner's commemmorative plaque."
3327     skip(2)
3328     while True:
3329         proutn(_("File or device name for your plaque: "))
3330         winner = cgetline()
3331         try:
3332             fp = open(winner, "w")
3333             break
3334         except IOError:
3335             prout(_("Invalid name."))
3336
3337     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3338     winner = cgetline()
3339     # The 38 below must be 64 for 132-column paper
3340     nskip = 38 - len(winner)/2
3341     # This is where the ASCII art picture was emitted.
3342     # It got garbled somewhere in the chain of transmission to the Almy version.
3343     # We should restore it if we can find old enough FORTRAN sources.
3344     fp.write("\n\n\n")
3345     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3346     fp.write("\n\n\n\n")
3347     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3348     fp.write("\n")
3349     fp.write(_("                                                Starfleet Command bestows to you\n"))
3350     fp.write("\n")
3351     fp.write("%*s%s\n\n" % (nskip, "", winner))
3352     fp.write(_("                                                           the rank of\n\n"))
3353     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3354     fp.write("                                                          ")
3355     if game.skill ==  SKILL_EXPERT:
3356         fp.write(_(" Expert level\n\n"))
3357     elif game.skill == SKILL_EMERITUS:
3358         fp.write(_("Emeritus level\n\n"))
3359     else:
3360         fp.write(_(" Cheat level\n\n"))
3361     timestring = time.ctime()
3362     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3363              (timestring+4, timestring+20, timestring+11))
3364     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3365     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3366     fp.close()
3367
3368 # Code from io.c begins here
3369
3370 rows = linecount = 0        # for paging
3371 stdscr = None
3372 replayfp = None
3373 fullscreen_window = None
3374 srscan_window     = None   # Short range scan
3375 report_window     = None   # Report legends for status window
3376 status_window     = None   # The status window itself
3377 lrscan_window     = None   # Long range scan
3378 message_window    = None   # Main window for scrolling text
3379 prompt_window     = None   # Prompt window at bottom of display
3380 curwnd = None
3381
3382 def iostart():
3383     global stdscr, rows
3384     # for some recent versions of python2, the following enables UTF8
3385     # for the older ones we probably need to set C locale, and python3
3386     # has no problems at all
3387     if sys.version_info[0] < 3:
3388         locale.setlocale(locale.LC_ALL, "")
3389     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3390     gettext.textdomain("sst")
3391     if not (game.options & OPTION_CURSES):
3392         ln_env = os.getenv("LINES")
3393         if ln_env:
3394             rows = ln_env
3395         else:
3396             rows = 25
3397     else:
3398         stdscr = curses.initscr()
3399         stdscr.keypad(True)
3400         curses.nonl()
3401         curses.cbreak()
3402         if game.options & OPTION_COLOR:
3403             curses.start_color()
3404             curses.use_default_colors()
3405             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3406             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3407             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3408             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3409             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3410             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3411             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3412             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3413         global fullscreen_window, srscan_window, report_window, status_window
3414         global lrscan_window, message_window, prompt_window
3415         (rows, _columns)   = stdscr.getmaxyx()
3416         fullscreen_window = stdscr
3417         srscan_window     = curses.newwin(12, 25, 0,       0)
3418         report_window     = curses.newwin(11, 0,  1,       25)
3419         status_window     = curses.newwin(10, 0,  1,       39)
3420         lrscan_window     = curses.newwin(5,  0,  0,       64)
3421         message_window    = curses.newwin(0,  0,  12,      0)
3422         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3423         message_window.scrollok(True)
3424         setwnd(fullscreen_window)
3425
3426 def ioend():
3427     "Wrap up I/O."
3428     if game.options & OPTION_CURSES:
3429         stdscr.keypad(False)
3430         curses.echo()
3431         curses.nocbreak()
3432         curses.endwin()
3433
3434 def waitfor():
3435     "Wait for user action -- OK to do nothing if on a TTY"
3436     if game.options & OPTION_CURSES:
3437         stdscr.getch()
3438
3439 def announce():
3440     skip(1)
3441     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3442     skip(1)
3443
3444 def pause_game():
3445     if game.skill > SKILL_FAIR:
3446         prompt = _("[CONTINUE?]")
3447     else:
3448         prompt = _("[PRESS ENTER TO CONTINUE]")
3449
3450     if game.options & OPTION_CURSES:
3451         drawmaps(0)
3452         setwnd(prompt_window)
3453         prompt_window.clear()
3454         prompt_window.addstr(prompt)
3455         prompt_window.getstr()
3456         prompt_window.clear()
3457         prompt_window.refresh()
3458         setwnd(message_window)
3459     else:
3460         global linecount
3461         sys.stdout.write('\n')
3462         proutn(prompt)
3463         if not replayfp:
3464             my_input()
3465         sys.stdout.write('\n' * rows)
3466         linecount = 0
3467
3468 def skip(i):
3469     "Skip i lines.  Pause game if this would cause a scrolling event."
3470     for _dummy in range(i):
3471         if game.options & OPTION_CURSES:
3472             (y, _x) = curwnd.getyx()
3473             try:
3474                 curwnd.move(y+1, 0)
3475             except curses.error:
3476                 pass
3477         else:
3478             global linecount
3479             linecount += 1
3480             if rows and linecount >= rows:
3481                 pause_game()
3482             else:
3483                 sys.stdout.write('\n')
3484
3485 def proutn(proutntline):
3486     "Utter a line with no following line feed."
3487     if game.options & OPTION_CURSES:
3488         (y, x) = curwnd.getyx()
3489         (my, _mx) = curwnd.getmaxyx()
3490         if curwnd == message_window and y >= my - 2:
3491             pause_game()
3492             clrscr()
3493         if logfp and game.cdebug:
3494             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3495         curwnd.addstr(proutntline)
3496         curwnd.refresh()
3497     else:
3498         sys.stdout.write(proutntline)
3499         sys.stdout.flush()
3500
3501 def prout(proutline):
3502     proutn(proutline)
3503     skip(1)
3504
3505 def prouts(proutsline):
3506     "Emit slowly!"
3507     for c in proutsline:
3508         if not replayfp or replayfp.closed:        # Don't slow down replays
3509             time.sleep(0.03)
3510         proutn(c)
3511         if game.options & OPTION_CURSES:
3512             curwnd.refresh()
3513         else:
3514             sys.stdout.flush()
3515     if not replayfp or replayfp.closed:
3516         time.sleep(0.03)
3517
3518 def cgetline():
3519     "Get a line of input."
3520     if game.options & OPTION_CURSES:
3521         linein = curwnd.getstr() + "\n"
3522         curwnd.refresh()
3523     else:
3524         if replayfp and not replayfp.closed:
3525             while True:
3526                 linein = replayfp.readline()
3527                 proutn(linein)
3528                 if linein == '':
3529                     prout("*** Replay finished")
3530                     replayfp.close()
3531                     break
3532                 elif linein[0] != "#":
3533                     break
3534         else:
3535             try:
3536                 linein = my_input() + "\n"
3537             except EOFError:
3538                 prout("")
3539                 sys.exit(0)
3540     if logfp:
3541         logfp.write(linein)
3542     return linein
3543
3544 def setwnd(wnd):
3545     "Change windows -- OK for this to be a no-op in tty mode."
3546     global curwnd
3547     if game.options & OPTION_CURSES:
3548         if game.cdebug and logfp:
3549             if wnd == fullscreen_window:
3550                 legend = "fullscreen"
3551             elif wnd == srscan_window:
3552                 legend = "srscan"
3553             elif wnd == report_window:
3554                 legend = "report"
3555             elif wnd == status_window:
3556                 legend = "status"
3557             elif wnd == lrscan_window:
3558                 legend = "lrscan"
3559             elif wnd == message_window:
3560                 legend = "message"
3561             elif wnd == prompt_window:
3562                 legend = "prompt"
3563             else:
3564                 legend = "unknown"
3565             logfp.write("#curses: setwnd(%s)\n" % legend)
3566         curwnd = wnd
3567         # Some curses implementations get confused when you try this.
3568         try:
3569             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3570         except curses.error:
3571             pass
3572
3573 def clreol():
3574     "Clear to end of line -- can be a no-op in tty mode"
3575     if game.options & OPTION_CURSES:
3576         curwnd.clrtoeol()
3577         curwnd.refresh()
3578
3579 def clrscr():
3580     "Clear screen -- can be a no-op in tty mode."
3581     global linecount
3582     if game.options & OPTION_CURSES:
3583         curwnd.clear()
3584         curwnd.move(0, 0)
3585         curwnd.refresh()
3586     linecount = 0
3587
3588 def textcolor(color=DEFAULT):
3589     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3590         if color == DEFAULT:
3591             curwnd.attrset(0)
3592         elif color ==  BLACK:
3593             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3594         elif color ==  BLUE:
3595             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3596         elif color ==  GREEN:
3597             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3598         elif color ==  CYAN:
3599             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3600         elif color ==  RED:
3601             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3602         elif color ==  MAGENTA:
3603             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3604         elif color ==  BROWN:
3605             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3606         elif color ==  LIGHTGRAY:
3607             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3608         elif color ==  DARKGRAY:
3609             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3610         elif color ==  LIGHTBLUE:
3611             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3612         elif color ==  LIGHTGREEN:
3613             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3614         elif color ==  LIGHTCYAN:
3615             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3616         elif color ==  LIGHTRED:
3617             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3618         elif color ==  LIGHTMAGENTA:
3619             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3620         elif color ==  YELLOW:
3621             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3622         elif color ==  WHITE:
3623             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3624
3625 def highvideo():
3626     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3627         curwnd.attron(curses.A_REVERSE)
3628
3629 #
3630 # Things past this point have policy implications.
3631 #
3632
3633 def drawmaps(mode):
3634     "Hook to be called after moving to redraw maps."
3635     if game.options & OPTION_CURSES:
3636         if mode == 1:
3637             sensor()
3638         setwnd(srscan_window)
3639         curwnd.move(0, 0)
3640         srscan()
3641         if mode != 2:
3642             setwnd(status_window)
3643             status_window.clear()
3644             status_window.move(0, 0)
3645             setwnd(report_window)
3646             report_window.clear()
3647             report_window.move(0, 0)
3648             status()
3649             setwnd(lrscan_window)
3650             lrscan_window.clear()
3651             lrscan_window.move(0, 0)
3652             lrscan(silent=False)
3653
3654 def put_srscan_sym(w, sym):
3655     "Emit symbol for short-range scan."
3656     srscan_window.move(w.i+1, w.j*2+2)
3657     srscan_window.addch(sym)
3658     srscan_window.refresh()
3659
3660 def boom(w):
3661     "Enemy fall down, go boom."
3662     if game.options & OPTION_CURSES:
3663         drawmaps(0)
3664         setwnd(srscan_window)
3665         srscan_window.attron(curses.A_REVERSE)
3666         put_srscan_sym(w, game.quad[w.i][w.j])
3667         #sound(500)
3668         #time.sleep(1.0)
3669         #nosound()
3670         srscan_window.attroff(curses.A_REVERSE)
3671         put_srscan_sym(w, game.quad[w.i][w.j])
3672         curses.delay_output(500)
3673         setwnd(message_window)
3674
3675 def warble():
3676     "Sound and visual effects for teleportation."
3677     if game.options & OPTION_CURSES:
3678         drawmaps(2)
3679         setwnd(message_window)
3680         #sound(50)
3681     prouts("     . . . . .     ")
3682     if game.options & OPTION_CURSES:
3683         #curses.delay_output(1000)
3684         #nosound()
3685         pass
3686
3687 def tracktorpedo(w, step, i, n, iquad):
3688     "Torpedo-track animation."
3689     if not game.options & OPTION_CURSES:
3690         if step == 1:
3691             if n != 1:
3692                 skip(1)
3693                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3694             else:
3695                 skip(1)
3696                 proutn(_("Torpedo track- "))
3697         elif step==4 or step==9:
3698             skip(1)
3699         proutn("%s   " % w)
3700     else:
3701         if not damaged(DSRSENS) or game.condition=="docked":
3702             if i != 0 and step == 1:
3703                 drawmaps(2)
3704                 time.sleep(0.4)
3705             if (iquad=='.') or (iquad==' '):
3706                 put_srscan_sym(w, '+')
3707                 #sound(step*10)
3708                 #time.sleep(0.1)
3709                 #nosound()
3710                 put_srscan_sym(w, iquad)
3711             else:
3712                 curwnd.attron(curses.A_REVERSE)
3713                 put_srscan_sym(w, iquad)
3714                 #sound(500)
3715                 #time.sleep(1.0)
3716                 #nosound()
3717                 curwnd.attroff(curses.A_REVERSE)
3718                 put_srscan_sym(w, iquad)
3719         else:
3720             proutn("%s   " % w)
3721
3722 def makechart():
3723     "Display the current galaxy chart."
3724     if game.options & OPTION_CURSES:
3725         setwnd(message_window)
3726         message_window.clear()
3727     chart()
3728     if game.options & OPTION_TTY:
3729         skip(1)
3730
3731 NSYM        = 14
3732
3733 def prstat(txt, data):
3734     proutn(txt)
3735     if game.options & OPTION_CURSES:
3736         skip(1)
3737         setwnd(status_window)
3738     else:
3739         proutn(" " * (NSYM - len(txt)))
3740     proutn(data)
3741     skip(1)
3742     if game.options & OPTION_CURSES:
3743         setwnd(report_window)
3744
3745 # Code from moving.c begins here
3746
3747 def imove(icourse=None, noattack=False):
3748     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3749     w = Coord()
3750
3751     def newquadrant(noattack):
3752         # Leaving quadrant -- allow final enemy attack
3753         # Don't set up attack if being pushed by nova or cloaked
3754         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3755             newcnd()
3756             for enemy in game.enemies:
3757                 finald = (w - enemy.location).distance()
3758                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3759             # Stas Sergeev added the condition
3760             # that attacks only happen if Klingons
3761             # are present and your skill is good.
3762             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3763                 attack(torps_ok=False)
3764             if game.alldone:
3765                 return
3766         # check for edge of galaxy
3767         kinks = 0
3768         while True:
3769             kink = False
3770             if icourse.final.i < 0:
3771                 icourse.final.i = -icourse.final.i
3772                 kink = True
3773             if icourse.final.j < 0:
3774                 icourse.final.j = -icourse.final.j
3775                 kink = True
3776             if icourse.final.i >= GALSIZE*QUADSIZE:
3777                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3778                 kink = True
3779             if icourse.final.j >= GALSIZE*QUADSIZE:
3780                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3781                 kink = True
3782             if kink:
3783                 kinks += 1
3784             else:
3785                 break
3786         if kinks:
3787             game.nkinks += 1
3788             if game.nkinks == 3:
3789                 # Three strikes -- you're out!
3790                 finish(FNEG3)
3791                 return
3792             skip(1)
3793             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3794             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3795             prout(_("YOU WILL BE DESTROYED."))
3796         # Compute final position in new quadrant
3797         if trbeam: # Don't bother if we are to be beamed
3798             return
3799         game.quadrant = icourse.final.quadrant()
3800         game.sector = icourse.final.sector()
3801         skip(1)
3802         prout(_("Entering Quadrant %s.") % game.quadrant)
3803         game.quad[game.sector.i][game.sector.j] = game.ship
3804         newqad()
3805         if game.skill>SKILL_NOVICE:
3806             attack(torps_ok=False)
3807
3808     def check_collision(h):
3809         iquad = game.quad[h.i][h.j]
3810         if iquad != '.':
3811             # object encountered in flight path
3812             stopegy = 50.0*icourse.distance/game.optime
3813             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3814                 for enemy in game.enemies:
3815                     if enemy.location == game.sector:
3816                         collision(rammed=False, enemy=enemy)
3817                         return True
3818                 # This should not happen
3819                 prout(_("Which way did he go?"))
3820                 return False
3821             elif iquad == ' ':
3822                 skip(1)
3823                 prouts(_("***RED ALERT!  RED ALERT!"))
3824                 skip(1)
3825                 proutn("***" + crmshp())
3826                 proutn(_(" pulled into black hole at Sector %s") % h)
3827                 # Getting pulled into a black hole was certain
3828                 # death in Almy's original.  Stas Sergeev added a
3829                 # possibility that you'll get timewarped instead.
3830                 n=0
3831                 for m in range(NDEVICES):
3832                     if game.damage[m]>0:
3833                         n += 1
3834                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3835                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3836                     timwrp()
3837                 else:
3838                     finish(FHOLE)
3839                 return True
3840             else:
3841                 # something else
3842                 skip(1)
3843                 proutn(crmshp())
3844                 if iquad == '#':
3845                     prout(_(" encounters Tholian web at %s;") % h)
3846                 else:
3847                     prout(_(" blocked by object at %s;") % h)
3848                 proutn(_("Emergency stop required "))
3849                 prout(_("%2d units of energy.") % int(stopegy))
3850                 game.energy -= stopegy
3851                 if game.energy <= 0:
3852                     finish(FNRG)
3853                 return True
3854         return False
3855
3856     trbeam = False
3857     if game.inorbit:
3858         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3859         game.inorbit = False
3860     # If tractor beam is to occur, don't move full distance
3861     if game.state.date+game.optime >= scheduled(FTBEAM):
3862         if game.iscloaked:
3863             # We can't be tractor beamed if cloaked,
3864             # so move the event into the future
3865             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
3866         else:
3867             trbeam = True
3868             game.condition = "red"
3869             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3870             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3871     # Move out
3872     game.quad[game.sector.i][game.sector.j] = '.'
3873     for _m in range(icourse.moves):
3874         icourse.nexttok()
3875         w = icourse.sector()
3876         if icourse.origin.quadrant() != icourse.location.quadrant():
3877             newquadrant(noattack)
3878             break
3879         elif check_collision(w):
3880             print("Collision detected")
3881             break
3882         else:
3883             game.sector = w
3884     # We're in destination quadrant -- compute new average enemy distances
3885     game.quad[game.sector.i][game.sector.j] = game.ship
3886     if game.enemies:
3887         for enemy in game.enemies:
3888             finald = (w-enemy.location).distance()
3889             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3890             enemy.kdist = finald
3891         sortenemies()
3892         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3893             attack(torps_ok=False)
3894         for enemy in game.enemies:
3895             enemy.kavgd = enemy.kdist
3896     newcnd()
3897     drawmaps(0)
3898     setwnd(message_window)
3899     return
3900
3901 def dock(verbose):
3902     "Dock our ship at a starbase."
3903     scanner.chew()
3904     if game.condition == "docked" and verbose:
3905         prout(_("Already docked."))
3906         return
3907     if game.inorbit:
3908         prout(_("You must first leave standard orbit."))
3909         return
3910     if game.base is None or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3911         prout(crmshp() + _(" not adjacent to base."))
3912         return
3913     if game.iscloaked:
3914         prout(_("You cannot dock while cloaked."))
3915         return
3916     game.condition = "docked"
3917     if verbose:
3918         prout(_("Docked."))
3919     game.ididit = True
3920     if game.energy < game.inenrg:
3921         game.energy = game.inenrg
3922     game.shield = game.inshld
3923     game.torps = game.intorps
3924     game.lsupres = game.inlsr
3925     game.state.crew = FULLCREW
3926     if game.brigcapacity-game.brigfree > 0:
3927         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3928         game.kcaptured += game.brigcapacity-game.brigfree
3929         game.brigfree = game.brigcapacity
3930     if communicating() and \
3931         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3932         # get attack report from base
3933         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3934         attackreport(False)
3935         game.iseenit = True
3936
3937 def cartesian(loc1=None, loc2=None):
3938     if loc1 is None:
3939         return game.quadrant * QUADSIZE + game.sector
3940     elif loc2 is None:
3941         return game.quadrant * QUADSIZE + loc1
3942     else:
3943         return loc1 * QUADSIZE + loc2
3944
3945 def getcourse(isprobe):
3946     "Get a course and distance from the user."
3947     key = ""
3948     dquad = copy.copy(game.quadrant)
3949     navmode = "unspecified"
3950     itemp = "curt"
3951     dsect = Coord()
3952     iprompt = False
3953     if game.landed and not isprobe:
3954         prout(_("Dummy! You can't leave standard orbit until you"))
3955         proutn(_("are back aboard the ship."))
3956         scanner.chew()
3957         raise TrekError
3958     while navmode == "unspecified":
3959         if damaged(DNAVSYS):
3960             if isprobe:
3961                 prout(_("Computer damaged; manual navigation only"))
3962             else:
3963                 prout(_("Computer damaged; manual movement only"))
3964             scanner.chew()
3965             navmode = "manual"
3966             key = "IHEOL"
3967             break
3968         key = scanner.nexttok()
3969         if key == "IHEOL":
3970             proutn(_("Manual or automatic- "))
3971             iprompt = True
3972             scanner.chew()
3973         elif key == "IHALPHA":
3974             if scanner.sees("manual"):
3975                 navmode = "manual"
3976                 key = scanner.nexttok()
3977                 break
3978             elif scanner.sees("automatic"):
3979                 navmode = "automatic"
3980                 key = scanner.nexttok()
3981                 break
3982             else:
3983                 huh()
3984                 scanner.chew()
3985                 raise TrekError
3986         else: # numeric
3987             if isprobe:
3988                 prout(_("(Manual navigation assumed.)"))
3989             else:
3990                 prout(_("(Manual movement assumed.)"))
3991             navmode = "manual"
3992             break
3993     delta = Coord()
3994     if navmode == "automatic":
3995         while key == "IHEOL":
3996             if isprobe:
3997                 proutn(_("Target quadrant or quadrant&sector- "))
3998             else:
3999                 proutn(_("Destination sector or quadrant&sector- "))
4000             scanner.chew()
4001             iprompt = True
4002             key = scanner.nexttok()
4003         if key != "IHREAL":
4004             huh()
4005             raise TrekError
4006         xi = int(round(scanner.real))-1
4007         key = scanner.nexttok()
4008         if key != "IHREAL":
4009             huh()
4010             raise TrekError
4011         xj = int(round(scanner.real))-1
4012         key = scanner.nexttok()
4013         if key == "IHREAL":
4014             # both quadrant and sector specified
4015             xk = int(round(scanner.real))-1
4016             key = scanner.nexttok()
4017             if key != "IHREAL":
4018                 huh()
4019                 raise TrekError
4020             xl = int(round(scanner.real))-1
4021             dquad.i = xi
4022             dquad.j = xj
4023             dsect.i = xk
4024             dsect.j = xl
4025         else:
4026             # only one pair of numbers was specified
4027             if isprobe:
4028                 # only quadrant specified -- go to center of dest quad
4029                 dquad.i = xi
4030                 dquad.j = xj
4031                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
4032             else:
4033                 # only sector specified
4034                 dsect.i = xi
4035                 dsect.j = xj
4036             itemp = "normal"
4037         if not dquad.valid_quadrant() or not dsect.valid_sector():
4038             huh()
4039             raise TrekError
4040         skip(1)
4041         if not isprobe:
4042             if itemp > "curt":
4043                 if iprompt:
4044                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4045             else:
4046                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4047         # the actual deltas get computed here
4048         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4049         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4050     else: # manual
4051         while key == "IHEOL":
4052             proutn(_("X and Y displacements- "))
4053             scanner.chew()
4054             iprompt = True
4055             key = scanner.nexttok()
4056         itemp = "verbose"
4057         if key == "IHREAL":
4058             delta.j = scanner.real
4059         else:
4060             huh()
4061             raise TrekError
4062         key = scanner.nexttok()
4063         if key == "IHREAL":
4064             delta.i = scanner.real
4065         elif key == "IHEOL":
4066             delta.i = 0
4067             scanner.push("\n")
4068         else:
4069             huh()
4070             raise TrekError
4071     # Check for zero movement
4072     if delta.i == 0 and delta.j == 0:
4073         scanner.chew()
4074         raise TrekError
4075     if itemp == "verbose" and not isprobe:
4076         skip(1)
4077         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4078     scanner.chew()
4079     return course(bearing=delta.bearing(), distance=delta.distance())
4080
4081 class course:
4082     def __init__(self, bearing, distance, origin=None):
4083         self.distance = distance
4084         self.bearing = bearing
4085         if origin is None:
4086             self.origin = cartesian(game.quadrant, game.sector)
4087         else:
4088             self.origin = origin
4089         # The bearing() code we inherited from FORTRAN is actually computing
4090         # clockface directions!
4091         if self.bearing < 0.0:
4092             self.bearing += 12.0
4093         self.angle = ((15.0 - self.bearing) * 0.5235988)
4094         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4095         bigger = max(abs(self.increment.i), abs(self.increment.j))
4096         self.increment /= bigger
4097         self.moves = int(round(10*self.distance*bigger))
4098         self.reset()
4099         self.final = (self.location + self.moves*self.increment).roundtogrid()
4100         self.location = self.origin
4101         self.nextlocation = None
4102     def reset(self):
4103         self.location = self.origin
4104         self.step = 0
4105     def arrived(self):
4106         return self.location.roundtogrid() == self.final
4107     def nexttok(self):
4108         "Next step on course."
4109         self.step += 1
4110         self.nextlocation = self.location + self.increment
4111         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4112         self.location = self.nextlocation
4113         return samequad
4114     def quadrant(self):
4115         return self.location.quadrant()
4116     def sector(self):
4117         return self.location.sector()
4118     def power(self, w):
4119         return self.distance*(w**3)*(game.shldup+1)
4120     def time(self, w):
4121         return 10.0*self.distance/w**2
4122
4123 def impulse():
4124     "Move under impulse power."
4125     game.ididit = False
4126     if damaged(DIMPULS):
4127         scanner.chew()
4128         skip(1)
4129         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4130         return
4131     if game.energy > 30.0:
4132         try:
4133             icourse = getcourse(isprobe=False)
4134         except TrekError:
4135             return
4136         power = 20.0 + 100.0*icourse.distance
4137     else:
4138         power = 30.0
4139     if power >= game.energy:
4140         # Insufficient power for trip
4141         skip(1)
4142         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4143         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4144         if game.energy > 30:
4145             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4146                    int(0.01 * (game.energy-20.0)-0.05))
4147             prout(_(" quadrants.\""))
4148         else:
4149             prout(_("quadrant.  They are, therefore, useless.\""))
4150         scanner.chew()
4151         return
4152     # Make sure enough time is left for the trip
4153     game.optime = icourse.distance/0.095
4154     if game.optime >= game.state.remtime:
4155         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4156         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4157         proutn(_("we dare spend the time?\" "))
4158         if not ja():
4159             return
4160     # Activate impulse engines and pay the cost
4161     imove(icourse, noattack=False)
4162     game.ididit = True
4163     if game.alldone:
4164         return
4165     power = 20.0 + 100.0*icourse.distance
4166     game.energy -= power
4167     game.optime = icourse.distance/0.095
4168     if game.energy <= 0:
4169         finish(FNRG)
4170     return
4171
4172 def warp(wcourse, involuntary):
4173     "ove under warp drive."
4174     blooey = False; twarp = False
4175     if not involuntary: # Not WARPX entry
4176         game.ididit = False
4177         if game.iscloaked:
4178             scanner.chew()
4179             skip(1)
4180             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4181             return
4182         if game.damage[DWARPEN] > 10.0:
4183             scanner.chew()
4184             skip(1)
4185             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4186             return
4187         if damaged(DWARPEN) and game.warpfac > 4.0:
4188             scanner.chew()
4189             skip(1)
4190             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4191             prout(_("  is repaired, I can only give you warp 4.\""))
4192             return
4193                # Read in course and distance
4194         if wcourse is None:
4195             try:
4196                 wcourse = getcourse(isprobe=False)
4197             except TrekError:
4198                 return
4199         # Make sure starship has enough energy for the trip
4200         # Note: this formula is slightly different from the C version,
4201         # and lets you skate a bit closer to the edge.
4202         if wcourse.power(game.warpfac) >= game.energy:
4203             # Insufficient power for trip
4204             game.ididit = False
4205             skip(1)
4206             prout(_("Engineering to bridge--"))
4207             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4208                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4209                 if iwarp <= 0:
4210                     prout(_("We can't do it, Captain. We don't have enough energy."))
4211                 else:
4212                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4213                     if game.shldup:
4214                         prout(",")
4215                         prout(_("if you'll lower the shields."))
4216                     else:
4217                         prout(".")
4218             else:
4219                 prout(_("We haven't the energy to go that far with the shields up."))
4220             return
4221         # Make sure enough time is left for the trip
4222         game.optime = wcourse.time(game.warpfac)
4223         if game.optime >= 0.8*game.state.remtime:
4224             skip(1)
4225             prout(_("First Officer Spock- \"Captain, I compute that such"))
4226             proutn(_("  a trip would require approximately %2.0f") %
4227                    (100.0*game.optime/game.state.remtime))
4228             prout(_(" percent of our"))
4229             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4230             if not ja():
4231                 game.ididit = False
4232                 game.optime=0
4233                 return
4234     # Entry WARPX
4235     if game.warpfac > 6.0:
4236         # Decide if engine damage will occur
4237         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4238         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4239         if prob > rnd.real():
4240             blooey = True
4241             wcourse.distance = rnd.real(wcourse.distance)
4242         # Decide if time warp will occur
4243         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
4244             twarp = True
4245         if game.idebug and game.warpfac==10 and not twarp:
4246             blooey = False
4247             proutn("=== Force time warp? ")
4248             if ja():
4249                 twarp = True
4250         if blooey or twarp:
4251             # If time warp or engine damage, check path
4252             # If it is obstructed, don't do warp or damage
4253             look = wcourse.moves
4254             while look > 0:
4255                 look -= 1
4256                 wcourse.nexttok()
4257                 w = wcourse.sector()
4258                 if not w.valid_sector():
4259                     break
4260                 if game.quad[w.i][w.j] != '.':
4261                     blooey = False
4262                     twarp = False
4263             wcourse.reset()
4264     # Activate Warp Engines and pay the cost
4265     imove(wcourse, noattack=False)
4266     if game.alldone:
4267         return
4268     game.energy -= wcourse.power(game.warpfac)
4269     if game.energy <= 0:
4270         finish(FNRG)
4271     game.optime = wcourse.time(game.warpfac)
4272     if twarp:
4273         timwrp()
4274     if blooey:
4275         game.damage[DWARPEN] = game.damfac * rnd.real(1.0, 4.0)
4276         skip(1)
4277         prout(_("Engineering to bridge--"))
4278         prout(_("  Scott here.  The warp engines are damaged."))
4279         prout(_("  We'll have to reduce speed to warp 4."))
4280     game.ididit = True
4281     return
4282
4283 def setwarp():
4284     "Change the warp factor."
4285     while True:
4286         key=scanner.nexttok()
4287         if key != "IHEOL":
4288             break
4289         scanner.chew()
4290         proutn(_("Warp factor- "))
4291     if key != "IHREAL":
4292         huh()
4293         return
4294     if game.damage[DWARPEN] > 10.0:
4295         prout(_("Warp engines inoperative."))
4296         return
4297     if damaged(DWARPEN) and scanner.real > 4.0:
4298         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4299         prout(_("  but right now we can only go warp 4.\""))
4300         return
4301     if scanner.real > 10.0:
4302         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4303         return
4304     if scanner.real < 1.0:
4305         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4306         return
4307     oldfac = game.warpfac
4308     game.warpfac = scanner.real
4309     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4310         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4311               int(game.warpfac))
4312         return
4313     if game.warpfac < 8.00:
4314         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4315         return
4316     if game.warpfac == 10.0:
4317         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4318         return
4319     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4320     return
4321
4322 def atover(igrab):
4323     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4324     scanner.chew()
4325     # is captain on planet?
4326     if game.landed:
4327         if damaged(DTRANSP):
4328             finish(FPNOVA)
4329             return
4330         prout(_("Scotty rushes to the transporter controls."))
4331         if game.shldup:
4332             prout(_("But with the shields up it's hopeless."))
4333             finish(FPNOVA)
4334         prouts(_("His desperate attempt to rescue you . . ."))
4335         if rnd.withprob(0.5):
4336             prout(_("fails."))
4337             finish(FPNOVA)
4338             return
4339         prout(_("SUCCEEDS!"))
4340         if game.imine:
4341             game.imine = False
4342             proutn(_("The crystals mined were "))
4343             if rnd.withprob(0.25):
4344                 prout(_("lost."))
4345             else:
4346                 prout(_("saved."))
4347                 game.icrystl = True
4348     if igrab:
4349         return
4350     # Check to see if captain in shuttle craft
4351     if game.icraft:
4352         finish(FSTRACTOR)
4353     if game.alldone:
4354         return
4355     # Inform captain of attempt to reach safety
4356     skip(1)
4357     while True:
4358         if game.justin:
4359             prouts(_("***RED ALERT!  RED ALERT!"))
4360             skip(1)
4361             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4362             prouts(_("   a supernova."))
4363             skip(2)
4364         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4365         prout(_("safely out of quadrant."))
4366         if not damaged(DRADIO):
4367             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4368         # Try to use warp engines
4369         if damaged(DWARPEN):
4370             skip(1)
4371             prout(_("Warp engines damaged."))
4372             finish(FSNOVAED)
4373             return
4374         game.warpfac = rnd.real(6.0, 8.0)
4375         prout(_("Warp factor set to %d") % int(game.warpfac))
4376         power = 0.75*game.energy
4377         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4378         dist = max(dist, rnd.real(math.sqrt(2)))
4379         bugout = course(bearing=rnd.real(12), distance=dist)        # How dumb!
4380         game.optime = bugout.time(game.warpfac)
4381         game.justin = False
4382         game.inorbit = False
4383         warp(bugout, involuntary=True)
4384         if not game.justin:
4385             # This is bad news, we didn't leave quadrant.
4386             if game.alldone:
4387                 return
4388             skip(1)
4389             prout(_("Insufficient energy to leave quadrant."))
4390             finish(FSNOVAED)
4391             return
4392         # Repeat if another snova
4393         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4394             break
4395     if game.unwon()==0:
4396         finish(FWON) # Snova killed remaining enemy.
4397
4398 def timwrp():
4399     "Let's do the time warp again."
4400     prout(_("***TIME WARP ENTERED."))
4401     if game.state.snap and rnd.withprob(0.5):
4402         # Go back in time
4403         prout(_("You are traveling backwards in time %d stardates.") %
4404               int(game.state.date-game.snapsht.date))
4405         game.state = game.snapsht
4406         game.state.snap = False
4407         if len(game.state.kcmdr):
4408             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4409             schedule(FBATTAK, expran(0.3*game.intime))
4410         schedule(FSNOVA, expran(0.5*game.intime))
4411         # next snapshot will be sooner
4412         schedule(FSNAP, expran(0.25*game.state.remtime))
4413
4414         if game.state.nscrem:
4415             schedule(FSCMOVE, 0.2777)
4416         game.isatb = 0
4417         unschedule(FCDBAS)
4418         unschedule(FSCDBAS)
4419         game.battle.invalidate()
4420         # Make sure Galileo is consistant -- Snapshot may have been taken
4421         # when on planet, which would give us two Galileos!
4422         gotit = False
4423         for l in range(game.inplan):
4424             if game.state.planets[l].known == "shuttle_down":
4425                 gotit = True
4426                 if game.iscraft == "onship" and game.ship=='E':
4427                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4428                     game.iscraft = "offship"
4429         # Likewise, if in the original time the Galileo was abandoned, but
4430         # was on ship earlier, it would have vanished -- let's restore it.
4431         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4432             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4433             game.iscraft = "onship"
4434         # There used to be code to do the actual reconstrction here,
4435         # but the starchart is now part of the snapshotted galaxy state.
4436         prout(_("Spock has reconstructed a correct star chart from memory"))
4437     else:
4438         # Go forward in time
4439         game.optime = expran(0.5*game.intime)
4440         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4441         # cheat to make sure no tractor beams occur during time warp
4442         postpone(FTBEAM, game.optime)
4443         game.damage[DRADIO] += game.optime
4444     newqad()
4445     events()        # Stas Sergeev added this -- do pending events
4446
4447 def probe():
4448     "Launch deep-space probe."
4449     # New code to launch a deep space probe
4450     if game.nprobes == 0:
4451         scanner.chew()
4452         skip(1)
4453         if game.ship == 'E':
4454             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4455         else:
4456             prout(_("Ye Faerie Queene has no deep space probes."))
4457         return
4458     if damaged(DDSP):
4459         scanner.chew()
4460         skip(1)
4461         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4462         return
4463     if is_scheduled(FDSPROB):
4464         scanner.chew()
4465         skip(1)
4466         if damaged(DRADIO) and game.condition != "docked":
4467             prout(_("Spock-  \"Records show the previous probe has not yet"))
4468             prout(_("   reached its destination.\""))
4469         else:
4470             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4471         return
4472     key = scanner.nexttok()
4473     if key == "IHEOL":
4474         if game.nprobes == 1:
4475             prout(_("1 probe left."))
4476         else:
4477             prout(_("%d probes left") % game.nprobes)
4478         proutn(_("Are you sure you want to fire a probe? "))
4479         if not ja():
4480             return
4481     game.isarmed = False
4482     if key == "IHALPHA" and scanner.token == "armed":
4483         game.isarmed = True
4484         key = scanner.nexttok()
4485     elif key == "IHEOL":
4486         proutn(_("Arm NOVAMAX warhead? "))
4487         game.isarmed = ja()
4488     elif key == "IHREAL":                # first element of course
4489         scanner.push(scanner.token)
4490     try:
4491         game.probe = getcourse(isprobe=True)
4492     except TrekError:
4493         return
4494     game.nprobes -= 1
4495     schedule(FDSPROB, 0.01) # Time to move one sector
4496     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4497     game.ididit = True
4498     return
4499
4500 def mayday():
4501     "Yell for help from nearest starbase."
4502     # There's more than one way to move in this game!
4503     scanner.chew()
4504     # Test for conditions which prevent calling for help
4505     if game.condition == "docked":
4506         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4507         return
4508     if damaged(DRADIO):
4509         prout(_("Subspace radio damaged."))
4510         return
4511     if not game.state.baseq:
4512         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4513         return
4514     if game.landed:
4515         prout(_("You must be aboard the %s.") % crmshp())
4516         return
4517     # OK -- call for help from nearest starbase
4518     game.nhelp += 1
4519     if game.base.i!=0:
4520         # There's one in this quadrant
4521         ddist = (game.base - game.sector).distance()
4522     else:
4523         ibq = None      # Force base-quadrant game to persist past loop
4524         ddist = FOREVER
4525         for ibq in game.state.baseq:
4526             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4527             if xdist < ddist:
4528                 ddist = xdist
4529         if ibq is None:
4530             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4531             return
4532         # Since starbase not in quadrant, set up new quadrant
4533         game.quadrant = ibq
4534         newqad()
4535     # dematerialize starship
4536     game.quad[game.sector.i][game.sector.j]='.'
4537     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4538            % (game.quadrant, crmshp()))
4539     game.sector.invalidate()
4540     for m in range(1, 5+1):
4541         w = game.base.scatter()
4542         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4543             # found one -- finish up
4544             game.sector = w
4545             break
4546     if game.sector is None:
4547         prout(_("You have been lost in space..."))
4548         finish(FMATERIALIZE)
4549         return
4550     # Give starbase three chances to rematerialize starship
4551     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4552     for m in range(1, 3+1):
4553         if m == 1: proutn(_("1st"))
4554         elif m == 2: proutn(_("2nd"))
4555         elif m == 3: proutn(_("3rd"))
4556         proutn(_(" attempt to re-materialize ") + crmshp())
4557         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4558         textcolor(RED)
4559         warble()
4560         if rnd.real() > probf:
4561             break
4562         prout(_("fails."))
4563         textcolor(DEFAULT)
4564         curses.delay_output(500)
4565     if m > 3:
4566         game.quad[game.sector.i][game.sector.j]='?'
4567         game.alive = False
4568         drawmaps(1)
4569         setwnd(message_window)
4570         finish(FMATERIALIZE)
4571         return
4572     game.quad[game.sector.i][game.sector.j]=game.ship
4573     textcolor(GREEN)
4574     prout(_("succeeds."))
4575     textcolor(DEFAULT)
4576     dock(False)
4577     skip(1)
4578     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4579
4580 def abandon():
4581     "Abandon ship."
4582     scanner.chew()
4583     if game.condition=="docked":
4584         if game.ship!='E':
4585             prout(_("You cannot abandon Ye Faerie Queene."))
4586             return
4587     else:
4588         # Must take shuttle craft to exit
4589         if game.damage[DSHUTTL]==-1:
4590             prout(_("Ye Faerie Queene has no shuttle craft."))
4591             return
4592         if game.damage[DSHUTTL]<0:
4593             prout(_("Shuttle craft now serving Big Macs."))
4594             return
4595         if game.damage[DSHUTTL]>0:
4596             prout(_("Shuttle craft damaged."))
4597             return
4598         if game.landed:
4599             prout(_("You must be aboard the ship."))
4600             return
4601         if game.iscraft != "onship":
4602             prout(_("Shuttle craft not currently available."))
4603             return
4604         # Emit abandon ship messages
4605         skip(1)
4606         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4607         skip(1)
4608         prouts(_("***ALL HANDS ABANDON SHIP!"))
4609         skip(2)
4610         prout(_("Captain and crew escape in shuttle craft."))
4611         if not game.state.baseq:
4612             # Oops! no place to go...
4613             finish(FABANDN)
4614             return
4615         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4616         # Dispose of crew
4617         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4618             prout(_("Remainder of ship's complement beam down"))
4619             prout(_("to nearest habitable planet."))
4620         elif q.planet is not None and not damaged(DTRANSP):
4621             prout(_("Remainder of ship's complement beam down to %s.") %
4622                   q.planet)
4623         else:
4624             prout(_("Entire crew of %d left to die in outer space.") %
4625                   game.state.crew)
4626             game.casual += game.state.crew
4627             game.abandoned += game.state.crew
4628         # If at least one base left, give 'em the Faerie Queene
4629         skip(1)
4630         game.icrystl = False # crystals are lost
4631         game.nprobes = 0 # No probes
4632         prout(_("You are captured by Klingons and released to"))
4633         prout(_("the Federation in a prisoner-of-war exchange."))
4634         nb = rnd.integer(len(game.state.baseq))
4635         # Set up quadrant and position FQ adjacient to base
4636         if not game.quadrant == game.state.baseq[nb]:
4637             game.quadrant = game.state.baseq[nb]
4638             game.sector.i = game.sector.j = 5
4639             newqad()
4640         while True:
4641             # position next to base by trial and error
4642             game.quad[game.sector.i][game.sector.j] = '.'
4643             l = QUADSIZE
4644             for l in range(QUADSIZE):
4645                 game.sector = game.base.scatter()
4646                 if game.sector.valid_sector() and \
4647                        game.quad[game.sector.i][game.sector.j] == '.':
4648                     break
4649             if l < QUADSIZE:
4650                 break # found a spot
4651             game.sector.i=QUADSIZE/2
4652             game.sector.j=QUADSIZE/2
4653             newqad()
4654     # Get new commission
4655     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4656     game.state.crew = FULLCREW
4657     prout(_("Starfleet puts you in command of another ship,"))
4658     prout(_("the Faerie Queene, which is antiquated but,"))
4659     prout(_("still useable."))
4660     if game.icrystl:
4661         prout(_("The dilithium crystals have been moved."))
4662     game.imine = False
4663     game.iscraft = "offship" # Galileo disappears
4664     # Resupply ship
4665     game.condition="docked"
4666     for l in range(NDEVICES):
4667         game.damage[l] = 0.0
4668     game.damage[DSHUTTL] = -1
4669     game.energy = game.inenrg = 3000.0
4670     game.shield = game.inshld = 1250.0
4671     game.torps = game.intorps = 6
4672     game.lsupres=game.inlsr=3.0
4673     game.shldup=False
4674     game.warpfac=5.0
4675     game.brigfree = game.brigcapacity = 300
4676     return
4677
4678 # Code from planets.c begins here.
4679
4680 def consumeTime():
4681     "Abort a lengthy operation if an event interrupts it."
4682     game.ididit = True
4683     events()
4684     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4685         return True
4686     return False
4687
4688 def survey():
4689     "Report on (uninhabited) planets in the galaxy."
4690     iknow = False
4691     skip(1)
4692     scanner.chew()
4693     prout(_("Spock-  \"Planet report follows, Captain.\""))
4694     skip(1)
4695     for i in range(game.inplan):
4696         if game.state.planets[i].pclass == "destroyed":
4697             continue
4698         if (game.state.planets[i].known != "unknown" \
4699             and not game.state.planets[i].inhabited) \
4700             or game.idebug:
4701             iknow = True
4702             if game.idebug and game.state.planets[i].known=="unknown":
4703                 proutn("(Unknown) ")
4704             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4705             proutn(_("   class "))
4706             proutn(game.state.planets[i].pclass)
4707             proutn("   ")
4708             if game.state.planets[i].crystals != "present":
4709                 proutn(_("no "))
4710             prout(_("dilithium crystals present."))
4711             if game.state.planets[i].known=="shuttle_down":
4712                 prout(_("    Shuttle Craft Galileo on surface."))
4713     if not iknow:
4714         prout(_("No information available."))
4715
4716 def orbit():
4717     "Enter standard orbit."
4718     skip(1)
4719     scanner.chew()
4720     if game.inorbit:
4721         prout(_("Already in standard orbit."))
4722         return
4723     if damaged(DWARPEN) and damaged(DIMPULS):
4724         prout(_("Both warp and impulse engines damaged."))
4725         return
4726     if game.plnet is None:
4727         prout("There is no planet in this sector.")
4728         return
4729     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4730         prout(crmshp() + _(" not adjacent to planet."))
4731         skip(1)
4732         return
4733     game.optime = rnd.real(0.02, 0.05)
4734     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4735     newcnd()
4736     if consumeTime():
4737         return
4738     game.height = rnd.real(1400, 8600)
4739     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4740     game.inorbit = True
4741     game.ididit = True
4742
4743 def sensor():
4744     "Examine planets in this quadrant."
4745     if damaged(DSRSENS):
4746         if game.options & OPTION_TTY:
4747             prout(_("Short range sensors damaged."))
4748         return
4749     if game.iplnet is None:
4750         if game.options & OPTION_TTY:
4751             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4752         return
4753     if game.iplnet.known == "unknown":
4754         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4755         skip(1)
4756         prout(_("         Planet at Sector %s is of class %s.") %
4757               (game.plnet, game.iplnet.pclass))
4758         if game.iplnet.known=="shuttle_down":
4759             prout(_("         Sensors show Galileo still on surface."))
4760         proutn(_("         Readings indicate"))
4761         if game.iplnet.crystals != "present":
4762             proutn(_(" no"))
4763         prout(_(" dilithium crystals present.\""))
4764         if game.iplnet.known == "unknown":
4765             game.iplnet.known = "known"
4766     elif game.iplnet.inhabited:
4767         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4768         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4769
4770 def beam():
4771     "Use the transporter."
4772     nrgneed = 0
4773     scanner.chew()
4774     skip(1)
4775     if damaged(DTRANSP):
4776         prout(_("Transporter damaged."))
4777         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4778             skip(1)
4779             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4780             if ja():
4781                 shuttle()
4782         return
4783     if not game.inorbit:
4784         prout(crmshp() + _(" not in standard orbit."))
4785         return
4786     if game.shldup:
4787         prout(_("Impossible to transport through shields."))
4788         return
4789     if game.iplnet.known=="unknown":
4790         prout(_("Spock-  \"Captain, we have no information on this planet"))
4791         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4792         prout(_("  you may not go down.\""))
4793         return
4794     if not game.landed and game.iplnet.crystals=="absent":
4795         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4796         prout(_("  exploring a planet with no dilithium crystals."))
4797         proutn(_("  Are you sure this is wise?\" "))
4798         if not ja():
4799             scanner.chew()
4800             return
4801     if not (game.options & OPTION_PLAIN):
4802         nrgneed = 50 * game.skill + game.height / 100.0
4803         if nrgneed > game.energy:
4804             prout(_("Engineering to bridge--"))
4805             prout(_("  Captain, we don't have enough energy for transportation."))
4806             return
4807         if not game.landed and nrgneed * 2 > game.energy:
4808             prout(_("Engineering to bridge--"))
4809             prout(_("  Captain, we have enough energy only to transport you down to"))
4810             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4811             if game.iplnet.known == "shuttle_down":
4812                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4813             proutn(_("  Are you sure this is wise?\" "))
4814             if not ja():
4815                 scanner.chew()
4816                 return
4817     if game.landed:
4818         # Coming from planet
4819         if game.iplnet.known=="shuttle_down":
4820             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4821             if ja():
4822                 scanner.chew()
4823                 return
4824             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4825         prout(_("Landing party assembled, ready to beam up."))
4826         skip(1)
4827         prout(_("Kirk whips out communicator..."))
4828         prouts(_("BEEP  BEEP  BEEP"))
4829         skip(2)
4830         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4831     else:
4832         # Going to planet
4833         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4834         skip(1)
4835         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4836         skip(1)
4837         prout(_("Kirk-  \"Energize.\""))
4838     game.ididit = True
4839     skip(1)
4840     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4841     skip(2)
4842     if not rnd.withprob(0.98):
4843         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4844         skip(2)
4845         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4846         finish(FLOST)
4847         return
4848     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4849     game.landed = not game.landed
4850     game.energy -= nrgneed
4851     skip(2)
4852     prout(_("Transport complete."))
4853     if game.landed and game.iplnet.known=="shuttle_down":
4854         prout(_("The shuttle craft Galileo is here!"))
4855     if not game.landed and game.imine:
4856         game.icrystl = True
4857         game.cryprob = 0.05
4858     game.imine = False
4859     return
4860
4861 def mine():
4862     "Strip-mine a world for dilithium."
4863     skip(1)
4864     scanner.chew()
4865     if not game.landed:
4866         prout(_("Mining party not on planet."))
4867         return
4868     if game.iplnet.crystals == "mined":
4869         prout(_("This planet has already been strip-mined for dilithium."))
4870         return
4871     elif game.iplnet.crystals == "absent":
4872         prout(_("No dilithium crystals on this planet."))
4873         return
4874     if game.imine:
4875         prout(_("You've already mined enough crystals for this trip."))
4876         return
4877     if game.icrystl and game.cryprob == 0.05:
4878         prout(_("With all those fresh crystals aboard the ") + crmshp())
4879         prout(_("there's no reason to mine more at this time."))
4880         return
4881     game.optime = rnd.real(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4882     if consumeTime():
4883         return
4884     prout(_("Mining operation complete."))
4885     game.iplnet.crystals = "mined"
4886     game.imine = game.ididit = True
4887
4888 def usecrystals():
4889     "Use dilithium crystals."
4890     game.ididit = False
4891     skip(1)
4892     scanner.chew()
4893     if not game.icrystl:
4894         prout(_("No dilithium crystals available."))
4895         return
4896     if game.energy >= 1000:
4897         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4898         prout(_("  except when Condition Yellow exists."))
4899         return
4900     prout(_("Spock- \"Captain, I must warn you that loading"))
4901     prout(_("  raw dilithium crystals into the ship's power"))
4902     prout(_("  system may risk a severe explosion."))
4903     proutn(_("  Are you sure this is wise?\" "))
4904     if not ja():
4905         scanner.chew()
4906         return
4907     skip(1)
4908     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4909     prout(_("  Mr. Spock and I will try it.\""))
4910     skip(1)
4911     prout(_("Spock-  \"Crystals in place, Sir."))
4912     prout(_("  Ready to activate circuit.\""))
4913     skip(1)
4914     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4915     skip(1)
4916     if rnd.withprob(game.cryprob):
4917         prouts(_("  \"Activating now! - - No good!  It's***"))
4918         skip(2)
4919         prouts(_("***RED ALERT!  RED A*L********************************"))
4920         skip(1)
4921         stars()
4922         prouts(_("******************   KA-BOOM!!!!   *******************"))
4923         skip(1)
4924         kaboom()
4925         return
4926     game.energy += rnd.real(5000.0, 5500.0)
4927     prouts(_("  \"Activating now! - - "))
4928     prout(_("The instruments"))
4929     prout(_("   are going crazy, but I think it's"))
4930     prout(_("   going to work!!  Congratulations, Sir!\""))
4931     game.cryprob *= 2.0
4932     game.ididit = True
4933
4934 def shuttle():
4935     "Use shuttlecraft for planetary jaunt."
4936     scanner.chew()
4937     skip(1)
4938     if damaged(DSHUTTL):
4939         if game.damage[DSHUTTL] == -1.0:
4940             if game.inorbit and game.iplnet.known == "shuttle_down":
4941                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4942             else:
4943                 prout(_("Ye Faerie Queene had no shuttle craft."))
4944         elif game.damage[DSHUTTL] > 0:
4945             prout(_("The Galileo is damaged."))
4946         else: # game.damage[DSHUTTL] < 0
4947             prout(_("Shuttle craft is now serving Big Macs."))
4948         return
4949     if not game.inorbit:
4950         prout(crmshp() + _(" not in standard orbit."))
4951         return
4952     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4953         prout(_("Shuttle craft not currently available."))
4954         return
4955     if not game.landed and game.iplnet.known=="shuttle_down":
4956         prout(_("You will have to beam down to retrieve the shuttle craft."))
4957         return
4958     if game.shldup or game.condition == "docked":
4959         prout(_("Shuttle craft cannot pass through shields."))
4960         return
4961     if game.iplnet.known=="unknown":
4962         prout(_("Spock-  \"Captain, we have no information on this planet"))
4963         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4964         prout(_("  you may not fly down.\""))
4965         return
4966     game.optime = 3.0e-5*game.height
4967     if game.optime >= 0.8*game.state.remtime:
4968         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4969         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4970                int(100*game.optime/game.state.remtime))
4971         prout(_("remaining time."))
4972         proutn(_("Are you sure this is wise?\" "))
4973         if not ja():
4974             game.optime = 0.0
4975             return
4976     if game.landed:
4977         # Kirk on planet
4978         if game.iscraft == "onship":
4979             # Galileo on ship!
4980             if not damaged(DTRANSP):
4981                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4982                 if ja():
4983                     beam()
4984                     return
4985                 proutn(_("Shuttle crew"))
4986             else:
4987                 proutn(_("Rescue party"))
4988             prout(_(" boards Galileo and swoops toward planet surface."))
4989             game.iscraft = "offship"
4990             skip(1)
4991             if consumeTime():
4992                 return
4993             game.iplnet.known="shuttle_down"
4994             prout(_("Trip complete."))
4995             return
4996         else:
4997             # Ready to go back to ship
4998             prout(_("You and your mining party board the"))
4999             prout(_("shuttle craft for the trip back to the Enterprise."))
5000             skip(1)
5001             prouts(_("The short hop begins . . ."))
5002             skip(1)
5003             game.iplnet.known="known"
5004             game.icraft = True
5005             skip(1)
5006             game.landed = False
5007             if consumeTime():
5008                 return
5009             game.iscraft = "onship"
5010             game.icraft = False
5011             if game.imine:
5012                 game.icrystl = True
5013                 game.cryprob = 0.05
5014             game.imine = False
5015             prout(_("Trip complete."))
5016             return
5017     else:
5018         # Kirk on ship and so is Galileo
5019         prout(_("Mining party assembles in the hangar deck,"))
5020         prout(_("ready to board the shuttle craft \"Galileo\"."))
5021         skip(1)
5022         prouts(_("The hangar doors open; the trip begins."))
5023         skip(1)
5024         game.icraft = True
5025         game.iscraft = "offship"
5026         if consumeTime():
5027             return
5028         game.iplnet.known = "shuttle_down"
5029         game.landed = True
5030         game.icraft = False
5031         prout(_("Trip complete."))
5032         return
5033
5034 def deathray():
5035     "Use the big zapper."
5036     game.ididit = False
5037     skip(1)
5038     scanner.chew()
5039     if game.ship != 'E':
5040         prout(_("Ye Faerie Queene has no death ray."))
5041         return
5042     if len(game.enemies)==0:
5043         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5044         return
5045     if damaged(DDRAY):
5046         prout(_("Death Ray is damaged."))
5047         return
5048     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5049     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5050     proutn(_("  are you sure this is wise?\" "))
5051     if not ja():
5052         return
5053     prout(_("Spock-  \"Acknowledged.\""))
5054     skip(1)
5055     game.ididit = True
5056     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5057     skip(1)
5058     prout(_("Crew scrambles in emergency preparation."))
5059     prout(_("Spock and Scotty ready the death ray and"))
5060     prout(_("prepare to channel all ship's power to the device."))
5061     skip(1)
5062     prout(_("Spock-  \"Preparations complete, sir.\""))
5063     prout(_("Kirk-  \"Engage!\""))
5064     skip(1)
5065     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5066     skip(1)
5067     dprob = 0.30
5068     if game.options & OPTION_PLAIN:
5069         dprob = 0.5
5070     r = rnd.real()
5071     if r > dprob:
5072         prouts(_("Sulu- \"Captain!  It's working!\""))
5073         skip(2)
5074         while len(game.enemies) > 0:
5075             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5076         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5077         if game.unwon() == 0:
5078             finish(FWON)
5079         if (game.options & OPTION_PLAIN) == 0:
5080             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5081             if rnd.withprob(0.05):
5082                 prout(_("   is still operational.\""))
5083             else:
5084                 prout(_("   has been rendered nonfunctional.\""))
5085                 game.damage[DDRAY] = 39.95
5086         return
5087     r = rnd.real()        # Pick failure method
5088     if r <= 0.30:
5089         prouts(_("Sulu- \"Captain!  It's working!\""))
5090         skip(1)
5091         prouts(_("***RED ALERT!  RED ALERT!"))
5092         skip(1)
5093         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5094         skip(1)
5095         prouts(_("***RED ALERT!  RED A*L********************************"))
5096         skip(1)
5097         stars()
5098         prouts(_("******************   KA-BOOM!!!!   *******************"))
5099         skip(1)
5100         kaboom()
5101         return
5102     if r <= 0.55:
5103         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5104         skip(1)
5105         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5106         skip(1)
5107         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5108         prout(_("  have apparently been transformed into strange mutations."))
5109         prout(_("  Vulcans do not seem to be affected."))
5110         skip(1)
5111         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5112         finish(FDRAY)
5113         return
5114     if r <= 0.75:
5115         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5116         skip(2)
5117         proutn(_("Spock-  \"I believe the word is"))
5118         prouts(_(" *ASTONISHING*"))
5119         prout(_(" Mr. Sulu."))
5120         for i in range(QUADSIZE):
5121             for j in range(QUADSIZE):
5122                 if game.quad[i][j] == '.':
5123                     game.quad[i][j] = '?'
5124         prout(_("  Captain, our quadrant is now infested with"))
5125         prouts(_(" - - - - - -  *THINGS*."))
5126         skip(1)
5127         prout(_("  I have no logical explanation.\""))
5128         return
5129     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5130     skip(1)
5131     prout(_("Scotty-  \"There are so many tribbles down here"))
5132     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5133     finish(FTRIBBLE)
5134     return
5135
5136 # Code from reports.c begins here
5137
5138 def attackreport(curt):
5139     "eport status of bases under attack."
5140     if not curt:
5141         if is_scheduled(FCDBAS):
5142             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5143             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5144         elif game.isatb == 1:
5145             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5146             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5147         else:
5148             prout(_("No Starbase is currently under attack."))
5149     else:
5150         if is_scheduled(FCDBAS):
5151             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5152         if game.isatb:
5153             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5154         clreol()
5155
5156 def report():
5157     # report on general game status
5158     scanner.chew()
5159     s1 = (game.thawed and _("thawed ")) or ""
5160     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5161     s3 = (None, _("novice"), _("fair"),
5162           _("good"), _("expert"), _("emeritus"))[game.skill]
5163     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5164     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5165         prout(_("No plaque is allowed."))
5166     if game.tourn:
5167         prout(_("This is tournament game %d.") % game.tourn)
5168     prout(_("Your secret password is \"%s\"") % game.passwd)
5169     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5170                                                       (game.inkling + game.incom + game.inscom)))
5171     if game.incom - len(game.state.kcmdr):
5172         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5173     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5174         prout(_(", but no Commanders."))
5175     else:
5176         prout(".")
5177     if game.skill > SKILL_FAIR:
5178         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5179     if len(game.state.baseq) != game.inbase:
5180         proutn(_("There "))
5181         if game.inbase-len(game.state.baseq)==1:
5182             proutn(_("has been 1 base"))
5183         else:
5184             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5185         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5186     else:
5187         prout(_("There are %d bases.") % game.inbase)
5188     if communicating() or game.iseenit:
5189         # Don't report this if not seen and
5190         # either the radio is dead or not at base!
5191         attackreport(False)
5192         game.iseenit = True
5193     if game.casual:
5194         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5195     if game.brigcapacity != game.brigfree:
5196         embriggened = game.brigcapacity-game.brigfree
5197         if embriggened == 1:
5198             prout(_("1 Klingon in brig"))
5199         else:
5200             prout(_("%d Klingons in brig.") %  embriggened)
5201         if game.kcaptured == 0:
5202             pass
5203         elif game.kcaptured == 1:
5204             prout(_("1 captured Klingon turned in to Starfleet."))
5205         else:
5206             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5207     if game.nhelp:
5208         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5209     if game.ship == 'E':
5210         proutn(_("You have "))
5211         if game.nprobes:
5212             proutn("%d" % (game.nprobes))
5213         else:
5214             proutn(_("no"))
5215         proutn(_(" deep space probe"))
5216         if game.nprobes!=1:
5217             proutn(_("s"))
5218         prout(".")
5219     if communicating() and is_scheduled(FDSPROB):
5220         if game.isarmed:
5221             proutn(_("An armed deep space probe is in "))
5222         else:
5223             proutn(_("A deep space probe is in "))
5224         prout("Quadrant %s." % game.probe.quadrant())
5225     if game.icrystl:
5226         if game.cryprob <= .05:
5227             prout(_("Dilithium crystals aboard ship... not yet used."))
5228         else:
5229             i=0
5230             ai = 0.05
5231             while game.cryprob > ai:
5232                 ai *= 2.0
5233                 i += 1
5234             prout(_("Dilithium crystals have been used %d time%s.") % \
5235                   (i, (_("s"), "")[i==1]))
5236     skip(1)
5237
5238 def lrscan(silent):
5239     "Long-range sensor scan."
5240     if damaged(DLRSENS):
5241         # Now allow base's sensors if docked
5242         if game.condition != "docked":
5243             if not silent:
5244                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5245             return
5246         if not silent:
5247             prout(_("Starbase's long-range scan"))
5248     elif not silent:
5249         prout(_("Long-range scan"))
5250     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5251         if not silent:
5252             proutn(" ")
5253         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5254             if not Coord(x, y).valid_quadrant():
5255                 if not silent:
5256                     proutn("  -1")
5257             else:
5258                 if not damaged(DRADIO):
5259                     game.state.galaxy[x][y].charted = True
5260                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5261                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5262                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5263                 if not silent and game.state.galaxy[x][y].supernova:
5264                     proutn(" ***")
5265                 elif not silent:
5266                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5267                     proutn(((3 - len(cn)) * '.') + cn)
5268         if not silent:
5269             prout(" ")
5270
5271 def damagereport():
5272     "Damage report."
5273     jdam = False
5274     scanner.chew()
5275     for i in range(NDEVICES):
5276         if damaged(i):
5277             if not jdam:
5278                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5279                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5280                 jdam = True
5281             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5282                                                game.damage[i]+0.05,
5283                                                DOCKFAC*game.damage[i]+0.005))
5284     if not jdam:
5285         prout(_("All devices functional."))
5286
5287 def rechart():
5288     "Update the chart in the Enterprise's computer from galaxy data."
5289     game.lastchart = game.state.date
5290     for i in range(GALSIZE):
5291         for j in range(GALSIZE):
5292             if game.state.galaxy[i][j].charted:
5293                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5294                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5295                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5296
5297 def chart():
5298     "Display the star chart."
5299     scanner.chew()
5300     if (game.options & OPTION_AUTOSCAN):
5301         lrscan(silent=True)
5302     if communicating():
5303         rechart()
5304     if game.lastchart < game.state.date and game.condition == "docked":
5305         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5306         rechart()
5307     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5308     if game.state.date > game.lastchart:
5309         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5310     prout("      1    2    3    4    5    6    7    8")
5311     for i in range(GALSIZE):
5312         proutn("%d |" % (i+1))
5313         for j in range(GALSIZE):
5314             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5315                 proutn("<")
5316             else:
5317                 proutn(" ")
5318             if game.state.galaxy[i][j].supernova:
5319                 show = "***"
5320             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5321                 show = ".1."
5322             elif game.state.galaxy[i][j].charted:
5323                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5324             else:
5325                 show = "..."
5326             proutn(show)
5327             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5328                 proutn(">")
5329             else:
5330                 proutn(" ")
5331         proutn("  |")
5332         if i<GALSIZE:
5333             skip(1)
5334
5335 def sectscan(goodScan, i, j):
5336     "Light up an individual dot in a sector."
5337     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5338         if game.quad[i][j] in ('E', 'F'):
5339             if game.iscloaked:
5340                 highvideo()
5341             textcolor({"green":GREEN,
5342                        "yellow":YELLOW,
5343                        "red":RED,
5344                        "docked":CYAN,
5345                        "dead":BROWN}[game.condition])
5346         else:
5347             textcolor({'?':LIGHTMAGENTA,
5348                        'K':LIGHTRED,
5349                        'S':LIGHTRED,
5350                        'C':LIGHTRED,
5351                        'R':LIGHTRED,
5352                        'T':LIGHTRED,
5353                       }.get(game.quad[i][j], DEFAULT))
5354         proutn("%c " % game.quad[i][j])
5355         textcolor(DEFAULT)
5356     else:
5357         proutn("- ")
5358
5359 def status(req=0):
5360     "Emit status report lines"
5361     if not req or req == 1:
5362         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5363                % (game.state.date, game.state.remtime))
5364     if not req or req == 2:
5365         if game.condition != "docked":
5366             newcnd()
5367         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5368                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5369         if game.iscloaked:
5370             prout(_(", CLOAKED"))
5371     if not req or req == 3:
5372         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5373     if not req or req == 4:
5374         if damaged(DLIFSUP):
5375             if game.condition == "docked":
5376                 s = _("DAMAGED, Base provides")
5377             else:
5378                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5379         else:
5380             s = _("ACTIVE")
5381         prstat(_("Life Support"), s)
5382     if not req or req == 5:
5383         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5384     if not req or req == 6:
5385         extra = ""
5386         if game.icrystl and (game.options & OPTION_SHOWME):
5387             extra = _(" (have crystals)")
5388         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5389     if not req or req == 7:
5390         prstat(_("Torpedoes"), "%d" % (game.torps))
5391     if not req or req == 8:
5392         if damaged(DSHIELD):
5393             s = _("DAMAGED,")
5394         elif game.shldup:
5395             s = _("UP,")
5396         else:
5397             s = _("DOWN,")
5398         data = _(" %d%% %.1f units") \
5399                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5400         prstat(_("Shields"), s+data)
5401     if not req or req == 9:
5402         prstat(_("Klingons Left"), "%d" % game.unwon())
5403     if not req or req == 10:
5404         if game.options & OPTION_WORLDS:
5405             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5406             if plnet and plnet.inhabited:
5407                 prstat(_("Major system"), plnet.name)
5408             else:
5409                 prout(_("Sector is uninhabited"))
5410     elif not req or req == 11:
5411         attackreport(not req)
5412
5413 def request():
5414     "Request specified status data, a historical relic from slow TTYs."
5415     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5416     while scanner.nexttok() == "IHEOL":
5417         proutn(_("Information desired? "))
5418     scanner.chew()
5419     if scanner.token in requests:
5420         status(requests.index(scanner.token))
5421     else:
5422         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5423         prout(("  date, condition, position, lsupport, warpfactor,"))
5424         prout(("  energy, torpedoes, shields, klingons, system, time."))
5425
5426 def srscan():
5427     "Short-range scan."
5428     goodScan=True
5429     if damaged(DSRSENS):
5430         # Allow base's sensors if docked
5431         if game.condition != "docked":
5432             prout(_("   S.R. SENSORS DAMAGED!"))
5433             goodScan=False
5434         else:
5435             prout(_("  [Using Base's sensors]"))
5436     else:
5437         prout(_("     Short-range scan"))
5438     if goodScan and communicating():
5439         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5440         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5441         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5442         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5443     prout("    1 2 3 4 5 6 7 8 9 10")
5444     if game.condition != "docked":
5445         newcnd()
5446     for i in range(QUADSIZE):
5447         proutn("%2d  " % (i+1))
5448         for j in range(QUADSIZE):
5449             sectscan(goodScan, i, j)
5450         skip(1)
5451
5452 def eta():
5453     "Use computer to get estimated time of arrival for a warp jump."
5454     w1 = Coord(); w2 = Coord()
5455     prompt = False
5456     if damaged(DCOMPTR):
5457         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5458         skip(1)
5459         return
5460     if scanner.nexttok() != "IHREAL":
5461         prompt = True
5462         scanner.chew()
5463         proutn(_("Destination quadrant and/or sector? "))
5464         if scanner.nexttok()!="IHREAL":
5465             huh()
5466             return
5467     w1.j = int(scanner.real-0.5)
5468     if scanner.nexttok() != "IHREAL":
5469         huh()
5470         return
5471     w1.i = int(scanner.real-0.5)
5472     if scanner.nexttok() == "IHREAL":
5473         w2.j = int(scanner.real-0.5)
5474         if scanner.nexttok() != "IHREAL":
5475             huh()
5476             return
5477         w2.i = int(scanner.real-0.5)
5478     else:
5479         if game.quadrant.j>w1.i:
5480             w2.i = 0
5481         else:
5482             w2.i=QUADSIZE-1
5483         if game.quadrant.i>w1.j:
5484             w2.j = 0
5485         else:
5486             w2.j=QUADSIZE-1
5487     if not w1.valid_quadrant() or not w2.valid_sector():
5488         huh()
5489         return
5490     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5491                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5492     wfl = False
5493     if prompt:
5494         prout(_("Answer \"no\" if you don't know the value:"))
5495     while True:
5496         scanner.chew()
5497         proutn(_("Time or arrival date? "))
5498         if scanner.nexttok()=="IHREAL":
5499             ttime = scanner.real
5500             if ttime > game.state.date:
5501                 ttime -= game.state.date # Actually a star date
5502             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5503             if ttime <= 1e-10 or twarp > 10:
5504                 prout(_("We'll never make it, sir."))
5505                 scanner.chew()
5506                 return
5507             if twarp < 1.0:
5508                 twarp = 1.0
5509             break
5510         scanner.chew()
5511         proutn(_("Warp factor? "))
5512         if scanner.nexttok()== "IHREAL":
5513             wfl = True
5514             twarp = scanner.real
5515             if twarp<1.0 or twarp > 10.0:
5516                 huh()
5517                 return
5518             break
5519         prout(_("Captain, certainly you can give me one of these."))
5520     while True:
5521         scanner.chew()
5522         ttime = (10.0*dist)/twarp**2
5523         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5524         if tpower >= game.energy:
5525             prout(_("Insufficient energy, sir."))
5526             if not game.shldup or tpower > game.energy*2.0:
5527                 if not wfl:
5528                     return
5529                 proutn(_("New warp factor to try? "))
5530                 if scanner.nexttok() == "IHREAL":
5531                     wfl = True
5532                     twarp = scanner.real
5533                     if twarp<1.0 or twarp > 10.0:
5534                         huh()
5535                         return
5536                     continue
5537                 else:
5538                     scanner.chew()
5539                     skip(1)
5540                     return
5541             prout(_("But if you lower your shields,"))
5542             proutn(_("remaining"))
5543             tpower /= 2
5544         else:
5545             proutn(_("Remaining"))
5546         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5547         if wfl:
5548             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5549         elif twarp==1.0:
5550             prout(_("Any warp speed is adequate."))
5551         else:
5552             prout(_("Minimum warp needed is %.2f,") % (twarp))
5553             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5554         if game.state.remtime < ttime:
5555             prout(_("Unfortunately, the Federation will be destroyed by then."))
5556         if twarp > 6.0:
5557             prout(_("You'll be taking risks at that speed, Captain"))
5558         if (game.isatb==1 and game.state.kscmdr == w1 and \
5559              scheduled(FSCDBAS)< ttime+game.state.date) or \
5560             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5561             prout(_("The starbase there will be destroyed by then."))
5562         proutn(_("New warp factor to try? "))
5563         if scanner.nexttok() == "IHREAL":
5564             wfl = True
5565             twarp = scanner.real
5566             if twarp<1.0 or twarp > 10.0:
5567                 huh()
5568                 return
5569         else:
5570             scanner.chew()
5571             skip(1)
5572             return
5573
5574 # Code from setup.c begins here
5575
5576 def prelim():
5577     "Issue a historically correct banner."
5578     skip(2)
5579     prout(_("-SUPER- STAR TREK"))
5580     skip(1)
5581 # From the FORTRAN original
5582 #    prout(_("Latest update-21 Sept 78"))
5583 #    skip(1)
5584
5585 def freeze(boss):
5586     "Save game."
5587     if boss:
5588         scanner.push("emsave.trk")
5589     key = scanner.nexttok()
5590     if key == "IHEOL":
5591         proutn(_("File name: "))
5592         key = scanner.nexttok()
5593     if key != "IHALPHA":
5594         huh()
5595         return
5596     if '.' not in scanner.token:
5597         scanner.token += ".trk"
5598     try:
5599         fp = open(scanner.token, "wb")
5600     except IOError:
5601         prout(_("Can't freeze game as file %s") % scanner.token)
5602         return
5603     pickle.dump(game, fp)
5604     fp.close()
5605     scanner.chew()
5606
5607 def thaw():
5608     "Retrieve saved game."
5609     global game
5610     game.passwd = None
5611     key = scanner.nexttok()
5612     if key == "IHEOL":
5613         proutn(_("File name: "))
5614         key = scanner.nexttok()
5615     if key != "IHALPHA":
5616         huh()
5617         return True
5618     if '.' not in scanner.token:
5619         scanner.token += ".trk"
5620     try:
5621         fp = open(scanner.token, "rb")
5622     except IOError:
5623         prout(_("Can't thaw game in %s") % scanner.token)
5624         return True
5625     game = pickle.load(fp)
5626     fp.close()
5627     scanner.chew()
5628     return False
5629
5630 # I used <http://www.memory-alpha.org> to find planets
5631 # with references in ST:TOS.  Earth and the Alpha Centauri
5632 # Colony have been omitted.
5633 #
5634 # Some planets marked Class G and P here will be displayed as class M
5635 # because of the way planets are generated. This is a known bug.
5636 systnames = (
5637     # Federation Worlds
5638     _("Andoria (Fesoan)"),        # several episodes
5639     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5640     _("Vulcan (T'Khasi)"),        # many episodes
5641     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5642     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5643     _("Ardana"),                # TOS: "The Cloud Minders"
5644     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5645     _("Gideon"),                # TOS: "The Mark of Gideon"
5646     _("Aldebaran III"),                # TOS: "The Deadly Years"
5647     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5648     _("Altair IV"),                # TOS: "Amok Time
5649     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5650     _("Benecia"),                # TOS: "The Conscience of the King"
5651     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5652     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5653     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5654     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5655     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5656     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5657     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5658     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5659     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5660     _("Janus IV"),                # TOS: "The Devil in the Dark"
5661     _("Makus III"),                # TOS: "The Galileo Seven"
5662     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5663     _("Omega IV"),                # TOS: "The Omega Glory"
5664     _("Regulus V"),                # TOS: "Amok Time
5665     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5666     # Worlds from BSD Trek
5667     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5668     _("Beta III"),                # TOS: "The Return of the Archons"
5669     _("Triacus"),                # TOS: "And the Children Shall Lead",
5670     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5671     #        # Others
5672     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5673     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5674     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5675     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5676     #    _("Tiburon"),                # TOS: "The Way to Eden"
5677     #    _("Merak II"),                # TOS: "The Cloud Minders"
5678     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5679     #    _("Iotia"),                # TOS: "A Piece of the Action"
5680 )
5681
5682 device = (
5683     _("S. R. Sensors"), \
5684     _("L. R. Sensors"), \
5685     _("Phasers"), \
5686     _("Photon Tubes"), \
5687     _("Life Support"), \
5688     _("Warp Engines"), \
5689     _("Impulse Engines"), \
5690     _("Shields"), \
5691     _("Subspace Radio"), \
5692     _("Shuttle Craft"), \
5693     _("Computer"), \
5694     _("Navigation System"), \
5695     _("Transporter"), \
5696     _("Shield Control"), \
5697     _("Death Ray"), \
5698     _("D. S. Probe"), \
5699     _("Cloaking Device"), \
5700 )
5701
5702 def setup():
5703     "Prepare to play, set up cosmos."
5704     w = Coord()
5705     #  Decide how many of everything
5706     if choose():
5707         return # frozen game
5708     # Prepare the Enterprise
5709     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5710     game.ship = 'E'
5711     game.state.crew = FULLCREW
5712     game.energy = game.inenrg = 5000.0
5713     game.shield = game.inshld = 2500.0
5714     game.inlsr = 4.0
5715     game.lsupres = 4.0
5716     game.quadrant = randplace(GALSIZE)
5717     game.sector = randplace(QUADSIZE)
5718     game.torps = game.intorps = 10
5719     game.nprobes = rnd.integer(2, 5)
5720     game.warpfac = 5.0
5721     for i in range(NDEVICES):
5722         game.damage[i] = 0.0
5723     # Set up assorted game parameters
5724     game.battle = Coord()
5725     game.state.date = game.indate = 100.0 * rnd.real(20, 51)
5726     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5727     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5728     game.isatb = game.state.nplankl = 0
5729     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5730     game.iscraft = "onship"
5731     game.landed = False
5732     game.alive = True
5733
5734     # the galaxy
5735     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5736     # the starchart
5737     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5738
5739     game.state.planets = []      # Planet information
5740     game.state.baseq = []      # Base quadrant coordinates
5741     game.state.kcmdr = []      # Commander quadrant coordinates
5742     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5743
5744     # Starchart is functional but we've never seen it
5745     game.lastchart = FOREVER
5746     # Put stars in the galaxy
5747     game.instar = 0
5748     for i in range(GALSIZE):
5749         for j in range(GALSIZE):
5750             # Can't have more stars per quadrant than fit in one decimal digit,
5751             # if we do the chart representation will break.
5752             k = rnd.integer(1, min(10, QUADSIZE**2/10))
5753             game.instar += k
5754             game.state.galaxy[i][j].stars = k
5755     # Locate star bases in galaxy
5756     if game.idebug:
5757         prout("=== Allocating %d bases" % game.inbase)
5758     for i in range(game.inbase):
5759         while True:
5760             while True:
5761                 w = randplace(GALSIZE)
5762                 if not game.state.galaxy[w.i][w.j].starbase:
5763                     break
5764             contflag = False
5765             # C version: for (j = i-1; j > 0; j--)
5766             # so it did them in the opposite order.
5767             for j in range(1, i):
5768                 # Improved placement algorithm to spread out bases
5769                 distq = (w - game.state.baseq[j]).distance()
5770                 if distq < 6.0*(BASEMAX+1-game.inbase) and rnd.withprob(0.75):
5771                     contflag = True
5772                     if game.idebug:
5773                         prout("=== Abandoning base #%d at %s" % (i, w))
5774                     break
5775                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5776                     if game.idebug:
5777                         prout("=== Saving base #%d, close to #%d" % (i, j))
5778             if not contflag:
5779                 break
5780         if game.idebug:
5781             prout("=== Placing base #%d in quadrant %s" % (i, w))
5782         game.state.baseq.append(w)
5783         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5784     # Position ordinary Klingon Battle Cruisers
5785     krem = game.inkling
5786     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5787     if klumper > MAXKLQUAD:
5788         klumper = MAXKLQUAD
5789     while True:
5790         r = rnd.real()
5791         klump = int((1.0 - r*r)*klumper)
5792         if klump > krem:
5793             klump = krem
5794         krem -= klump
5795         while True:
5796             w = randplace(GALSIZE)
5797             if not game.state.galaxy[w.i][w.j].supernova and \
5798                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5799                 break
5800         game.state.galaxy[w.i][w.j].klingons += klump
5801         if krem <= 0:
5802             break
5803     # Position Klingon Commander Ships
5804     for i in range(game.incom):
5805         while True:
5806             w = randplace(GALSIZE)
5807             if not welcoming(w) or w in game.state.kcmdr:
5808                 continue
5809             if (game.state.galaxy[w.i][w.j].klingons or rnd.withprob(0.25)):
5810                 break
5811         game.state.galaxy[w.i][w.j].klingons += 1
5812         game.state.kcmdr.append(w)
5813     # Locate planets in galaxy
5814     for i in range(game.inplan):
5815         while True:
5816             w = randplace(GALSIZE)
5817             if game.state.galaxy[w.i][w.j].planet is None:
5818                 break
5819         new = Planet()
5820         new.quadrant = w
5821         new.crystals = "absent"
5822         if (game.options & OPTION_WORLDS) and i < NINHAB:
5823             new.pclass = "M"        # All inhabited planets are class M
5824             new.crystals = "absent"
5825             new.known = "known"
5826             new.name = systnames[i]
5827             new.inhabited = True
5828         else:
5829             new.pclass = ("M", "N", "O")[rnd.integer(0, 3)]
5830             if rnd.withprob(0.33):
5831                 new.crystals = "present"
5832             new.known = "unknown"
5833             new.inhabited = False
5834         game.state.galaxy[w.i][w.j].planet = new
5835         game.state.planets.append(new)
5836     # Locate Romulans
5837     for i in range(game.state.nromrem):
5838         w = randplace(GALSIZE)
5839         game.state.galaxy[w.i][w.j].romulans += 1
5840     # Place the Super-Commander if needed
5841     if game.state.nscrem > 0:
5842         while True:
5843             w = randplace(GALSIZE)
5844             if welcoming(w):
5845                 break
5846         game.state.kscmdr = w
5847         game.state.galaxy[w.i][w.j].klingons += 1
5848     # Initialize times for extraneous events
5849     schedule(FSNOVA, expran(0.5 * game.intime))
5850     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5851     schedule(FSNAP, rnd.real(1.0, 2.0)) # Force an early snapshot
5852     schedule(FBATTAK, expran(0.3*game.intime))
5853     unschedule(FCDBAS)
5854     if game.state.nscrem:
5855         schedule(FSCMOVE, 0.2777)
5856     else:
5857         unschedule(FSCMOVE)
5858     unschedule(FSCDBAS)
5859     unschedule(FDSPROB)
5860     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5861         schedule(FDISTR, expran(1.0 + game.intime))
5862     else:
5863         unschedule(FDISTR)
5864     unschedule(FENSLV)
5865     unschedule(FREPRO)
5866     # Place thing (in tournament game, we don't want one!)
5867     # New in SST2K: never place the Thing near a starbase.
5868     # This makes sense and avoids a special case in the old code.
5869     global thing
5870     if game.tourn is None:
5871         while True:
5872             thing = randplace(GALSIZE)
5873             if thing not in game.state.baseq:
5874                 break
5875     skip(2)
5876     game.state.snap = False
5877     if game.skill == SKILL_NOVICE:
5878         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5879         prout(_("a deadly Klingon invasion force. As captain of the United"))
5880         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5881         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5882         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5883         prout(_("your mission.  As you proceed you may be given more time."))
5884         skip(1)
5885         prout(_("You will have %d supporting starbases.") % (game.inbase))
5886         proutn(_("Starbase locations-  "))
5887     else:
5888         prout(_("Stardate %d.") % int(game.state.date))
5889         skip(1)
5890         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5891         prout(_("An unknown number of Romulans."))
5892         if game.state.nscrem:
5893             prout(_("And one (GULP) Super-Commander."))
5894         prout(_("%d stardates.") % int(game.intime))
5895         proutn(_("%d starbases in ") % game.inbase)
5896     for i in range(game.inbase):
5897         proutn(repr(game.state.baseq[i]))
5898         proutn("  ")
5899     skip(2)
5900     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5901     proutn(_(" Sector %s") % game.sector)
5902     skip(2)
5903     prout(_("Good Luck!"))
5904     if game.state.nscrem:
5905         prout(_("  YOU'LL NEED IT."))
5906     waitfor()
5907     clrscr()
5908     setwnd(message_window)
5909     newqad()
5910     if len(game.enemies) - (thing == game.quadrant) - (game.tholian is not None):
5911         game.shldup = True
5912     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5913         attack(torps_ok=False)
5914
5915 def choose():
5916     "Choose your game type."
5917     while True:
5918         game.tourn = game.length = 0
5919         game.thawed = False
5920         game.skill = SKILL_NONE
5921         # Do not chew here, we want to use command-line tokens
5922         if not scanner.inqueue: # Can start with command line options
5923             proutn(_("Would you like a regular, tournament, or saved game? "))
5924         scanner.nexttok()
5925         if scanner.sees("tournament"):
5926             while scanner.nexttok() == "IHEOL":
5927                 proutn(_("Type in tournament number-"))
5928             if scanner.real == 0:
5929                 scanner.chew()
5930                 continue # We don't want a blank entry
5931             game.tourn = int(round(scanner.real))
5932             rnd.seed(scanner.real)
5933             if logfp:
5934                 logfp.write("# rnd.seed(%d)\n" % scanner.real)
5935             break
5936         if scanner.sees("saved") or scanner.sees("frozen"):
5937             if thaw():
5938                 continue
5939             scanner.chew()
5940             if game.passwd is None:
5941                 continue
5942             if not game.alldone:
5943                 game.thawed = True # No plaque if not finished
5944             report()
5945             waitfor()
5946             return True
5947         if scanner.sees("regular"):
5948             break
5949         proutn(_("What is \"%s\"? ") % scanner.token)
5950         scanner.chew()
5951     while game.length==0 or game.skill==SKILL_NONE:
5952         if scanner.nexttok() == "IHALPHA":
5953             if scanner.sees("short"):
5954                 game.length = 1
5955             elif scanner.sees("medium"):
5956                 game.length = 2
5957             elif scanner.sees("long"):
5958                 game.length = 4
5959             elif scanner.sees("novice"):
5960                 game.skill = SKILL_NOVICE
5961             elif scanner.sees("fair"):
5962                 game.skill = SKILL_FAIR
5963             elif scanner.sees("good"):
5964                 game.skill = SKILL_GOOD
5965             elif scanner.sees("expert"):
5966                 game.skill = SKILL_EXPERT
5967             elif scanner.sees("emeritus"):
5968                 game.skill = SKILL_EMERITUS
5969             else:
5970                 proutn(_("What is \""))
5971                 proutn(scanner.token)
5972                 prout("\"?")
5973         else:
5974             scanner.chew()
5975             if game.length==0:
5976                 proutn(_("Would you like a Short, Medium, or Long game? "))
5977             elif game.skill == SKILL_NONE:
5978                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5979     # Choose game options -- added by ESR for SST2K
5980     if scanner.nexttok() != "IHALPHA":
5981         scanner.chew()
5982         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5983         scanner.nexttok()
5984     if scanner.sees("plain"):
5985         # Approximates the UT FORTRAN version.
5986         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE | OPTION_CLOAK)
5987         game.options |= OPTION_PLAIN
5988     elif scanner.sees("almy"):
5989         # Approximates Tom Almy's version.
5990         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR)
5991         game.options |= OPTION_ALMY
5992     elif scanner.sees("fancy") or scanner.sees("\n"):
5993         pass
5994     elif len(scanner.token):
5995         proutn(_("What is \"%s\"?") % scanner.token)
5996     setpassword()
5997     if game.passwd == "debug":
5998         game.idebug = True
5999         prout("=== Debug mode enabled.")
6000     # Use parameters to generate initial values of things
6001     game.damfac = 0.5 * game.skill
6002     game.inbase = rnd.integer(BASEMIN, BASEMAX+1)
6003     game.inplan = 0
6004     if game.options & OPTION_PLANETS:
6005         game.inplan += rnd.integer(MAXUNINHAB/2, MAXUNINHAB+1)
6006     if game.options & OPTION_WORLDS:
6007         game.inplan += int(NINHAB)
6008     game.state.nromrem = game.inrom = rnd.integer(2 * game.skill)
6009     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
6010     game.state.remtime = 7.0 * game.length
6011     game.intime = game.state.remtime
6012     game.inkling = int(2.0*game.intime*((game.skill+1 - 2*rnd.real())*game.skill*0.1+.15))
6013     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*rnd.real()))
6014     game.state.remres = (game.inkling+4*game.incom)*game.intime
6015     game.inresor = game.state.remres
6016     if game.inkling > 50:
6017         game.inbase += 1
6018     return False
6019
6020 def dropin(iquad=None):
6021     "Drop a feature on a random dot in the current quadrant."
6022     while True:
6023         w = randplace(QUADSIZE)
6024         if game.quad[w.i][w.j] == '.':
6025             break
6026     if iquad is not None:
6027         game.quad[w.i][w.j] = iquad
6028     return w
6029
6030 def newcnd():
6031     "Update our alert status."
6032     game.condition = "green"
6033     if game.energy < 1000.0:
6034         game.condition = "yellow"
6035     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
6036         game.condition = "red"
6037     if not game.alive:
6038         game.condition="dead"
6039
6040 def newkling():
6041     "Drop new Klingon into current quadrant."
6042     return Enemy('K', loc=dropin(), power=rnd.real(300,450)+25.0*game.skill)
6043
6044 def sortenemies():
6045     "Sort enemies by distance so 'nearest' is meaningful."
6046     game.enemies.sort(key=lambda x: x.kdist)
6047
6048 def newqad():
6049     "Set up a new state of quadrant, for when we enter or re-enter it."
6050     game.justin = True
6051     game.iplnet = None
6052     game.neutz = game.inorbit = game.landed = False
6053     game.ientesc = game.iseenit = game.isviolreported = False
6054     # Create a blank quadrant
6055     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6056     if game.iscate:
6057         # Attempt to escape Super-commander, so tbeam back!
6058         game.iscate = False
6059         game.ientesc = True
6060     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6061     # cope with supernova
6062     if q.supernova:
6063         return
6064     game.klhere = q.klingons
6065     game.irhere = q.romulans
6066     # Position Starship
6067     game.quad[game.sector.i][game.sector.j] = game.ship
6068     game.enemies = []
6069     if q.klingons:
6070         # Position ordinary Klingons
6071         for _i in range(game.klhere):
6072             newkling()
6073         # If we need a commander, promote a Klingon
6074         for cmdr in game.state.kcmdr:
6075             if cmdr == game.quadrant:
6076                 e = game.enemies[game.klhere-1]
6077                 game.quad[e.location.i][e.location.j] = 'C'
6078                 e.power = rnd.real(950,1350) + 50.0*game.skill
6079                 break
6080         # If we need a super-commander, promote a Klingon
6081         if game.quadrant == game.state.kscmdr:
6082             e = game.enemies[0]
6083             game.quad[e.location.i][e.location.j] = 'S'
6084             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6085             game.iscate = (game.remkl() > 1)
6086     # Put in Romulans if needed
6087     for _i in range(q.romulans):
6088         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6089     # If quadrant needs a starbase, put it in
6090     if q.starbase:
6091         game.base = dropin('B')
6092     # If quadrant needs a planet, put it in
6093     if q.planet:
6094         game.iplnet = q.planet
6095         if not q.planet.inhabited:
6096             game.plnet = dropin('P')
6097         else:
6098             game.plnet = dropin('@')
6099     # Check for condition
6100     newcnd()
6101     # Check for RNZ
6102     if game.irhere > 0 and game.klhere == 0:
6103         game.neutz = True
6104         if not damaged(DRADIO):
6105             skip(1)
6106             prout(_("LT. Uhura- \"Captain, an urgent message."))
6107             prout(_("  I'll put it on audio.\"  CLICK"))
6108             skip(1)
6109             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6110             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6111     # Put in THING if needed
6112     if thing == game.quadrant:
6113         Enemy(etype='?', loc=dropin(),
6114               power=rnd.real(6000,6500.0)+250.0*game.skill)
6115         if not damaged(DSRSENS):
6116             skip(1)
6117             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6118             prout(_("    Please examine your short-range scan.\""))
6119     # Decide if quadrant needs a Tholian; lighten up if skill is low
6120     if game.options & OPTION_THOLIAN:
6121         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6122             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6123             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6124             w = Coord()
6125             while True:
6126                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6127                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6128                 if game.quad[w.i][w.j] == '.':
6129                     break
6130             game.tholian = Enemy(etype='T', loc=w,
6131                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6132             # Reserve unoccupied corners
6133             if game.quad[0][0]=='.':
6134                 game.quad[0][0] = 'X'
6135             if game.quad[0][QUADSIZE-1]=='.':
6136                 game.quad[0][QUADSIZE-1] = 'X'
6137             if game.quad[QUADSIZE-1][0]=='.':
6138                 game.quad[QUADSIZE-1][0] = 'X'
6139             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6140                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6141     sortenemies()
6142     # And finally the stars
6143     for _i in range(q.stars):
6144         dropin('*')
6145     # Put in a few black holes
6146     for _i in range(1, 3+1):
6147         if rnd.withprob(0.5):
6148             dropin(' ')
6149     # Take out X's in corners if Tholian present
6150     if game.tholian:
6151         if game.quad[0][0]=='X':
6152             game.quad[0][0] = '.'
6153         if game.quad[0][QUADSIZE-1]=='X':
6154             game.quad[0][QUADSIZE-1] = '.'
6155         if game.quad[QUADSIZE-1][0]=='X':
6156             game.quad[QUADSIZE-1][0] = '.'
6157         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6158             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6159     # This should guarantee that replay games don't lose info about the chart
6160     if (game.options & OPTION_AUTOSCAN) or replayfp:
6161         lrscan(silent=True)
6162
6163 def setpassword():
6164     "Set the self-destruct password."
6165     if game.options & OPTION_PLAIN:
6166         while True:
6167             scanner.chew()
6168             proutn(_("Please type in a secret password- "))
6169             scanner.nexttok()
6170             game.passwd = scanner.token
6171             if game.passwd is not None:
6172                 break
6173     else:
6174         game.passwd = ""
6175         game.passwd += chr(ord('a')+rnd.integer(26))
6176         game.passwd += chr(ord('a')+rnd.integer(26))
6177         game.passwd += chr(ord('a')+rnd.integer(26))
6178
6179 # Code from sst.c begins here
6180
6181 commands = [
6182     ("SRSCAN",           OPTION_TTY),
6183     ("STATUS",           OPTION_TTY),
6184     ("REQUEST",          OPTION_TTY),
6185     ("LRSCAN",           OPTION_TTY),
6186     ("PHASERS",          0),
6187     ("TORPEDO",          0),
6188     ("PHOTONS",          0),
6189     ("MOVE",             0),
6190     ("SHIELDS",          0),
6191     ("DOCK",             0),
6192     ("DAMAGES",          0),
6193     ("CHART",            0),
6194     ("IMPULSE",          0),
6195     ("REST",             0),
6196     ("WARP",             0),
6197     ("SENSORS",          OPTION_PLANETS),
6198     ("ORBIT",            OPTION_PLANETS),
6199     ("TRANSPORT",        OPTION_PLANETS),
6200     ("MINE",             OPTION_PLANETS),
6201     ("CRYSTALS",         OPTION_PLANETS),
6202     ("SHUTTLE",          OPTION_PLANETS),
6203     ("PLANETS",          OPTION_PLANETS),
6204     ("REPORT",           0),
6205     ("COMPUTER",         0),
6206     ("COMMANDS",         0),
6207     ("EMEXIT",           0),
6208     ("PROBE",            OPTION_PROBE),
6209     ("SAVE",             0),
6210     ("FREEZE",           0),        # Synonym for SAVE
6211     ("ABANDON",          0),
6212     ("DESTRUCT",         0),
6213     ("DEATHRAY",         0),
6214     ("CAPTURE",          OPTION_CAPTURE),
6215     ("CLOAK",            OPTION_CLOAK),
6216     ("DEBUG",            0),
6217     ("MAYDAY",           0),
6218     ("SOS",              0),        # Synonym for MAYDAY
6219     ("CALL",             0),        # Synonym for MAYDAY
6220     ("QUIT",             0),
6221     ("HELP",             0),
6222     ("SCORE",            0),
6223     ("CURSES",            0),
6224     ("",                 0),
6225 ]
6226
6227 def listCommands():
6228     "Generate a list of legal commands."
6229     prout(_("LEGAL COMMANDS ARE:"))
6230     emitted = 0
6231     for (key, opt) in commands:
6232         if not opt or (opt & game.options):
6233             proutn("%-12s " % key)
6234             emitted += 1
6235             if emitted % 5 == 4:
6236                 skip(1)
6237     skip(1)
6238
6239 def helpme():
6240     "Browse on-line help."
6241     key = scanner.nexttok()
6242     while True:
6243         if key == "IHEOL":
6244             setwnd(prompt_window)
6245             proutn(_("Help on what command? "))
6246             key = scanner.nexttok()
6247         setwnd(message_window)
6248         if key == "IHEOL":
6249             return
6250         cmds = [x[0] for x in commands]
6251         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6252             break
6253         skip(1)
6254         listCommands()
6255         key = "IHEOL"
6256         scanner.chew()
6257         skip(1)
6258     cmd = scanner.token.upper()
6259     for directory in docpath:
6260         try:
6261             fp = open(os.path.join(directory, "sst.doc"), "r")
6262             break
6263         except IOError:
6264             pass
6265     else:
6266         prout(_("Spock-  \"Captain, that information is missing from the"))
6267         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6268         proutn(_("   in these directories: %s") % ":".join(docpath))
6269         prout(".\"")
6270         # This used to continue: "You need to find SST.DOC and put
6271         # it in the current directory."
6272         return
6273     while True:
6274         linebuf = fp.readline()
6275         if linebuf == '':
6276             prout(_("Spock- \"Captain, there is no information on that command.\""))
6277             fp.close()
6278             return
6279         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6280             linebuf = linebuf[3:].strip()
6281             if cmd.upper() == linebuf:
6282                 break
6283     skip(1)
6284     prout(_("Spock- \"Captain, I've found the following information:\""))
6285     skip(1)
6286     while True:
6287         linebuf = fp.readline()
6288         if "******" in linebuf:
6289             break
6290         proutn(linebuf)
6291     fp.close()
6292
6293 def makemoves():
6294     "Command-interpretation loop."
6295     def checkviol():
6296         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6297             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6298             game.ncviol += 1
6299             game.isviolreported = True
6300     while True:         # command loop
6301         drawmaps(1)
6302         while True:        # get a command
6303             hitme = False
6304             game.optime = game.justin = False
6305             scanner.chew()
6306             setwnd(prompt_window)
6307             clrscr()
6308             proutn("COMMAND> ")
6309             if scanner.nexttok() == "IHEOL":
6310                 if game.options & OPTION_CURSES:
6311                     makechart()
6312                 continue
6313             elif scanner.token == "":
6314                 continue
6315             game.ididit = False
6316             clrscr()
6317             setwnd(message_window)
6318             clrscr()
6319             abandon_passed = False
6320             cmd = ""    # Force cmd to persist after loop
6321             opt = 0     # Force opt to persist after loop
6322             for (cmd, opt) in commands:
6323                 # commands after ABANDON cannot be abbreviated
6324                 if cmd == "ABANDON":
6325                     abandon_passed = True
6326                 if cmd == scanner.token.upper() or (not abandon_passed \
6327                         and cmd.startswith(scanner.token.upper())):
6328                     break
6329             if cmd == "":
6330                 listCommands()
6331                 continue
6332             elif opt and not (opt & game.options):
6333                 huh()
6334             else:
6335                 break
6336         if game.options & OPTION_CURSES:
6337             prout("COMMAND> %s" % cmd)
6338         if cmd == "SRSCAN":                # srscan
6339             srscan()
6340         elif cmd == "STATUS":                # status
6341             status()
6342         elif cmd == "REQUEST":                # status request
6343             request()
6344         elif cmd == "LRSCAN":                # long range scan
6345             lrscan(silent=False)
6346         elif cmd == "PHASERS":                # phasers
6347             phasers()
6348             if game.ididit:
6349                 checkviol()
6350                 hitme = True
6351         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6352             torps()
6353             if game.ididit:
6354                 checkviol()
6355                 hitme = True
6356         elif cmd == "MOVE":                # move under warp
6357             warp(wcourse=None, involuntary=False)
6358         elif cmd == "SHIELDS":                # shields
6359             doshield(shraise=False)
6360             if game.ididit:
6361                 hitme = True
6362                 game.shldchg = False
6363         elif cmd == "DOCK":                # dock at starbase
6364             dock(True)
6365             if game.ididit:
6366                 attack(torps_ok=False)
6367         elif cmd == "DAMAGES":                # damage reports
6368             damagereport()
6369         elif cmd == "CHART":                # chart
6370             makechart()
6371         elif cmd == "IMPULSE":                # impulse
6372             impulse()
6373         elif cmd == "REST":                # rest
6374             wait()
6375             if game.ididit:
6376                 hitme = True
6377         elif cmd == "WARP":                # warp
6378             setwarp()
6379         elif cmd == "SENSORS":                # sensors
6380             sensor()
6381         elif cmd == "ORBIT":                # orbit
6382             orbit()
6383             if game.ididit:
6384                 hitme = True
6385         elif cmd == "TRANSPORT":                # transport "beam"
6386             beam()
6387         elif cmd == "MINE":                # mine
6388             mine()
6389             if game.ididit:
6390                 hitme = True
6391         elif cmd == "CRYSTALS":                # crystals
6392             usecrystals()
6393             if game.ididit:
6394                 hitme = True
6395         elif cmd == "SHUTTLE":                # shuttle
6396             shuttle()
6397             if game.ididit:
6398                 hitme = True
6399         elif cmd == "PLANETS":                # Planet list
6400             survey()
6401         elif cmd == "REPORT":                # Game Report
6402             report()
6403         elif cmd == "COMPUTER":                # use COMPUTER!
6404             eta()
6405         elif cmd == "COMMANDS":
6406             listCommands()
6407         elif cmd == "EMEXIT":                # Emergency exit
6408             clrscr()                        # Hide screen
6409             freeze(True)                # forced save
6410             raise SystemExit(1)                # And quick exit
6411         elif cmd == "PROBE":
6412             probe()                        # Launch probe
6413             if game.ididit:
6414                 hitme = True
6415         elif cmd == "ABANDON":                # Abandon Ship
6416             abandon()
6417         elif cmd == "DESTRUCT":                # Self Destruct
6418             selfdestruct()
6419         elif cmd == "SAVE":                # Save Game
6420             freeze(False)
6421             clrscr()
6422             if game.skill > SKILL_GOOD:
6423                 prout(_("WARNING--Saved games produce no plaques!"))
6424         elif cmd == "DEATHRAY":                # Try a desparation measure
6425             deathray()
6426             if game.ididit:
6427                 hitme = True
6428         elif cmd == "CAPTURE":
6429             capture()
6430         elif cmd == "CLOAK":
6431             cloak()
6432         elif cmd == "DEBUGCMD":                # What do we want for debug???
6433             debugme()
6434         elif cmd == "MAYDAY":                # Call for help
6435             mayday()
6436             if game.ididit:
6437                 hitme = True
6438         elif cmd == "QUIT":
6439             game.alldone = True                # quit the game
6440         elif cmd == "HELP":
6441             helpme()                        # get help
6442         elif cmd == "SCORE":
6443             score()                         # see current score
6444         elif cmd == "CURSES":
6445             game.options |= (OPTION_CURSES | OPTION_COLOR)
6446             iostart()
6447         while True:
6448             if game.alldone:
6449                 break                # Game has ended
6450             if game.optime != 0.0:
6451                 events()
6452                 if game.alldone:
6453                     break        # Events did us in
6454             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6455                 atover(False)
6456                 continue
6457             if hitme and not game.justin:
6458                 attack(torps_ok=True)
6459                 if game.alldone:
6460                     break
6461                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6462                     atover(False)
6463                     hitme = True
6464                     continue
6465             break
6466         if game.alldone:
6467             break
6468     if game.idebug:
6469         prout("=== Ending")
6470
6471 def cramen(ch):
6472     "Emit the name of an enemy or feature."
6473     if   ch == 'R': s = _("Romulan")
6474     elif ch == 'K': s = _("Klingon")
6475     elif ch == 'C': s = _("Commander")
6476     elif ch == 'S': s = _("Super-commander")
6477     elif ch == '*': s = _("Star")
6478     elif ch == 'P': s = _("Planet")
6479     elif ch == 'B': s = _("Starbase")
6480     elif ch == ' ': s = _("Black hole")
6481     elif ch == 'T': s = _("Tholian")
6482     elif ch == '#': s = _("Tholian web")
6483     elif ch == '?': s = _("Stranger")
6484     elif ch == '@': s = _("Inhabited World")
6485     else: s = "Unknown??"
6486     return s
6487
6488 def crmena(loud, enemy, loctype, w):
6489     "Emit the name of an enemy and his location."
6490     buf = ""
6491     if loud:
6492         buf += "***"
6493     buf += cramen(enemy) + _(" at ")
6494     if loctype == "quadrant":
6495         buf += _("Quadrant ")
6496     elif loctype == "sector":
6497         buf += _("Sector ")
6498     return buf + repr(w)
6499
6500 def crmshp():
6501     "Emit our ship name."
6502     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6503
6504 def stars():
6505     "Emit a line of stars"
6506     prouts("******************************************************")
6507     skip(1)
6508
6509 def expran(avrage):
6510     return -avrage*math.log(1e-7 + rnd.real())
6511
6512 def randplace(size):
6513     "Choose a random location."
6514     w = Coord()
6515     w.i = rnd.integer(size)
6516     w.j = rnd.integer(size)
6517     return w
6518
6519 class sstscanner:
6520     def __init__(self):
6521         self.type = None
6522         self.token = None
6523         self.real = 0.0
6524         self.inqueue = []
6525     def nexttok(self):
6526         # Get a token from the user
6527         self.real = 0.0
6528         self.token = ''
6529         # Fill the token quue if nothing here
6530         while not self.inqueue:
6531             sline = cgetline()
6532             if curwnd==prompt_window:
6533                 clrscr()
6534                 setwnd(message_window)
6535                 clrscr()
6536             if sline == '':
6537                 return None
6538             if not sline:
6539                 continue
6540             else:
6541                 self.inqueue = sline.lstrip().split() + ["\n"]
6542         # From here on in it's all looking at the queue
6543         self.token = self.inqueue.pop(0)
6544         if self.token == "\n":
6545             self.type = "IHEOL"
6546             return "IHEOL"
6547         try:
6548             self.real = float(self.token)
6549             self.type = "IHREAL"
6550             return "IHREAL"
6551         except ValueError:
6552             pass
6553         # Treat as alpha
6554         self.token = self.token.lower()
6555         self.type = "IHALPHA"
6556         self.real = None
6557         return "IHALPHA"
6558     def append(self, tok):
6559         self.inqueue.append(tok)
6560     def push(self, tok):
6561         self.inqueue.insert(0, tok)
6562     def waiting(self):
6563         return self.inqueue
6564     def chew(self):
6565         # Demand input for next scan
6566         self.inqueue = []
6567         self.real = self.token = None
6568     def sees(self, s):
6569         # compares s to item and returns true if it matches to the length of s
6570         return s.startswith(self.token)
6571     def int(self):
6572         # Round token value to nearest integer
6573         return int(round(self.real))
6574     def getcoord(self):
6575         s = Coord()
6576         self.nexttok()
6577         if self.type != "IHREAL":
6578             huh()
6579             return None
6580         s.i = self.int()-1
6581         self.nexttok()
6582         if self.type != "IHREAL":
6583             huh()
6584             return None
6585         s.j = self.int()-1
6586         return s
6587     def __repr__(self):
6588         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6589
6590 def ja():
6591     "Yes-or-no confirmation."
6592     scanner.chew()
6593     while True:
6594         scanner.nexttok()
6595         if scanner.token == 'y':
6596             return True
6597         if scanner.token == 'n':
6598             return False
6599         scanner.chew()
6600         proutn(_("Please answer with \"y\" or \"n\": "))
6601
6602 def huh():
6603     "Complain about unparseable input."
6604     scanner.chew()
6605     skip(1)
6606     prout(_("Beg your pardon, Captain?"))
6607
6608 def debugme():
6609     "Access to the internals for debugging."
6610     proutn("Reset levels? ")
6611     if ja():
6612         if game.energy < game.inenrg:
6613             game.energy = game.inenrg
6614         game.shield = game.inshld
6615         game.torps = game.intorps
6616         game.lsupres = game.inlsr
6617     proutn("Reset damage? ")
6618     if ja():
6619         for i in range(NDEVICES):
6620             if game.damage[i] > 0.0:
6621                 game.damage[i] = 0.0
6622     proutn("Toggle debug flag? ")
6623     if ja():
6624         game.idebug = not game.idebug
6625         if game.idebug:
6626             prout("Debug output ON")
6627         else:
6628             prout("Debug output OFF")
6629     proutn("Cause selective damage? ")
6630     if ja():
6631         for i in range(NDEVICES):
6632             proutn("Kill %s?" % device[i])
6633             scanner.chew()
6634             key = scanner.nexttok()
6635             if key == "IHALPHA" and scanner.sees("y"):
6636                 game.damage[i] = 10.0
6637     proutn("Examine/change events? ")
6638     if ja():
6639         ev = Event()
6640         w = Coord()
6641         legends = {
6642             FSNOVA:  "Supernova       ",
6643             FTBEAM:  "T Beam          ",
6644             FSNAP:   "Snapshot        ",
6645             FBATTAK: "Base Attack     ",
6646             FCDBAS:  "Base Destroy    ",
6647             FSCMOVE: "SC Move         ",
6648             FSCDBAS: "SC Base Destroy ",
6649             FDSPROB: "Probe Move      ",
6650             FDISTR:  "Distress Call   ",
6651             FENSLV:  "Enslavement     ",
6652             FREPRO:  "Klingon Build   ",
6653         }
6654         for i in range(1, NEVENTS):
6655             proutn(legends[i])
6656             if is_scheduled(i):
6657                 proutn("%.2f" % (scheduled(i)-game.state.date))
6658                 if i == FENSLV or i == FREPRO:
6659                     ev = findevent(i)
6660                     proutn(" in %s" % ev.quadrant)
6661             else:
6662                 proutn("never")
6663             proutn("? ")
6664             scanner.chew()
6665             key = scanner.nexttok()
6666             if key == 'n':
6667                 unschedule(i)
6668                 scanner.chew()
6669             elif key == "IHREAL":
6670                 ev = schedule(i, scanner.real)
6671                 if i == FENSLV or i == FREPRO:
6672                     scanner.chew()
6673                     proutn("In quadrant- ")
6674                     key = scanner.nexttok()
6675                     # "IHEOL" says to leave coordinates as they are
6676                     if key != "IHEOL":
6677                         if key != "IHREAL":
6678                             prout("Event %d canceled, no x coordinate." % (i))
6679                             unschedule(i)
6680                             continue
6681                         w.i = int(round(scanner.real))
6682                         key = scanner.nexttok()
6683                         if key != "IHREAL":
6684                             prout("Event %d canceled, no y coordinate." % (i))
6685                             unschedule(i)
6686                             continue
6687                         w.j = int(round(scanner.real))
6688                         ev.quadrant = w
6689         scanner.chew()
6690     proutn("Induce supernova here? ")
6691     if ja():
6692         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6693         atover(True)
6694
6695 if __name__ == '__main__':
6696     try:
6697         #global line, thing, game
6698         game = None
6699         thing = Thingy()
6700         game = Gamestate()
6701         rnd = randomizer()
6702         logfp = None
6703         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6704         if os.getenv("TERM"):
6705             game.options |= OPTION_CURSES
6706         else:
6707             game.options |= OPTION_TTY
6708         seed = int(time.time())
6709         (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6710         replay = False
6711         for (switch, val) in options:
6712             if switch == '-r':
6713                 try:
6714                     replayfp = open(val, "r")
6715                 except IOError:
6716                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6717                     raise SystemExit(1)
6718                 try:
6719                     line = replayfp.readline().strip()
6720                     (leader, __, seed) = line.split()
6721                     seed = eval(seed)
6722                     line = replayfp.readline().strip()
6723                     arguments += line.split()[2:]
6724                     replay = True
6725                 except ValueError:
6726                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6727                     raise SystemExit(1)
6728                 game.options |= OPTION_TTY
6729                 game.options &=~ OPTION_CURSES
6730             elif switch == '-s':
6731                 seed = int(val)
6732             elif switch == '-t':
6733                 game.options |= OPTION_TTY
6734                 game.options &=~ OPTION_CURSES
6735             elif switch == '-x':
6736                 game.idebug = True
6737             elif switch == '-c':        # Enable curses debugging - undocumented
6738                 game.cdebug = True
6739             elif switch == '-V':
6740                 print("SST2K", version)
6741                 raise SystemExit(0)
6742             else:
6743                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6744                 raise SystemExit(1)
6745         # where to save the input in case of bugs
6746         if "TMPDIR" in os.environ:
6747             tmpdir = os.environ['TMPDIR']
6748         else:
6749             tmpdir = "/tmp"
6750         try:
6751             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6752         except IOError:
6753             sys.stderr.write("sst: warning, can't open logfile\n")
6754             sys.exit(1)
6755         if logfp:
6756             logfp.write("# seed %s\n" % seed)
6757             logfp.write("# options %s\n" % " ".join(arguments))
6758             logfp.write("# SST2K version %s\n" % version)
6759             logfp.write("# recorded by %s@%s on %s\n" % \
6760                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6761         rnd.seed(seed)
6762         scanner = sstscanner()
6763         for arg in arguments:
6764             scanner.append(arg)
6765         try:
6766             iostart()
6767             while True: # Play a game
6768                 setwnd(fullscreen_window)
6769                 clrscr()
6770                 prelim()
6771                 setup()
6772                 if game.alldone:
6773                     score()
6774                     game.alldone = False
6775                 else:
6776                     makemoves()
6777                 if replay:
6778                     break
6779                 skip(1)
6780                 stars()
6781                 skip(1)
6782                 if game.tourn and game.alldone:
6783                     proutn(_("Do you want your score recorded?"))
6784                     if ja():
6785                         scanner.chew()
6786                         scanner.push("\n")
6787                         freeze(False)
6788                 scanner.chew()
6789                 proutn(_("Do you want to play again? "))
6790                 if not ja():
6791                     break
6792             skip(1)
6793             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6794         finally:
6795             ioend()
6796         raise SystemExit(0)
6797     except KeyboardInterrupt:
6798         if logfp:
6799             logfp.close()
6800         print("")
6801
6802 # End.