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