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