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