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