Second step of pylint cleanup - all regression tess pass.
[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         ddist = FOREVER
4251         for ibq in game.state.baseq:
4252             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4253             if xdist < ddist:
4254                 ddist = xdist
4255         # Since starbase not in quadrant, set up new quadrant
4256         game.quadrant = ibq
4257         newqad()
4258     # dematerialize starship
4259     game.quad[game.sector.i][game.sector.j]='.'
4260     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4261            % (game.quadrant, crmshp()))
4262     game.sector.invalidate()
4263     for m in range(1, 5+1):
4264         w = game.base.scatter()
4265         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4266             # found one -- finish up
4267             game.sector = w
4268             break
4269     if not game.sector.is_valid():
4270         prout(_("You have been lost in space..."))
4271         finish(FMATERIALIZE)
4272         return
4273     # Give starbase three chances to rematerialize starship
4274     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4275     for m in range(1, 3+1):
4276         if m == 1: proutn(_("1st"))
4277         elif m == 2: proutn(_("2nd"))
4278         elif m == 3: proutn(_("3rd"))
4279         proutn(_(" attempt to re-materialize ") + crmshp())
4280         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4281         textcolor(RED)
4282         warble()
4283         if randreal() > probf:
4284             break
4285         prout(_("fails."))
4286         textcolor(DEFAULT)
4287         curses.delay_output(500)
4288     if m > 3:
4289         game.quad[game.sector.i][game.sector.j]='?'
4290         game.alive = False
4291         drawmaps(1)
4292         setwnd(message_window)
4293         finish(FMATERIALIZE)
4294         return
4295     game.quad[game.sector.i][game.sector.j]=game.ship
4296     textcolor(GREEN)
4297     prout(_("succeeds."))
4298     textcolor(DEFAULT)
4299     dock(False)
4300     skip(1)
4301     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4302
4303 def abandon():
4304     "Abandon ship."
4305     scanner.chew()
4306     if game.condition=="docked":
4307         if game.ship!='E':
4308             prout(_("You cannot abandon Ye Faerie Queene."))
4309             return
4310     else:
4311         # Must take shuttle craft to exit
4312         if game.damage[DSHUTTL]==-1:
4313             prout(_("Ye Faerie Queene has no shuttle craft."))
4314             return
4315         if game.damage[DSHUTTL]<0:
4316             prout(_("Shuttle craft now serving Big Macs."))
4317             return
4318         if game.damage[DSHUTTL]>0:
4319             prout(_("Shuttle craft damaged."))
4320             return
4321         if game.landed:
4322             prout(_("You must be aboard the ship."))
4323             return
4324         if game.iscraft != "onship":
4325             prout(_("Shuttle craft not currently available."))
4326             return
4327         # Emit abandon ship messages
4328         skip(1)
4329         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4330         skip(1)
4331         prouts(_("***ALL HANDS ABANDON SHIP!"))
4332         skip(2)
4333         prout(_("Captain and crew escape in shuttle craft."))
4334         if not game.state.baseq:
4335             # Oops! no place to go...
4336             finish(FABANDN)
4337             return
4338         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4339         # Dispose of crew
4340         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4341             prout(_("Remainder of ship's complement beam down"))
4342             prout(_("to nearest habitable planet."))
4343         elif q.planet != None and not damaged(DTRANSP):
4344             prout(_("Remainder of ship's complement beam down to %s.") %
4345                   q.planet)
4346         else:
4347             prout(_("Entire crew of %d left to die in outer space.") %
4348                   game.state.crew)
4349             game.casual += game.state.crew
4350             game.abandoned += game.state.crew
4351         # If at least one base left, give 'em the Faerie Queene
4352         skip(1)
4353         game.icrystl = False # crystals are lost
4354         game.nprobes = 0 # No probes
4355         prout(_("You are captured by Klingons and released to"))
4356         prout(_("the Federation in a prisoner-of-war exchange."))
4357         nb = randrange(len(game.state.baseq))
4358         # Set up quadrant and position FQ adjacient to base
4359         if not game.quadrant == game.state.baseq[nb]:
4360             game.quadrant = game.state.baseq[nb]
4361             game.sector.i = game.sector.j = 5
4362             newqad()
4363         while True:
4364             # position next to base by trial and error
4365             game.quad[game.sector.i][game.sector.j] = '.'
4366             for l in range(QUADSIZE):
4367                 game.sector = game.base.scatter()
4368                 if game.sector.valid_sector() and \
4369                        game.quad[game.sector.i][game.sector.j] == '.':
4370                     break
4371             if l < QUADSIZE+1:
4372                 break # found a spot
4373             game.sector.i=QUADSIZE/2
4374             game.sector.j=QUADSIZE/2
4375             newqad()
4376     # Get new commission
4377     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4378     game.state.crew = FULLCREW
4379     prout(_("Starfleet puts you in command of another ship,"))
4380     prout(_("the Faerie Queene, which is antiquated but,"))
4381     prout(_("still useable."))
4382     if game.icrystl:
4383         prout(_("The dilithium crystals have been moved."))
4384     game.imine = False
4385     game.iscraft = "offship" # Galileo disappears
4386     # Resupply ship
4387     game.condition="docked"
4388     for l in range(NDEVICES):
4389         game.damage[l] = 0.0
4390     game.damage[DSHUTTL] = -1
4391     game.energy = game.inenrg = 3000.0
4392     game.shield = game.inshld = 1250.0
4393     game.torps = game.intorps = 6
4394     game.lsupres=game.inlsr=3.0
4395     game.shldup=False
4396     game.warpfac=5.0
4397     return
4398
4399 # Code from planets.c begins here.
4400
4401 def consumeTime():
4402     "Abort a lengthy operation if an event interrupts it."
4403     game.ididit = True
4404     events()
4405     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4406         return True
4407     return False
4408
4409 def survey():
4410     "Report on (uninhabited) planets in the galaxy."
4411     iknow = False
4412     skip(1)
4413     scanner.chew()
4414     prout(_("Spock-  \"Planet report follows, Captain.\""))
4415     skip(1)
4416     for i in range(game.inplan):
4417         if game.state.planets[i].pclass == "destroyed":
4418             continue
4419         if (game.state.planets[i].known != "unknown" \
4420             and not game.state.planets[i].inhabited) \
4421             or game.idebug:
4422             iknow = True
4423             if game.idebug and game.state.planets[i].known=="unknown":
4424                 proutn("(Unknown) ")
4425             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4426             proutn(_("   class "))
4427             proutn(game.state.planets[i].pclass)
4428             proutn("   ")
4429             if game.state.planets[i].crystals != "present":
4430                 proutn(_("no "))
4431             prout(_("dilithium crystals present."))
4432             if game.state.planets[i].known=="shuttle_down":
4433                 prout(_("    Shuttle Craft Galileo on surface."))
4434     if not iknow:
4435         prout(_("No information available."))
4436
4437 def orbit():
4438     "Enter standard orbit."
4439     skip(1)
4440     scanner.chew()
4441     if game.inorbit:
4442         prout(_("Already in standard orbit."))
4443         return
4444     if damaged(DWARPEN) and damaged(DIMPULS):
4445         prout(_("Both warp and impulse engines damaged."))
4446         return
4447     if not game.plnet.is_valid():
4448         prout("There is no planet in this sector.")
4449         return
4450     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4451         prout(crmshp() + _(" not adjacent to planet."))
4452         skip(1)
4453         return
4454     game.optime = randreal(0.02, 0.05)
4455     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4456     newcnd()
4457     if consumeTime():
4458         return
4459     game.height = randreal(1400, 8600)
4460     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4461     game.inorbit = True
4462     game.ididit = True
4463
4464 def sensor():
4465     "Examine planets in this quadrant."
4466     if damaged(DSRSENS):
4467         if game.options & OPTION_TTY:
4468             prout(_("Short range sensors damaged."))
4469         return
4470     if game.iplnet is None:
4471         if game.options & OPTION_TTY:
4472             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4473         return
4474     if game.iplnet.known == "unknown":
4475         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4476         skip(1)
4477         prout(_("         Planet at Sector %s is of class %s.") %
4478               (game.plnet, game.iplnet.pclass))
4479         if game.iplnet.known=="shuttle_down":
4480             prout(_("         Sensors show Galileo still on surface."))
4481         proutn(_("         Readings indicate"))
4482         if game.iplnet.crystals != "present":
4483             proutn(_(" no"))
4484         prout(_(" dilithium crystals present.\""))
4485         if game.iplnet.known == "unknown":
4486             game.iplnet.known = "known"
4487     elif game.iplnet.inhabited:
4488         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4489         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4490
4491 def beam():
4492     "Use the transporter."
4493     nrgneed = 0
4494     scanner.chew()
4495     skip(1)
4496     if damaged(DTRANSP):
4497         prout(_("Transporter damaged."))
4498         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4499             skip(1)
4500             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4501             if ja():
4502                 shuttle()
4503         return
4504     if not game.inorbit:
4505         prout(crmshp() + _(" not in standard orbit."))
4506         return
4507     if game.shldup:
4508         prout(_("Impossible to transport through shields."))
4509         return
4510     if game.iplnet.known=="unknown":
4511         prout(_("Spock-  \"Captain, we have no information on this planet"))
4512         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4513         prout(_("  you may not go down.\""))
4514         return
4515     if not game.landed and game.iplnet.crystals=="absent":
4516         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4517         prout(_("  exploring a planet with no dilithium crystals."))
4518         proutn(_("  Are you sure this is wise?\" "))
4519         if not ja():
4520             scanner.chew()
4521             return
4522     if not (game.options & OPTION_PLAIN):
4523         nrgneed = 50 * game.skill + game.height / 100.0
4524         if nrgneed > game.energy:
4525             prout(_("Engineering to bridge--"))
4526             prout(_("  Captain, we don't have enough energy for transportation."))
4527             return
4528         if not game.landed and nrgneed * 2 > game.energy:
4529             prout(_("Engineering to bridge--"))
4530             prout(_("  Captain, we have enough energy only to transport you down to"))
4531             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4532             if game.iplnet.known == "shuttle_down":
4533                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4534             proutn(_("  Are you sure this is wise?\" "))
4535             if not ja():
4536                 scanner.chew()
4537                 return
4538     if game.landed:
4539         # Coming from planet
4540         if game.iplnet.known=="shuttle_down":
4541             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4542             if ja():
4543                 scanner.chew()
4544                 return
4545             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4546         prout(_("Landing party assembled, ready to beam up."))
4547         skip(1)
4548         prout(_("Kirk whips out communicator..."))
4549         prouts(_("BEEP  BEEP  BEEP"))
4550         skip(2)
4551         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4552     else:
4553         # Going to planet
4554         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4555         skip(1)
4556         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4557         skip(1)
4558         prout(_("Kirk-  \"Energize.\""))
4559     game.ididit = True
4560     skip(1)
4561     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4562     skip(2)
4563     if not withprob(0.98):
4564         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4565         skip(2)
4566         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4567         finish(FLOST)
4568         return
4569     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4570     game.landed = not game.landed
4571     game.energy -= nrgneed
4572     skip(2)
4573     prout(_("Transport complete."))
4574     if game.landed and game.iplnet.known=="shuttle_down":
4575         prout(_("The shuttle craft Galileo is here!"))
4576     if not game.landed and game.imine:
4577         game.icrystl = True
4578         game.cryprob = 0.05
4579     game.imine = False
4580     return
4581
4582 def mine():
4583     "Strip-mine a world for dilithium."
4584     skip(1)
4585     scanner.chew()
4586     if not game.landed:
4587         prout(_("Mining party not on planet."))
4588         return
4589     if game.iplnet.crystals == "mined":
4590         prout(_("This planet has already been strip-mined for dilithium."))
4591         return
4592     elif game.iplnet.crystals == "absent":
4593         prout(_("No dilithium crystals on this planet."))
4594         return
4595     if game.imine:
4596         prout(_("You've already mined enough crystals for this trip."))
4597         return
4598     if game.icrystl and game.cryprob == 0.05:
4599         prout(_("With all those fresh crystals aboard the ") + crmshp())
4600         prout(_("there's no reason to mine more at this time."))
4601         return
4602     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4603     if consumeTime():
4604         return
4605     prout(_("Mining operation complete."))
4606     game.iplnet.crystals = "mined"
4607     game.imine = game.ididit = True
4608
4609 def usecrystals():
4610     "Use dilithium crystals."
4611     game.ididit = False
4612     skip(1)
4613     scanner.chew()
4614     if not game.icrystl:
4615         prout(_("No dilithium crystals available."))
4616         return
4617     if game.energy >= 1000:
4618         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4619         prout(_("  except when Condition Yellow exists."))
4620         return
4621     prout(_("Spock- \"Captain, I must warn you that loading"))
4622     prout(_("  raw dilithium crystals into the ship's power"))
4623     prout(_("  system may risk a severe explosion."))
4624     proutn(_("  Are you sure this is wise?\" "))
4625     if not ja():
4626         scanner.chew()
4627         return
4628     skip(1)
4629     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4630     prout(_("  Mr. Spock and I will try it.\""))
4631     skip(1)
4632     prout(_("Spock-  \"Crystals in place, Sir."))
4633     prout(_("  Ready to activate circuit.\""))
4634     skip(1)
4635     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4636     skip(1)
4637     if withprob(game.cryprob):
4638         prouts(_("  \"Activating now! - - No good!  It's***"))
4639         skip(2)
4640         prouts(_("***RED ALERT!  RED A*L********************************"))
4641         skip(1)
4642         stars()
4643         prouts(_("******************   KA-BOOM!!!!   *******************"))
4644         skip(1)
4645         kaboom()
4646         return
4647     game.energy += randreal(5000.0, 5500.0)
4648     prouts(_("  \"Activating now! - - "))
4649     prout(_("The instruments"))
4650     prout(_("   are going crazy, but I think it's"))
4651     prout(_("   going to work!!  Congratulations, Sir!\""))
4652     game.cryprob *= 2.0
4653     game.ididit = True
4654
4655 def shuttle():
4656     "Use shuttlecraft for planetary jaunt."
4657     scanner.chew()
4658     skip(1)
4659     if damaged(DSHUTTL):
4660         if game.damage[DSHUTTL] == -1.0:
4661             if game.inorbit and game.iplnet.known == "shuttle_down":
4662                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4663             else:
4664                 prout(_("Ye Faerie Queene had no shuttle craft."))
4665         elif game.damage[DSHUTTL] > 0:
4666             prout(_("The Galileo is damaged."))
4667         else: # game.damage[DSHUTTL] < 0
4668             prout(_("Shuttle craft is now serving Big Macs."))
4669         return
4670     if not game.inorbit:
4671         prout(crmshp() + _(" not in standard orbit."))
4672         return
4673     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4674         prout(_("Shuttle craft not currently available."))
4675         return
4676     if not game.landed and game.iplnet.known=="shuttle_down":
4677         prout(_("You will have to beam down to retrieve the shuttle craft."))
4678         return
4679     if game.shldup or game.condition == "docked":
4680         prout(_("Shuttle craft cannot pass through shields."))
4681         return
4682     if game.iplnet.known=="unknown":
4683         prout(_("Spock-  \"Captain, we have no information on this planet"))
4684         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4685         prout(_("  you may not fly down.\""))
4686         return
4687     game.optime = 3.0e-5*game.height
4688     if game.optime >= 0.8*game.state.remtime:
4689         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4690         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4691                int(100*game.optime/game.state.remtime))
4692         prout(_("remaining time."))
4693         proutn(_("Are you sure this is wise?\" "))
4694         if not ja():
4695             game.optime = 0.0
4696             return
4697     if game.landed:
4698         # Kirk on planet
4699         if game.iscraft == "onship":
4700             # Galileo on ship!
4701             if not damaged(DTRANSP):
4702                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4703                 if ja():
4704                     beam()
4705                     return
4706                 proutn(_("Shuttle crew"))
4707             else:
4708                 proutn(_("Rescue party"))
4709             prout(_(" boards Galileo and swoops toward planet surface."))
4710             game.iscraft = "offship"
4711             skip(1)
4712             if consumeTime():
4713                 return
4714             game.iplnet.known="shuttle_down"
4715             prout(_("Trip complete."))
4716             return
4717         else:
4718             # Ready to go back to ship
4719             prout(_("You and your mining party board the"))
4720             prout(_("shuttle craft for the trip back to the Enterprise."))
4721             skip(1)
4722             prouts(_("The short hop begins . . ."))
4723             skip(1)
4724             game.iplnet.known="known"
4725             game.icraft = True
4726             skip(1)
4727             game.landed = False
4728             if consumeTime():
4729                 return
4730             game.iscraft = "onship"
4731             game.icraft = False
4732             if game.imine:
4733                 game.icrystl = True
4734                 game.cryprob = 0.05
4735             game.imine = False
4736             prout(_("Trip complete."))
4737             return
4738     else:
4739         # Kirk on ship and so is Galileo
4740         prout(_("Mining party assembles in the hangar deck,"))
4741         prout(_("ready to board the shuttle craft \"Galileo\"."))
4742         skip(1)
4743         prouts(_("The hangar doors open; the trip begins."))
4744         skip(1)
4745         game.icraft = True
4746         game.iscraft = "offship"
4747         if consumeTime():
4748             return
4749         game.iplnet.known = "shuttle_down"
4750         game.landed = True
4751         game.icraft = False
4752         prout(_("Trip complete."))
4753         return
4754
4755 def deathray():
4756     "Use the big zapper."
4757     game.ididit = False
4758     skip(1)
4759     scanner.chew()
4760     if game.ship != 'E':
4761         prout(_("Ye Faerie Queene has no death ray."))
4762         return
4763     if len(game.enemies)==0:
4764         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4765         return
4766     if damaged(DDRAY):
4767         prout(_("Death Ray is damaged."))
4768         return
4769     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4770     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4771     proutn(_("  are you sure this is wise?\" "))
4772     if not ja():
4773         return
4774     prout(_("Spock-  \"Acknowledged.\""))
4775     skip(1)
4776     game.ididit = True
4777     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4778     skip(1)
4779     prout(_("Crew scrambles in emergency preparation."))
4780     prout(_("Spock and Scotty ready the death ray and"))
4781     prout(_("prepare to channel all ship's power to the device."))
4782     skip(1)
4783     prout(_("Spock-  \"Preparations complete, sir.\""))
4784     prout(_("Kirk-  \"Engage!\""))
4785     skip(1)
4786     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4787     skip(1)
4788     dprob = 0.30
4789     if game.options & OPTION_PLAIN:
4790         dprob = 0.5
4791     r = randreal()
4792     if r > dprob:
4793         prouts(_("Sulu- \"Captain!  It's working!\""))
4794         skip(2)
4795         while len(game.enemies) > 0:
4796             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
4797         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4798         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4799             finish(FWON)
4800         if (game.options & OPTION_PLAIN) == 0:
4801             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4802             if withprob(0.05):
4803                 prout(_("   is still operational.\""))
4804             else:
4805                 prout(_("   has been rendered nonfunctional.\""))
4806                 game.damage[DDRAY] = 39.95
4807         return
4808     r = randreal()        # Pick failure method
4809     if r <= 0.30:
4810         prouts(_("Sulu- \"Captain!  It's working!\""))
4811         skip(1)
4812         prouts(_("***RED ALERT!  RED ALERT!"))
4813         skip(1)
4814         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4815         skip(1)
4816         prouts(_("***RED ALERT!  RED A*L********************************"))
4817         skip(1)
4818         stars()
4819         prouts(_("******************   KA-BOOM!!!!   *******************"))
4820         skip(1)
4821         kaboom()
4822         return
4823     if r <= 0.55:
4824         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4825         skip(1)
4826         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4827         skip(1)
4828         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4829         prout(_("  have apparently been transformed into strange mutations."))
4830         prout(_("  Vulcans do not seem to be affected."))
4831         skip(1)
4832         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4833         finish(FDRAY)
4834         return
4835     if r <= 0.75:
4836         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4837         skip(2)
4838         proutn(_("Spock-  \"I believe the word is"))
4839         prouts(_(" *ASTONISHING*"))
4840         prout(_(" Mr. Sulu."))
4841         for i in range(QUADSIZE):
4842             for j in range(QUADSIZE):
4843                 if game.quad[i][j] == '.':
4844                     game.quad[i][j] = '?'
4845         prout(_("  Captain, our quadrant is now infested with"))
4846         prouts(_(" - - - - - -  *THINGS*."))
4847         skip(1)
4848         prout(_("  I have no logical explanation.\""))
4849         return
4850     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4851     skip(1)
4852     prout(_("Scotty-  \"There are so many tribbles down here"))
4853     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4854     finish(FTRIBBLE)
4855     return
4856
4857 # Code from reports.c begins here
4858
4859 def attackreport(curt):
4860     "eport status of bases under attack."
4861     if not curt:
4862         if is_scheduled(FCDBAS):
4863             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4864             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4865         elif game.isatb == 1:
4866             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4867             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4868         else:
4869             prout(_("No Starbase is currently under attack."))
4870     else:
4871         if is_scheduled(FCDBAS):
4872             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4873         if game.isatb:
4874             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
4875         clreol()
4876
4877 def report():
4878     # report on general game status
4879     scanner.chew()
4880     s1 = (game.thawed and _("thawed ")) or ""
4881     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
4882     s3 = (None, _("novice"), _("fair"),
4883           _("good"), _("expert"), _("emeritus"))[game.skill]
4884     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
4885     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
4886         prout(_("No plaque is allowed."))
4887     if game.tourn:
4888         prout(_("This is tournament game %d.") % game.tourn)
4889     prout(_("Your secret password is \"%s\"") % game.passwd)
4890     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)),
4891                                                       (game.inkling + game.incom + game.inscom)))
4892     if game.incom - len(game.state.kcmdr):
4893         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
4894     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
4895         prout(_(", but no Commanders."))
4896     else:
4897         prout(".")
4898     if game.skill > SKILL_FAIR:
4899         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
4900     if len(game.state.baseq) != game.inbase:
4901         proutn(_("There "))
4902         if game.inbase-len(game.state.baseq)==1:
4903             proutn(_("has been 1 base"))
4904         else:
4905             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
4906         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
4907     else:
4908         prout(_("There are %d bases.") % game.inbase)
4909     if communicating() or game.iseenit:
4910         # Don't report this if not seen and
4911         # either the radio is dead or not at base!
4912         attackreport(False)
4913         game.iseenit = True
4914     if game.casual:
4915         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
4916     if game.nhelp:
4917         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
4918     if game.ship == 'E':
4919         proutn(_("You have "))
4920         if game.nprobes:
4921             proutn("%d" % (game.nprobes))
4922         else:
4923             proutn(_("no"))
4924         proutn(_(" deep space probe"))
4925         if game.nprobes!=1:
4926             proutn(_("s"))
4927         prout(".")
4928     if communicating() and is_scheduled(FDSPROB):
4929         if game.isarmed:
4930             proutn(_("An armed deep space probe is in "))
4931         else:
4932             proutn(_("A deep space probe is in "))
4933         prout("Quadrant %s." % game.probec)
4934     if game.icrystl:
4935         if game.cryprob <= .05:
4936             prout(_("Dilithium crystals aboard ship... not yet used."))
4937         else:
4938             i=0
4939             ai = 0.05
4940             while game.cryprob > ai:
4941                 ai *= 2.0
4942                 i += 1
4943             prout(_("Dilithium crystals have been used %d time%s.") % \
4944                   (i, (_("s"), "")[i==1]))
4945     skip(1)
4946
4947 def lrscan(silent):
4948     "Long-range sensor scan."
4949     if damaged(DLRSENS):
4950         # Now allow base's sensors if docked
4951         if game.condition != "docked":
4952             if not silent:
4953                 prout(_("LONG-RANGE SENSORS DAMAGED."))
4954             return
4955         if not silent:
4956             prout(_("Starbase's long-range scan"))
4957     elif not silent:
4958         prout(_("Long-range scan"))
4959     for x in range(game.quadrant.i-1, game.quadrant.i+2):
4960         if not silent:
4961             proutn(" ")
4962         for y in range(game.quadrant.j-1, game.quadrant.j+2):
4963             if not Coord(x, y).valid_quadrant():
4964                 if not silent:
4965                     proutn("  -1")
4966             else:
4967                 if not damaged(DRADIO):
4968                     game.state.galaxy[x][y].charted = True
4969                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
4970                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
4971                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
4972                 if not silent and game.state.galaxy[x][y].supernova:
4973                     proutn(" ***")
4974                 elif not silent:
4975                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
4976         if not silent:
4977             prout(" ")
4978
4979 def damagereport():
4980     "Damage report."
4981     jdam = False
4982     scanner.chew()
4983     for i in range(NDEVICES):
4984         if damaged(i):
4985             if not jdam:
4986                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
4987                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
4988                 jdam = True
4989             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
4990                                                game.damage[i]+0.05,
4991                                                DOCKFAC*game.damage[i]+0.005))
4992     if not jdam:
4993         prout(_("All devices functional."))
4994
4995 def rechart():
4996     "Update the chart in the Enterprise's computer from galaxy data."
4997     game.lastchart = game.state.date
4998     for i in range(GALSIZE):
4999         for j in range(GALSIZE):
5000             if game.state.galaxy[i][j].charted:
5001                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5002                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5003                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5004
5005 def chart():
5006     "Display the star chart."
5007     scanner.chew()
5008     if (game.options & OPTION_AUTOSCAN):
5009         lrscan(silent=True)
5010     if not damaged(DRADIO):
5011         rechart()
5012     if game.lastchart < game.state.date and game.condition == "docked":
5013         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5014         rechart()
5015     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5016     if game.state.date > game.lastchart:
5017         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5018     prout("      1    2    3    4    5    6    7    8")
5019     for i in range(GALSIZE):
5020         proutn("%d |" % (i+1))
5021         for j in range(GALSIZE):
5022             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5023                 proutn("<")
5024             else:
5025                 proutn(" ")
5026             if game.state.galaxy[i][j].supernova:
5027                 show = "***"
5028             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5029                 show = ".1."
5030             elif game.state.galaxy[i][j].charted:
5031                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5032             else:
5033                 show = "..."
5034             proutn(show)
5035             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5036                 proutn(">")
5037             else:
5038                 proutn(" ")
5039         proutn("  |")
5040         if i<GALSIZE:
5041             skip(1)
5042
5043 def sectscan(goodScan, i, j):
5044     "Light up an individual dot in a sector."
5045     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5046         textcolor({"green":GREEN,
5047                    "yellow":YELLOW,
5048                    "red":RED,
5049                    "docked":CYAN,
5050                    "dead":BROWN}[game.condition])
5051         if game.quad[i][j] != game.ship:
5052             highvideo()
5053         proutn("%c " % game.quad[i][j])
5054         textcolor(DEFAULT)
5055     else:
5056         proutn("- ")
5057
5058 def status(req=0):
5059     "Emit status report lines"
5060     if not req or req == 1:
5061         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5062                % (game.state.date, game.state.remtime))
5063     if not req or req == 2:
5064         if game.condition != "docked":
5065             newcnd()
5066         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5067                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5068     if not req or req == 3:
5069         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5070     if not req or req == 4:
5071         if damaged(DLIFSUP):
5072             if game.condition == "docked":
5073                 s = _("DAMAGED, Base provides")
5074             else:
5075                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5076         else:
5077             s = _("ACTIVE")
5078         prstat(_("Life Support"), s)
5079     if not req or req == 5:
5080         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5081     if not req or req == 6:
5082         extra = ""
5083         if game.icrystl and (game.options & OPTION_SHOWME):
5084             extra = _(" (have crystals)")
5085         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5086     if not req or req == 7:
5087         prstat(_("Torpedoes"), "%d" % (game.torps))
5088     if not req or req == 8:
5089         if damaged(DSHIELD):
5090             s = _("DAMAGED,")
5091         elif game.shldup:
5092             s = _("UP,")
5093         else:
5094             s = _("DOWN,")
5095         data = _(" %d%% %.1f units") \
5096                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5097         prstat(_("Shields"), s+data)
5098     if not req or req == 9:
5099         prstat(_("Klingons Left"), "%d" \
5100                % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
5101     if not req or req == 10:
5102         if game.options & OPTION_WORLDS:
5103             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5104             if plnet and plnet.inhabited:
5105                 prstat(_("Major system"), plnet.name)
5106             else:
5107                 prout(_("Sector is uninhabited"))
5108     elif not req or req == 11:
5109         attackreport(not req)
5110
5111 def request():
5112     "Request specified status data, a historical relic from slow TTYs."
5113     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5114     while scanner.nexttok() == "IHEOL":
5115         proutn(_("Information desired? "))
5116     scanner.chew()
5117     if scanner.token in requests:
5118         status(requests.index(scanner.token))
5119     else:
5120         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5121         prout(("  date, condition, position, lsupport, warpfactor,"))
5122         prout(("  energy, torpedoes, shields, klingons, system, time."))
5123
5124 def srscan():
5125     "Short-range scan."
5126     goodScan=True
5127     if damaged(DSRSENS):
5128         # Allow base's sensors if docked
5129         if game.condition != "docked":
5130             prout(_("   S.R. SENSORS DAMAGED!"))
5131             goodScan=False
5132         else:
5133             prout(_("  [Using Base's sensors]"))
5134     else:
5135         prout(_("     Short-range scan"))
5136     if goodScan and not damaged(DRADIO):
5137         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5138         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5139         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5140         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5141     prout("    1 2 3 4 5 6 7 8 9 10")
5142     if game.condition != "docked":
5143         newcnd()
5144     for i in range(QUADSIZE):
5145         proutn("%2d  " % (i+1))
5146         for j in range(QUADSIZE):
5147             sectscan(goodScan, i, j)
5148         skip(1)
5149
5150 def eta():
5151     "Use computer to get estimated time of arrival for a warp jump."
5152     w1 = Coord(); w2 = Coord()
5153     prompt = False
5154     if damaged(DCOMPTR):
5155         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5156         skip(1)
5157         return
5158     if scanner.nexttok() != "IHREAL":
5159         prompt = True
5160         scanner.chew()
5161         proutn(_("Destination quadrant and/or sector? "))
5162         if scanner.nexttok()!="IHREAL":
5163             huh()
5164             return
5165     w1.j = int(scanner.real-0.5)
5166     if scanner.nexttok() != "IHREAL":
5167         huh()
5168         return
5169     w1.i = int(scanner.real-0.5)
5170     if scanner.nexttok() == "IHREAL":
5171         w2.j = int(scanner.real-0.5)
5172         if scanner.nexttok() != "IHREAL":
5173             huh()
5174             return
5175         w2.i = int(scanner.real-0.5)
5176     else:
5177         if game.quadrant.j>w1.i:
5178             w2.i = 0
5179         else:
5180             w2.i=QUADSIZE-1
5181         if game.quadrant.i>w1.j:
5182             w2.j = 0
5183         else:
5184             w2.j=QUADSIZE-1
5185     if not w1.valid_quadrant() or not w2.valid_sector():
5186         huh()
5187         return
5188     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5189                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5190     wfl = False
5191     if prompt:
5192         prout(_("Answer \"no\" if you don't know the value:"))
5193     while True:
5194         scanner.chew()
5195         proutn(_("Time or arrival date? "))
5196         if scanner.nexttok()=="IHREAL":
5197             ttime = scanner.real
5198             if ttime > game.state.date:
5199                 ttime -= game.state.date # Actually a star date
5200             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5201             if ttime <= 1e-10 or twarp > 10:
5202                 prout(_("We'll never make it, sir."))
5203                 scanner.chew()
5204                 return
5205             if twarp < 1.0:
5206                 twarp = 1.0
5207             break
5208         scanner.chew()
5209         proutn(_("Warp factor? "))
5210         if scanner.nexttok()== "IHREAL":
5211             wfl = True
5212             twarp = scanner.real
5213             if twarp<1.0 or twarp > 10.0:
5214                 huh()
5215                 return
5216             break
5217         prout(_("Captain, certainly you can give me one of these."))
5218     while True:
5219         scanner.chew()
5220         ttime = (10.0*dist)/twarp**2
5221         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5222         if tpower >= game.energy:
5223             prout(_("Insufficient energy, sir."))
5224             if not game.shldup or tpower > game.energy*2.0:
5225                 if not wfl:
5226                     return
5227                 proutn(_("New warp factor to try? "))
5228                 if scanner.nexttok() == "IHREAL":
5229                     wfl = True
5230                     twarp = scanner.real
5231                     if twarp<1.0 or twarp > 10.0:
5232                         huh()
5233                         return
5234                     continue
5235                 else:
5236                     scanner.chew()
5237                     skip(1)
5238                     return
5239             prout(_("But if you lower your shields,"))
5240             proutn(_("remaining"))
5241             tpower /= 2
5242         else:
5243             proutn(_("Remaining"))
5244         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5245         if wfl:
5246             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5247         elif twarp==1.0:
5248             prout(_("Any warp speed is adequate."))
5249         else:
5250             prout(_("Minimum warp needed is %.2f,") % (twarp))
5251             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5252         if game.state.remtime < ttime:
5253             prout(_("Unfortunately, the Federation will be destroyed by then."))
5254         if twarp > 6.0:
5255             prout(_("You'll be taking risks at that speed, Captain"))
5256         if (game.isatb==1 and game.state.kscmdr == w1 and \
5257              scheduled(FSCDBAS)< ttime+game.state.date) or \
5258             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5259             prout(_("The starbase there will be destroyed by then."))
5260         proutn(_("New warp factor to try? "))
5261         if scanner.nexttok() == "IHREAL":
5262             wfl = True
5263             twarp = scanner.real
5264             if twarp<1.0 or twarp > 10.0:
5265                 huh()
5266                 return
5267         else:
5268             scanner.chew()
5269             skip(1)
5270             return
5271
5272 # Code from setup.c begins here
5273
5274 def prelim():
5275     "Issue a historically correct banner."
5276     skip(2)
5277     prout(_("-SUPER- STAR TREK"))
5278     skip(1)
5279 # From the FORTRAN original
5280 #    prout(_("Latest update-21 Sept 78"))
5281 #    skip(1)
5282
5283 def freeze(boss):
5284     "Save game."
5285     if boss:
5286         scanner.push("emsave.trk")
5287     key = scanner.nexttok()
5288     if key == "IHEOL":
5289         proutn(_("File name: "))
5290         key = scanner.nexttok()
5291     if key != "IHALPHA":
5292         huh()
5293         return
5294     if '.' not in scanner.token:
5295         scanner.token += ".trk"
5296     try:
5297         fp = open(scanner.token, "wb")
5298     except IOError:
5299         prout(_("Can't freeze game as file %s") % scanner.token)
5300         return
5301     pickle.dump(game, fp)
5302     fp.close()
5303     scanner.chew()
5304
5305 def thaw():
5306     "Retrieve saved game."
5307     global game
5308     game.passwd = None
5309     key = scanner.nexttok()
5310     if key == "IHEOL":
5311         proutn(_("File name: "))
5312         key = scanner.nexttok()
5313     if key != "IHALPHA":
5314         huh()
5315         return True
5316     if '.' not in scanner.token:
5317         scanner.token += ".trk"
5318     try:
5319         fp = open(scanner.token, "rb")
5320     except IOError:
5321         prout(_("Can't thaw game in %s") % scanner.token)
5322         return
5323     game = pickle.load(fp)
5324     fp.close()
5325     scanner.chew()
5326     return False
5327
5328 # I used <http://www.memory-alpha.org> to find planets
5329 # with references in ST:TOS.  Earth and the Alpha Centauri
5330 # Colony have been omitted.
5331 #
5332 # Some planets marked Class G and P here will be displayed as class M
5333 # because of the way planets are generated. This is a known bug.
5334 systnames = (
5335     # Federation Worlds
5336     _("Andoria (Fesoan)"),        # several episodes
5337     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5338     _("Vulcan (T'Khasi)"),        # many episodes
5339     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5340     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5341     _("Ardana"),                # TOS: "The Cloud Minders"
5342     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5343     _("Gideon"),                # TOS: "The Mark of Gideon"
5344     _("Aldebaran III"),                # TOS: "The Deadly Years"
5345     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5346     _("Altair IV"),                # TOS: "Amok Time
5347     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5348     _("Benecia"),                # TOS: "The Conscience of the King"
5349     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5350     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5351     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5352     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5353     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5354     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5355     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5356     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5357     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5358     _("Janus IV"),                # TOS: "The Devil in the Dark"
5359     _("Makus III"),                # TOS: "The Galileo Seven"
5360     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5361     _("Omega IV"),                # TOS: "The Omega Glory"
5362     _("Regulus V"),                # TOS: "Amok Time
5363     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5364     # Worlds from BSD Trek
5365     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5366     _("Beta III"),                # TOS: "The Return of the Archons"
5367     _("Triacus"),                # TOS: "And the Children Shall Lead",
5368     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5369     #        # Others
5370     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5371     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5372     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5373     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5374     #    _("Tiburon"),                # TOS: "The Way to Eden"
5375     #    _("Merak II"),                # TOS: "The Cloud Minders"
5376     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5377     #    _("Iotia"),                # TOS: "A Piece of the Action"
5378 )
5379
5380 device = (
5381     _("S. R. Sensors"), \
5382     _("L. R. Sensors"), \
5383     _("Phasers"), \
5384     _("Photon Tubes"), \
5385     _("Life Support"), \
5386     _("Warp Engines"), \
5387     _("Impulse Engines"), \
5388     _("Shields"), \
5389     _("Subspace Radio"), \
5390     _("Shuttle Craft"), \
5391     _("Computer"), \
5392     _("Navigation System"), \
5393     _("Transporter"), \
5394     _("Shield Control"), \
5395     _("Death Ray"), \
5396     _("D. S. Probe"), \
5397 )
5398
5399 def setup():
5400     "Prepare to play, set up cosmos."
5401     w = Coord()
5402     #  Decide how many of everything
5403     if choose():
5404         return # frozen game
5405     # Prepare the Enterprise
5406     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5407     game.ship = 'E'
5408     game.state.crew = FULLCREW
5409     game.energy = game.inenrg = 5000.0
5410     game.shield = game.inshld = 2500.0
5411     game.inlsr = 4.0
5412     game.lsupres = 4.0
5413     game.quadrant = randplace(GALSIZE)
5414     game.sector = randplace(QUADSIZE)
5415     game.torps = game.intorps = 10
5416     game.nprobes = randrange(2, 5)
5417     game.warpfac = 5.0
5418     for i in range(NDEVICES):
5419         game.damage[i] = 0.0
5420     # Set up assorted game parameters
5421     game.battle = Coord()
5422     game.state.date = game.indate = 100.0 * randreal(20, 51)
5423     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5424     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5425     game.isatb = game.state.nplankl = 0
5426     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5427     game.iscraft = "onship"
5428     game.landed = False
5429     game.alive = True
5430
5431     # the galaxy
5432     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5433     # the starchart
5434     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5435
5436     game.state.planets = []      # Planet information
5437     game.state.baseq = []      # Base quadrant coordinates
5438     game.state.kcmdr = []      # Commander quadrant coordinates
5439     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5440
5441     # Starchart is functional but we've never seen it
5442     game.lastchart = FOREVER
5443     # Put stars in the galaxy
5444     game.instar = 0
5445     for i in range(GALSIZE):
5446         for j in range(GALSIZE):
5447             # Can't have more stars per quadrant than fit in one decimal digit,
5448             # if we do the chart representation will break.
5449             k = randrange(1, min(10, QUADSIZE**2/10))
5450             game.instar += k
5451             game.state.galaxy[i][j].stars = k
5452     # Locate star bases in galaxy
5453     if game.idebug:
5454         prout("=== Allocating %d bases" % game.inbase)
5455     for i in range(game.inbase):
5456         while True:
5457             while True:
5458                 w = randplace(GALSIZE)
5459                 if not game.state.galaxy[w.i][w.j].starbase:
5460                     break
5461             contflag = False
5462             # C version: for (j = i-1; j > 0; j--)
5463             # so it did them in the opposite order.
5464             for j in range(1, i):
5465                 # Improved placement algorithm to spread out bases
5466                 distq = (w - game.state.baseq[j]).distance()
5467                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5468                     contflag = True
5469                     if game.idebug:
5470                         prout("=== Abandoning base #%d at %s" % (i, w))
5471                     break
5472                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5473                     if game.idebug:
5474                         prout("=== Saving base #%d, close to #%d" % (i, j))
5475             if not contflag:
5476                 break
5477         if game.idebug:
5478             prout("=== Placing base #%d in quadrant %s" % (i, w))
5479         game.state.baseq.append(w)
5480         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5481     # Position ordinary Klingon Battle Cruisers
5482     krem = game.inkling
5483     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5484     if klumper > MAXKLQUAD:
5485         klumper = MAXKLQUAD
5486     while True:
5487         r = randreal()
5488         klump = (1.0 - r*r)*klumper
5489         if klump > krem:
5490             klump = krem
5491         krem -= klump
5492         while True:
5493             w = randplace(GALSIZE)
5494             if not game.state.galaxy[w.i][w.j].supernova and \
5495                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5496                 break
5497         game.state.galaxy[w.i][w.j].klingons += int(klump)
5498         if krem <= 0:
5499             break
5500     # Position Klingon Commander Ships
5501     for i in range(game.incom):
5502         while True:
5503             w = randplace(GALSIZE)
5504             if not welcoming(w) or w in game.state.kcmdr:
5505                 continue
5506             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5507                 break
5508         game.state.galaxy[w.i][w.j].klingons += 1
5509         game.state.kcmdr.append(w)
5510     # Locate planets in galaxy
5511     for i in range(game.inplan):
5512         while True:
5513             w = randplace(GALSIZE)
5514             if game.state.galaxy[w.i][w.j].planet is None:
5515                 break
5516         new = Planet()
5517         new.quadrant = w
5518         new.crystals = "absent"
5519         if (game.options & OPTION_WORLDS) and i < NINHAB:
5520             new.pclass = "M"        # All inhabited planets are class M
5521             new.crystals = "absent"
5522             new.known = "known"
5523             new.name = systnames[i]
5524             new.inhabited = True
5525         else:
5526             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5527             if withprob(0.33):
5528                 new.crystals = "present"
5529             new.known = "unknown"
5530             new.inhabited = False
5531         game.state.galaxy[w.i][w.j].planet = new
5532         game.state.planets.append(new)
5533     # Locate Romulans
5534     for i in range(game.state.nromrem):
5535         w = randplace(GALSIZE)
5536         game.state.galaxy[w.i][w.j].romulans += 1
5537     # Place the Super-Commander if needed
5538     if game.state.nscrem > 0:
5539         while True:
5540             w = randplace(GALSIZE)
5541             if welcoming(w):
5542                 break
5543         game.state.kscmdr = w
5544         game.state.galaxy[w.i][w.j].klingons += 1
5545     # Initialize times for extraneous events
5546     schedule(FSNOVA, expran(0.5 * game.intime))
5547     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5548     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5549     schedule(FBATTAK, expran(0.3*game.intime))
5550     unschedule(FCDBAS)
5551     if game.state.nscrem:
5552         schedule(FSCMOVE, 0.2777)
5553     else:
5554         unschedule(FSCMOVE)
5555     unschedule(FSCDBAS)
5556     unschedule(FDSPROB)
5557     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5558         schedule(FDISTR, expran(1.0 + game.intime))
5559     else:
5560         unschedule(FDISTR)
5561     unschedule(FENSLV)
5562     unschedule(FREPRO)
5563     # Place thing (in tournament game, we don't want one!)
5564     # New in SST2K: never place the Thing near a starbase.
5565     # This makes sense and avoids a special case in the old code.
5566     global thing
5567     if game.tourn is None:
5568         while True:
5569             thing = randplace(GALSIZE)
5570             if thing not in game.state.baseq:
5571                 break
5572     skip(2)
5573     game.state.snap = False
5574     if game.skill == SKILL_NOVICE:
5575         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5576         prout(_("a deadly Klingon invasion force. As captain of the United"))
5577         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5578         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5579         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5580         prout(_("your mission.  As you proceed you may be given more time."))
5581         skip(1)
5582         prout(_("You will have %d supporting starbases.") % (game.inbase))
5583         proutn(_("Starbase locations-  "))
5584     else:
5585         prout(_("Stardate %d.") % int(game.state.date))
5586         skip(1)
5587         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5588         prout(_("An unknown number of Romulans."))
5589         if game.state.nscrem:
5590             prout(_("And one (GULP) Super-Commander."))
5591         prout(_("%d stardates.") % int(game.intime))
5592         proutn(_("%d starbases in ") % game.inbase)
5593     for i in range(game.inbase):
5594         proutn(repr(game.state.baseq[i]))
5595         proutn("  ")
5596     skip(2)
5597     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5598     proutn(_(" Sector %s") % game.sector)
5599     skip(2)
5600     prout(_("Good Luck!"))
5601     if game.state.nscrem:
5602         prout(_("  YOU'LL NEED IT."))
5603     waitfor()
5604     clrscr()
5605     setwnd(message_window)
5606     newqad()
5607     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5608         game.shldup = True
5609     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5610         attack(torps_ok=False)
5611
5612 def choose():
5613     "Choose your game type."
5614     while True:
5615         game.tourn = game.length = 0
5616         game.thawed = False
5617         game.skill = SKILL_NONE
5618         scanner.chew()
5619 #        if not scanner.inqueue: # Can start with command line options
5620         proutn(_("Would you like a regular, tournament, or saved game? "))
5621         scanner.nexttok()
5622         if scanner.sees("tournament"):
5623             while scanner.nexttok() == "IHEOL":
5624                 proutn(_("Type in tournament number-"))
5625             if scanner.real == 0:
5626                 scanner.chew()
5627                 continue # We don't want a blank entry
5628             game.tourn = int(round(scanner.real))
5629             random.seed(scanner.real)
5630             if logfp:
5631                 logfp.write("# random.seed(%d)\n" % scanner.real)
5632             break
5633         if scanner.sees("saved") or scanner.sees("frozen"):
5634             if thaw():
5635                 continue
5636             scanner.chew()
5637             if game.passwd is None:
5638                 continue
5639             if not game.alldone:
5640                 game.thawed = True # No plaque if not finished
5641             report()
5642             waitfor()
5643             return True
5644         if scanner.sees("regular"):
5645             break
5646         proutn(_("What is \"%s\"? ") % scanner.token)
5647         scanner.chew()
5648     while game.length==0 or game.skill==SKILL_NONE:
5649         if scanner.nexttok() == "IHALPHA":
5650             if scanner.sees("short"):
5651                 game.length = 1
5652             elif scanner.sees("medium"):
5653                 game.length = 2
5654             elif scanner.sees("long"):
5655                 game.length = 4
5656             elif scanner.sees("novice"):
5657                 game.skill = SKILL_NOVICE
5658             elif scanner.sees("fair"):
5659                 game.skill = SKILL_FAIR
5660             elif scanner.sees("good"):
5661                 game.skill = SKILL_GOOD
5662             elif scanner.sees("expert"):
5663                 game.skill = SKILL_EXPERT
5664             elif scanner.sees("emeritus"):
5665                 game.skill = SKILL_EMERITUS
5666             else:
5667                 proutn(_("What is \""))
5668                 proutn(scanner.token)
5669                 prout("\"?")
5670         else:
5671             scanner.chew()
5672             if game.length==0:
5673                 proutn(_("Would you like a Short, Medium, or Long game? "))
5674             elif game.skill == SKILL_NONE:
5675                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5676     # Choose game options -- added by ESR for SST2K
5677     if scanner.nexttok() != "IHALPHA":
5678         scanner.chew()
5679         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5680         scanner.nexttok()
5681     if scanner.sees("plain"):
5682         # Approximates the UT FORTRAN version.
5683         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5684         game.options |= OPTION_PLAIN
5685     elif scanner.sees("almy"):
5686         # Approximates Tom Almy's version.
5687         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5688         game.options |= OPTION_ALMY
5689     elif scanner.sees("fancy") or scanner.sees("\n"):
5690         pass
5691     elif len(scanner.token):
5692         proutn(_("What is \"%s\"?") % scanner.token)
5693     game.options &=~ OPTION_COLOR
5694     setpassword()
5695     if game.passwd == "debug":
5696         game.idebug = True
5697         prout("=== Debug mode enabled.")
5698     # Use parameters to generate initial values of things
5699     game.damfac = 0.5 * game.skill
5700     game.inbase = randrange(BASEMIN, BASEMAX+1)
5701     game.inplan = 0
5702     if game.options & OPTION_PLANETS:
5703         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5704     if game.options & OPTION_WORLDS:
5705         game.inplan += int(NINHAB)
5706     game.state.nromrem = game.inrom = randrange(2 *game.skill)
5707     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5708     game.state.remtime = 7.0 * game.length
5709     game.intime = game.state.remtime
5710     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5711     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5712     game.state.remres = (game.inkling+4*game.incom)*game.intime
5713     game.inresor = game.state.remres
5714     if game.inkling > 50:
5715         game.inbase += 1
5716     return False
5717
5718 def dropin(iquad=None):
5719     "Drop a feature on a random dot in the current quadrant."
5720     while True:
5721         w = randplace(QUADSIZE)
5722         if game.quad[w.i][w.j] == '.':
5723             break
5724     if iquad is not None:
5725         game.quad[w.i][w.j] = iquad
5726     return w
5727
5728 def newcnd():
5729     "Update our alert status."
5730     game.condition = "green"
5731     if game.energy < 1000.0:
5732         game.condition = "yellow"
5733     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5734         game.condition = "red"
5735     if not game.alive:
5736         game.condition="dead"
5737
5738 def newkling():
5739     "Drop new Klingon into current quadrant."
5740     return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5741
5742 def sortenemies():
5743     "Sort enemies by distance so 'nearest' is meaningful."
5744     game.enemies.sort(key=lambda x: x.kdist)
5745
5746 def newqad():
5747     "Set up a new state of quadrant, for when we enter or re-enter it."
5748     game.justin = True
5749     game.iplnet = None
5750     game.neutz = game.inorbit = game.landed = False
5751     game.ientesc = game.iseenit = False
5752     # Create a blank quadrant
5753     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
5754     if game.iscate:
5755         # Attempt to escape Super-commander, so tbeam back!
5756         game.iscate = False
5757         game.ientesc = True
5758     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5759     # cope with supernova
5760     if q.supernova:
5761         return
5762     game.klhere = q.klingons
5763     game.irhere = q.romulans
5764     # Position Starship
5765     game.quad[game.sector.i][game.sector.j] = game.ship
5766     game.enemies = []
5767     if q.klingons:
5768         # Position ordinary Klingons
5769         for _i in range(game.klhere):
5770             newkling()
5771         # If we need a commander, promote a Klingon
5772         for cmdr in game.state.kcmdr:
5773             if cmdr == game.quadrant:
5774                 e = game.enemies[game.klhere-1]
5775                 game.quad[e.location.i][e.location.j] = 'C'
5776                 e.power = randreal(950,1350) + 50.0*game.skill
5777                 break
5778         # If we need a super-commander, promote a Klingon
5779         if game.quadrant == game.state.kscmdr:
5780             e = game.enemies[0]
5781             game.quad[e.location.i][e.location.j] = 'S'
5782             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
5783             game.iscate = (game.state.remkl > 1)
5784     # Put in Romulans if needed
5785     for _i in range(q.romulans):
5786         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
5787     # If quadrant needs a starbase, put it in
5788     if q.starbase:
5789         game.base = dropin('B')
5790     # If quadrant needs a planet, put it in
5791     if q.planet:
5792         game.iplnet = q.planet
5793         if not q.planet.inhabited:
5794             game.plnet = dropin('P')
5795         else:
5796             game.plnet = dropin('@')
5797     # Check for condition
5798     newcnd()
5799     # Check for RNZ
5800     if game.irhere > 0 and game.klhere == 0:
5801         game.neutz = True
5802         if not damaged(DRADIO):
5803             skip(1)
5804             prout(_("LT. Uhura- \"Captain, an urgent message."))
5805             prout(_("  I'll put it on audio.\"  CLICK"))
5806             skip(1)
5807             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
5808             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
5809     # Put in THING if needed
5810     if thing == game.quadrant:
5811         Enemy(etype='?', loc=dropin(),
5812               power=randreal(6000,6500.0)+250.0*game.skill)
5813         if not damaged(DSRSENS):
5814             skip(1)
5815             prout(_("Mr. Spock- \"Captain, this is most unusual."))
5816             prout(_("    Please examine your short-range scan.\""))
5817     # Decide if quadrant needs a Tholian; lighten up if skill is low
5818     if game.options & OPTION_THOLIAN:
5819         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
5820             (game.skill == SKILL_GOOD and withprob(0.05)) or \
5821             (game.skill > SKILL_GOOD and withprob(0.08)):
5822             w = Coord()
5823             while True:
5824                 w.i = withprob(0.5) * (QUADSIZE-1)
5825                 w.j = withprob(0.5) * (QUADSIZE-1)
5826                 if game.quad[w.i][w.j] == '.':
5827                     break
5828             game.tholian = Enemy(etype='T', loc=w,
5829                                  power=randrange(100, 500) + 25.0*game.skill)
5830             # Reserve unoccupied corners
5831             if game.quad[0][0]=='.':
5832                 game.quad[0][0] = 'X'
5833             if game.quad[0][QUADSIZE-1]=='.':
5834                 game.quad[0][QUADSIZE-1] = 'X'
5835             if game.quad[QUADSIZE-1][0]=='.':
5836                 game.quad[QUADSIZE-1][0] = 'X'
5837             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
5838                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
5839     sortenemies()
5840     # And finally the stars
5841     for _i in range(q.stars):
5842         dropin('*')
5843     # Put in a few black holes
5844     for _i in range(1, 3+1):
5845         if withprob(0.5):
5846             dropin(' ')
5847     # Take out X's in corners if Tholian present
5848     if game.tholian:
5849         if game.quad[0][0]=='X':
5850             game.quad[0][0] = '.'
5851         if game.quad[0][QUADSIZE-1]=='X':
5852             game.quad[0][QUADSIZE-1] = '.'
5853         if game.quad[QUADSIZE-1][0]=='X':
5854             game.quad[QUADSIZE-1][0] = '.'
5855         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
5856             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
5857
5858 def setpassword():
5859     "Set the self-destruct password."
5860     if game.options & OPTION_PLAIN:
5861         while True:
5862             scanner.chew()
5863             proutn(_("Please type in a secret password- "))
5864             scanner.nexttok()
5865             game.passwd = scanner.token
5866             if game.passwd != None:
5867                 break
5868     else:
5869         game.passwd = ""
5870         game.passwd += chr(ord('a')+randrange(26))
5871         game.passwd += chr(ord('a')+randrange(26))
5872         game.passwd += chr(ord('a')+randrange(26))
5873
5874 # Code from sst.c begins here
5875
5876 commands = [
5877     ("SRSCAN",           OPTION_TTY),
5878     ("STATUS",           OPTION_TTY),
5879     ("REQUEST",          OPTION_TTY),
5880     ("LRSCAN",           OPTION_TTY),
5881     ("PHASERS",          0),
5882     ("TORPEDO",          0),
5883     ("PHOTONS",          0),
5884     ("MOVE",             0),
5885     ("SHIELDS",          0),
5886     ("DOCK",             0),
5887     ("DAMAGES",          0),
5888     ("CHART",            0),
5889     ("IMPULSE",          0),
5890     ("REST",             0),
5891     ("WARP",             0),
5892     ("SCORE",            0),
5893     ("SENSORS",          OPTION_PLANETS),
5894     ("ORBIT",            OPTION_PLANETS),
5895     ("TRANSPORT",        OPTION_PLANETS),
5896     ("MINE",             OPTION_PLANETS),
5897     ("CRYSTALS",         OPTION_PLANETS),
5898     ("SHUTTLE",          OPTION_PLANETS),
5899     ("PLANETS",          OPTION_PLANETS),
5900     ("REPORT",           0),
5901     ("COMPUTER",         0),
5902     ("COMMANDS",         0),
5903     ("EMEXIT",           0),
5904     ("PROBE",            OPTION_PROBE),
5905     ("SAVE",             0),
5906     ("FREEZE",           0),        # Synonym for SAVE
5907     ("ABANDON",          0),
5908     ("DESTRUCT",         0),
5909     ("DEATHRAY",         0),
5910     ("DEBUG",            0),
5911     ("MAYDAY",           0),
5912     ("SOS",              0),        # Synonym for MAYDAY
5913     ("CALL",             0),        # Synonym for MAYDAY
5914     ("QUIT",             0),
5915     ("HELP",             0),
5916     ("",                 0),
5917 ]
5918
5919 def listCommands():
5920     "Generate a list of legal commands."
5921     prout(_("LEGAL COMMANDS ARE:"))
5922     emitted = 0
5923     for (key, opt) in commands:
5924         if not opt or (opt & game.options):
5925             proutn("%-12s " % key)
5926             emitted += 1
5927             if emitted % 5 == 4:
5928                 skip(1)
5929     skip(1)
5930
5931 def helpme():
5932     "Browse on-line help."
5933     key = scanner.nexttok()
5934     while True:
5935         if key == "IHEOL":
5936             setwnd(prompt_window)
5937             proutn(_("Help on what command? "))
5938             key = scanner.nexttok()
5939         setwnd(message_window)
5940         if key == "IHEOL":
5941             return
5942         cmds = [x[0] for x in commands]
5943         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
5944             break
5945         skip(1)
5946         listCommands()
5947         key = "IHEOL"
5948         scanner.chew()
5949         skip(1)
5950     cmd = scanner.token.upper()
5951     for directory in docpath:
5952         try:
5953             fp = open(os.path.join(directory, "sst.doc"), "r")
5954             break
5955         except IOError:
5956             pass
5957     else:
5958         prout(_("Spock-  \"Captain, that information is missing from the"))
5959         prout(_("   computer. You need to find sst.doc and put it somewhere"))
5960         proutn(_("   in these directories: %s") % ":".join(docpath))
5961         prout(".\"")
5962         # This used to continue: "You need to find SST.DOC and put
5963         # it in the current directory."
5964         return
5965     while True:
5966         linebuf = fp.readline()
5967         if linebuf == '':
5968             prout(_("Spock- \"Captain, there is no information on that command.\""))
5969             fp.close()
5970             return
5971         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
5972             linebuf = linebuf[3:].strip()
5973             if cmd.upper() == linebuf:
5974                 break
5975     skip(1)
5976     prout(_("Spock- \"Captain, I've found the following information:\""))
5977     skip(1)
5978     while True:
5979         linebuf = fp.readline()
5980         if "******" in linebuf:
5981             break
5982         proutn(linebuf)
5983     fp.close()
5984
5985 def makemoves():
5986     "Command-interpretation loop."
5987     while True:         # command loop
5988         drawmaps(1)
5989         while True:        # get a command
5990             hitme = False
5991             game.optime = game.justin = False
5992             scanner.chew()
5993             setwnd(prompt_window)
5994             clrscr()
5995             proutn("COMMAND> ")
5996             if scanner.nexttok() == "IHEOL":
5997                 if game.options & OPTION_CURSES:
5998                     makechart()
5999                 continue
6000             elif scanner.token == "":
6001                 continue
6002             game.ididit = False
6003             clrscr()
6004             setwnd(message_window)
6005             clrscr()
6006             abandon_passed = False
6007             cmd = ""    # Force cmd to persist after loop
6008             opt = 0     # Force opt to persist after loop
6009             for (cmd, opt) in commands:
6010                 # commands after ABANDON cannot be abbreviated
6011                 if cmd == "ABANDON":
6012                     abandon_passed = True
6013                 if cmd == scanner.token.upper() or (not abandon_passed \
6014                         and cmd.startswith(scanner.token.upper())):
6015                     break
6016             if cmd == "":
6017                 listCommands()
6018                 continue
6019             elif opt and not (opt & game.options):
6020                 huh()
6021             else:
6022                 break
6023         if cmd == "SRSCAN":                # srscan
6024             srscan()
6025         elif cmd == "STATUS":                # status
6026             status()
6027         elif cmd == "REQUEST":                # status request
6028             request()
6029         elif cmd == "LRSCAN":                # long range scan
6030             lrscan(silent=False)
6031         elif cmd == "PHASERS":                # phasers
6032             phasers()
6033             if game.ididit:
6034                 hitme = True
6035         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6036             torps()
6037             if game.ididit:
6038                 hitme = True
6039         elif cmd == "MOVE":                # move under warp
6040             warp(wcourse=None, involuntary=False)
6041         elif cmd == "SHIELDS":                # shields
6042             doshield(shraise=False)
6043             if game.ididit:
6044                 hitme = True
6045                 game.shldchg = False
6046         elif cmd == "DOCK":                # dock at starbase
6047             dock(True)
6048             if game.ididit:
6049                 attack(torps_ok=False)
6050         elif cmd == "DAMAGES":                # damage reports
6051             damagereport()
6052         elif cmd == "CHART":                # chart
6053             makechart()
6054         elif cmd == "IMPULSE":                # impulse
6055             impulse()
6056         elif cmd == "REST":                # rest
6057             wait()
6058             if game.ididit:
6059                 hitme = True
6060         elif cmd == "WARP":                # warp
6061             setwarp()
6062         elif cmd == "SCORE":                # score
6063             score()
6064         elif cmd == "SENSORS":                # sensors
6065             sensor()
6066         elif cmd == "ORBIT":                # orbit
6067             orbit()
6068             if game.ididit:
6069                 hitme = True
6070         elif cmd == "TRANSPORT":                # transport "beam"
6071             beam()
6072         elif cmd == "MINE":                # mine
6073             mine()
6074             if game.ididit:
6075                 hitme = True
6076         elif cmd == "CRYSTALS":                # crystals
6077             usecrystals()
6078             if game.ididit:
6079                 hitme = True
6080         elif cmd == "SHUTTLE":                # shuttle
6081             shuttle()
6082             if game.ididit:
6083                 hitme = True
6084         elif cmd == "PLANETS":                # Planet list
6085             survey()
6086         elif cmd == "REPORT":                # Game Report
6087             report()
6088         elif cmd == "COMPUTER":                # use COMPUTER!
6089             eta()
6090         elif cmd == "COMMANDS":
6091             listCommands()
6092         elif cmd == "EMEXIT":                # Emergency exit
6093             clrscr()                        # Hide screen
6094             freeze(True)                # forced save
6095             raise SystemExit(1)                # And quick exit
6096         elif cmd == "PROBE":
6097             probe()                        # Launch probe
6098             if game.ididit:
6099                 hitme = True
6100         elif cmd == "ABANDON":                # Abandon Ship
6101             abandon()
6102         elif cmd == "DESTRUCT":                # Self Destruct
6103             selfdestruct()
6104         elif cmd == "SAVE":                # Save Game
6105             freeze(False)
6106             clrscr()
6107             if game.skill > SKILL_GOOD:
6108                 prout(_("WARNING--Saved games produce no plaques!"))
6109         elif cmd == "DEATHRAY":                # Try a desparation measure
6110             deathray()
6111             if game.ididit:
6112                 hitme = True
6113         elif cmd == "DEBUGCMD":                # What do we want for debug???
6114             debugme()
6115         elif cmd == "MAYDAY":                # Call for help
6116             mayday()
6117             if game.ididit:
6118                 hitme = True
6119         elif cmd == "QUIT":
6120             game.alldone = True                # quit the game
6121         elif cmd == "HELP":
6122             helpme()                        # get help
6123         while True:
6124             if game.alldone:
6125                 break                # Game has ended
6126             if game.optime != 0.0:
6127                 events()
6128                 if game.alldone:
6129                     break        # Events did us in
6130             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6131                 atover(False)
6132                 continue
6133             if hitme and not game.justin:
6134                 attack(torps_ok=True)
6135                 if game.alldone:
6136                     break
6137                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6138                     atover(False)
6139                     hitme = True
6140                     continue
6141             break
6142         if game.alldone:
6143             break
6144     if game.idebug:
6145         prout("=== Ending")
6146
6147 def cramen(ch):
6148     "Emit the name of an enemy or feature."
6149     if   ch == 'R': s = _("Romulan")
6150     elif ch == 'K': s = _("Klingon")
6151     elif ch == 'C': s = _("Commander")
6152     elif ch == 'S': s = _("Super-commander")
6153     elif ch == '*': s = _("Star")
6154     elif ch == 'P': s = _("Planet")
6155     elif ch == 'B': s = _("Starbase")
6156     elif ch == ' ': s = _("Black hole")
6157     elif ch == 'T': s = _("Tholian")
6158     elif ch == '#': s = _("Tholian web")
6159     elif ch == '?': s = _("Stranger")
6160     elif ch == '@': s = _("Inhabited World")
6161     else: s = "Unknown??"
6162     return s
6163
6164 def crmena(loud, enemy, loctype, w):
6165     "Emit the name of an enemy and his location."
6166     buf = ""
6167     if loud:
6168         buf += "***"
6169     buf += cramen(enemy) + _(" at ")
6170     if loctype == "quadrant":
6171         buf += _("Quadrant ")
6172     elif loctype == "sector":
6173         buf += _("Sector ")
6174     return buf + repr(w)
6175
6176 def crmshp():
6177     "Emit our ship name."
6178     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6179
6180 def stars():
6181     "Emit a line of stars"
6182     prouts("******************************************************")
6183     skip(1)
6184
6185 def expran(avrage):
6186     return -avrage*math.log(1e-7 + randreal())
6187
6188 def randplace(size):
6189     "Choose a random location."
6190     w = Coord()
6191     w.i = randrange(size)
6192     w.j = randrange(size)
6193     return w
6194
6195 class sstscanner:
6196     def __init__(self):
6197         self.type = None
6198         self.token = None
6199         self.real = 0.0
6200         self.inqueue = []
6201     def nexttok(self):
6202         # Get a token from the user
6203         self.real = 0.0
6204         self.token = ''
6205         # Fill the token quue if nothing here
6206         while not self.inqueue:
6207             sline = cgetline()
6208             if curwnd==prompt_window:
6209                 clrscr()
6210                 setwnd(message_window)
6211                 clrscr()
6212             if sline == '':
6213                 return None
6214             if not sline:
6215                 continue
6216             else:
6217                 self.inqueue = sline.lstrip().split() + ["\n"]
6218         # From here on in it's all looking at the queue
6219         self.token = self.inqueue.pop(0)
6220         if self.token == "\n":
6221             self.type = "IHEOL"
6222             return "IHEOL"
6223         try:
6224             self.real = float(self.token)
6225             self.type = "IHREAL"
6226             return "IHREAL"
6227         except ValueError:
6228             pass
6229         # Treat as alpha
6230         self.token = self.token.lower()
6231         self.type = "IHALPHA"
6232         self.real = None
6233         return "IHALPHA"
6234     def append(self, tok):
6235         self.inqueue.append(tok)
6236     def push(self, tok):
6237         self.inqueue.insert(0, tok)
6238     def waiting(self):
6239         return self.inqueue
6240     def chew(self):
6241         # Demand input for next scan
6242         self.inqueue = []
6243         self.real = self.token = None
6244     def sees(self, s):
6245         # compares s to item and returns true if it matches to the length of s
6246         return s.startswith(self.token)
6247     def int(self):
6248         # Round token value to nearest integer
6249         return int(round(self.real))
6250     def getcoord(self):
6251         s = Coord()
6252         self.nexttok()
6253         if self.type != "IHREAL":
6254             huh()
6255             return None
6256         s.i = self.int()-1
6257         self.nexttok()
6258         if self.type != "IHREAL":
6259             huh()
6260             return None
6261         s.j = self.int()-1
6262         return s
6263     def __repr__(self):
6264         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6265
6266 def ja():
6267     "Yes-or-no confirmation."
6268     scanner.chew()
6269     while True:
6270         scanner.nexttok()
6271         if scanner.token == 'y':
6272             return True
6273         if scanner.token == 'n':
6274             return False
6275         scanner.chew()
6276         proutn(_("Please answer with \"y\" or \"n\": "))
6277
6278 def huh():
6279     "Complain about unparseable input."
6280     scanner.chew()
6281     skip(1)
6282     prout(_("Beg your pardon, Captain?"))
6283
6284 def debugme():
6285     "Access to the internals for debugging."
6286     proutn("Reset levels? ")
6287     if ja():
6288         if game.energy < game.inenrg:
6289             game.energy = game.inenrg
6290         game.shield = game.inshld
6291         game.torps = game.intorps
6292         game.lsupres = game.inlsr
6293     proutn("Reset damage? ")
6294     if ja():
6295         for i in range(NDEVICES):
6296             if game.damage[i] > 0.0:
6297                 game.damage[i] = 0.0
6298     proutn("Toggle debug flag? ")
6299     if ja():
6300         game.idebug = not game.idebug
6301         if game.idebug:
6302             prout("Debug output ON")
6303         else:
6304             prout("Debug output OFF")
6305     proutn("Cause selective damage? ")
6306     if ja():
6307         for i in range(NDEVICES):
6308             proutn("Kill %s?" % device[i])
6309             scanner.chew()
6310             key = scanner.nexttok()
6311             if key == "IHALPHA" and scanner.sees("y"):
6312                 game.damage[i] = 10.0
6313     proutn("Examine/change events? ")
6314     if ja():
6315         ev = Event()
6316         w = Coord()
6317         legends = {
6318             FSNOVA:  "Supernova       ",
6319             FTBEAM:  "T Beam          ",
6320             FSNAP:   "Snapshot        ",
6321             FBATTAK: "Base Attack     ",
6322             FCDBAS:  "Base Destroy    ",
6323             FSCMOVE: "SC Move         ",
6324             FSCDBAS: "SC Base Destroy ",
6325             FDSPROB: "Probe Move      ",
6326             FDISTR:  "Distress Call   ",
6327             FENSLV:  "Enslavement     ",
6328             FREPRO:  "Klingon Build   ",
6329         }
6330         for i in range(1, NEVENTS):
6331             proutn(legends[i])
6332             if is_scheduled(i):
6333                 proutn("%.2f" % (scheduled(i)-game.state.date))
6334                 if i == FENSLV or i == FREPRO:
6335                     ev = findevent(i)
6336                     proutn(" in %s" % ev.quadrant)
6337             else:
6338                 proutn("never")
6339             proutn("? ")
6340             scanner.chew()
6341             key = scanner.nexttok()
6342             if key == 'n':
6343                 unschedule(i)
6344                 scanner.chew()
6345             elif key == "IHREAL":
6346                 ev = schedule(i, scanner.real)
6347                 if i == FENSLV or i == FREPRO:
6348                     scanner.chew()
6349                     proutn("In quadrant- ")
6350                     key = scanner.nexttok()
6351                     # "IHEOL" says to leave coordinates as they are
6352                     if key != "IHEOL":
6353                         if key != "IHREAL":
6354                             prout("Event %d canceled, no x coordinate." % (i))
6355                             unschedule(i)
6356                             continue
6357                         w.i = int(round(scanner.real))
6358                         key = scanner.nexttok()
6359                         if key != "IHREAL":
6360                             prout("Event %d canceled, no y coordinate." % (i))
6361                             unschedule(i)
6362                             continue
6363                         w.j = int(round(scanner.real))
6364                         ev.quadrant = w
6365         scanner.chew()
6366     proutn("Induce supernova here? ")
6367     if ja():
6368         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6369         atover(True)
6370
6371 if __name__ == '__main__':
6372     import getopt, socket
6373     try:
6374         #global line, thing, game
6375         game = None
6376         thing = Thingy()
6377         game = Gamestate()
6378         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6379         if os.getenv("TERM"):
6380             game.options |= OPTION_CURSES
6381         else:
6382             game.options |= OPTION_TTY
6383         seed = int(time.time())
6384         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
6385         replay = False
6386         for (switch, val) in options:
6387             if switch == '-r':
6388                 try:
6389                     replayfp = open(val, "r")
6390                 except IOError:
6391                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6392                     raise SystemExit(1)
6393                 try:
6394                     line = replayfp.readline().strip()
6395                     (leader, __, seed) = line.split()
6396                     seed = eval(seed)
6397                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6398                     line = replayfp.readline().strip()
6399                     arguments += line.split()[2:]
6400                     replay = True
6401                 except ValueError:
6402                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6403                     raise SystemExit(1)
6404                 game.options |= OPTION_TTY
6405                 game.options &=~ OPTION_CURSES
6406             elif switch == '-s':
6407                 seed = int(val)
6408             elif switch == '-t':
6409                 game.options |= OPTION_TTY
6410                 game.options &=~ OPTION_CURSES
6411             elif switch == '-x':
6412                 game.idebug = True
6413             elif switch == '-V':
6414                 print("SST2K", version)
6415                 raise SystemExit(0)
6416             else:
6417                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6418                 raise SystemExit(1)
6419         # where to save the input in case of bugs
6420         if "TMPDIR" in os.environ:
6421             tmpdir = os.environ['TMPDIR']
6422         else:
6423             tmpdir = "/tmp"
6424         try:
6425             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6426         except IOError:
6427             sys.stderr.write("sst: warning, can't open logfile\n")
6428             sys.exit(1)
6429         if logfp:
6430             logfp.write("# seed %s\n" % seed)
6431             logfp.write("# options %s\n" % " ".join(arguments))
6432             logfp.write("# SST2K version %s\n" % version)
6433             logfp.write("# recorded by %s@%s on %s\n" % \
6434                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6435         random.seed(seed)
6436         scanner = sstscanner()
6437         for arg in arguments:
6438             scanner.append(arg)
6439         try:
6440             iostart()
6441             while True: # Play a game
6442                 setwnd(fullscreen_window)
6443                 clrscr()
6444                 prelim()
6445                 setup()
6446                 if game.alldone:
6447                     score()
6448                     game.alldone = False
6449                 else:
6450                     makemoves()
6451                 if replay:
6452                     break
6453                 skip(1)
6454                 stars()
6455                 skip(1)
6456                 if game.tourn and game.alldone:
6457                     proutn(_("Do you want your score recorded?"))
6458                     if ja():
6459                         scanner.chew()
6460                         scanner.push("\n")
6461                         freeze(False)
6462                 scanner.chew()
6463                 proutn(_("Do you want to play again? "))
6464                 if not ja():
6465                     break
6466             skip(1)
6467             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6468         finally:
6469             ioend()
6470         raise SystemExit(0)
6471     except KeyboardInterrupt:
6472         if logfp:
6473             logfp.close()
6474         print("")
6475
6476 # End.