More vector-arithmetic abstraction.
[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 # How to represent features
205 IHR = 'R',
206 IHK = 'K',
207 IHC = 'C',
208 IHS = 'S',
209 IHSTAR = '*',
210 IHP = 'P',
211 IHW = '@',
212 IHB = 'B',
213 IHBLANK = ' ',
214 IHDOT = '.',
215 IHQUEST = '?',
216 IHE = 'E',
217 IHF = 'F',
218 IHT = 'T',
219 IHWEB = '#',
220 IHMATER0 = '-',
221 IHMATER1 = 'o',
222 IHMATER2 = '0'
223
224 class TrekError:
225     pass
226
227 class coord:
228     def __init__(self, x=None, y=None):
229         self.i = x
230         self.j = y
231     def valid_quadrant(self):
232         return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
233     def valid_sector(self):
234         return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
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 __mod__(self, other):
254         return coord(self.i % other, self.j % other)
255     def __rdiv__(self, other):
256         return coord(self.i/other, self.j/other)
257     def roundtogrid(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 quadrant(self):
277         return (self / QUADSIZE).roundtogrid()
278     def sector(self):
279         return self.roundtogrid() % QUADSIZE
280     def scatter(self):
281         s = coord()
282         s.i = self.i + randrange(-1, 2)
283         s.j = self.j + randrange(-1, 2)
284         return s
285     def __str__(self):
286         if self.i == None or self.j == None:
287             return "Nowhere"
288         return "%s - %s" % (self.i+1, self.j+1)
289     __repr__ = __str__
290
291 class planet:
292     def __init__(self):
293         self.name = None        # string-valued if inhabited
294         self.quadrant = coord() # quadrant located
295         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
296         self.crystals = "absent"# could be "mined", "present", "absent"
297         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
298         self.inhabited = False  # is it inhabites?
299     def __str__(self):
300         return self.name
301
302 class quadrant:
303     def __init__(self):
304         self.stars = 0
305         self.planet = None
306         self.starbase = False
307         self.klingons = 0
308         self.romulans = 0
309         self.supernova = False
310         self.charted = False
311         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
312
313 class page:
314     def __init__(self):
315         self.stars = None
316         self.starbase = None
317         self.klingons = None
318
319 def fill2d(size, fillfun):
320     "Fill an empty list in 2D."
321     lst = []
322     for i in range(size):
323         lst.append([]) 
324         for j in range(size):
325             lst[i].append(fillfun(i, j))
326     return lst
327
328 class snapshot:
329     def __init__(self):
330         self.snap = False       # snapshot taken
331         self.crew = 0           # crew complement
332         self.remkl = 0          # remaining klingons
333         self.nscrem = 0         # remaining super commanders
334         self.starkl = 0         # destroyed stars
335         self.basekl = 0         # destroyed bases
336         self.nromrem = 0        # Romulans remaining
337         self.nplankl = 0        # destroyed uninhabited planets
338         self.nworldkl = 0       # destroyed inhabited planets
339         self.planets = []       # Planet information
340         self.date = 0.0         # stardate
341         self.remres = 0         # remaining resources
342         self.remtime = 0        # remaining time
343         self.baseq = []         # Base quadrant coordinates
344         self.kcmdr = []         # Commander quadrant coordinates
345         self.kscmdr = coord()   # Supercommander quadrant coordinates
346         # the galaxy
347         self.galaxy = fill2d(GALSIZE, lambda i, j: quadrant())
348         # the starchart
349         self.chart = fill2d(GALSIZE, lambda i, j: page())
350
351 class event:
352     def __init__(self):
353         self.date = None        # A real number
354         self.quadrant = None    # A coord structure
355
356 # game options 
357 OPTION_ALL      = 0xffffffff
358 OPTION_TTY      = 0x00000001    # old interface 
359 OPTION_CURSES   = 0x00000002    # new interface 
360 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
361 OPTION_PLANETS  = 0x00000004    # planets and mining 
362 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs (UT 1979 version)
363 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back (Stas, 2005)
364 OPTION_PROBE    = 0x00000020    # deep-space probes (DECUS version, 1980)
365 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
366 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise (Almy)
367 OPTION_MVBADDY  = 0x00000100    # more enemies can move (Almy)
368 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you (Stas, 2005) 
369 OPTION_BASE     = 0x00000400    # bases have good shields (Stas, 2005)
370 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds (ESR, 2006)
371 OPTION_AUTOSCAN = 0x00001000    # automatic LRSCAN before CHART (ESR, 2006)
372 OPTION_PLAIN    = 0x01000000    # user chose plain game 
373 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
374
375 # Define devices 
376 DSRSENS = 0
377 DLRSENS = 1
378 DPHASER = 2
379 DPHOTON = 3
380 DLIFSUP = 4
381 DWARPEN = 5
382 DIMPULS = 6
383 DSHIELD = 7
384 DRADIO  = 0
385 DSHUTTL = 9
386 DCOMPTR = 10
387 DNAVSYS = 11
388 DTRANSP = 12
389 DSHCTRL = 13
390 DDRAY   = 14
391 DDSP    = 15
392 NDEVICES= 16    # Number of devices
393
394 SKILL_NONE      = 0
395 SKILL_NOVICE    = 1
396 SKILL_FAIR      = 2
397 SKILL_GOOD      = 3
398 SKILL_EXPERT    = 4
399 SKILL_EMERITUS  = 5
400
401 def damaged(dev):       return (game.damage[dev] != 0.0)
402 def communicating():    return not damaged(DRADIO) or game.condition=="docked"
403
404 # Define future events 
405 FSPY    = 0     # Spy event happens always (no future[] entry)
406                 # can cause SC to tractor beam Enterprise
407 FSNOVA  = 1     # Supernova
408 FTBEAM  = 2     # Commander tractor beams Enterprise
409 FSNAP   = 3     # Snapshot for time warp
410 FBATTAK = 4     # Commander attacks base
411 FCDBAS  = 5     # Commander destroys base
412 FSCMOVE = 6     # Supercommander moves (might attack base)
413 FSCDBAS = 7     # Supercommander destroys base
414 FDSPROB = 8     # Move deep space probe
415 FDISTR  = 9     # Emit distress call from an inhabited world 
416 FENSLV  = 10    # Inhabited word is enslaved */
417 FREPRO  = 11    # Klingons build a ship in an enslaved system
418 NEVENTS = 12
419
420 #
421 # abstract out the event handling -- underlying data structures will change
422 # when we implement stateful events
423
424 def findevent(evtype):  return game.future[evtype]
425
426 class enemy:
427     def __init__(self, type=None, loc=None, power=None):
428         self.type = type
429         self.kloc = coord()
430         if loc:
431             self.move(loc)
432         self.kpower = power     # enemy energy level
433         game.enemies.append(self)
434     def move(self, loc):
435         motion = (loc != self.kloc)
436         if self.kloc.i is not None and self.kloc.j is not None:
437             if motion:
438                 if self.type == IHT:
439                     game.quad[self.kloc.i][self.kloc.j] = IHWEB
440                 else:
441                     game.quad[self.kloc.i][self.kloc.j] = IHDOT
442         if loc:
443             self.kloc = copy.copy(loc)
444             game.quad[self.kloc.i][self.kloc.j] = self.type
445             self.kdist = self.kavgd = (game.sector - loc).distance()
446         else:
447             self.kloc = coord()
448             self.kdist = self.kavgd = None
449             game.enemies.remove(self)
450         return motion
451     def __repr__(self):
452         return "<%s,%s.%f>" % (self.type, self.kloc, self.kpower)       # For debugging
453
454 class gamestate:
455     def __init__(self):
456         self.options = None     # Game options
457         self.state = snapshot() # A snapshot structure
458         self.snapsht = snapshot()       # Last snapshot taken for time-travel purposes
459         self.quad = None        # contents of our quadrant
460         self.damage = [0.0] * NDEVICES  # damage encountered
461         self.future = []                # future events
462         for i in range(NEVENTS):
463             self.future.append(event())
464         self.passwd  = None;            # Self Destruct password
465         self.enemies = []
466         self.quadrant = None    # where we are in the large
467         self.sector = None      # where we are in the small
468         self.tholian = None     # Tholian enemy object
469         self.base = None        # position of base in current quadrant
470         self.battle = None      # base coordinates being attacked
471         self.plnet = None       # location of planet in quadrant
472         self.gamewon = False    # Finished!
473         self.ididit = False     # action taken -- allows enemy to attack
474         self.alive = False      # we are alive (not killed)
475         self.justin = False     # just entered quadrant
476         self.shldup = False     # shields are up
477         self.shldchg = False    # shield is changing (affects efficiency)
478         self.iscate = False     # super commander is here
479         self.ientesc = False    # attempted escape from supercommander
480         self.resting = False    # rest time
481         self.icraft = False     # Kirk in Galileo
482         self.landed = False     # party on planet (true), on ship (false)
483         self.alldone = False    # game is now finished
484         self.neutz = False      # Romulan Neutral Zone
485         self.isarmed = False    # probe is armed
486         self.inorbit = False    # orbiting a planet
487         self.imine = False      # mining
488         self.icrystl = False    # dilithium crystals aboard
489         self.iseenit = False    # seen base attack report
490         self.thawed = False     # thawed game
491         self.condition = None   # "green", "yellow", "red", "docked", "dead"
492         self.iscraft = None     # "onship", "offship", "removed"
493         self.skill = None       # Player skill level
494         self.inkling = 0        # initial number of klingons
495         self.inbase = 0         # initial number of bases
496         self.incom = 0          # initial number of commanders
497         self.inscom = 0         # initial number of commanders
498         self.inrom = 0          # initial number of commanders
499         self.instar = 0         # initial stars
500         self.intorps = 0        # initial/max torpedoes
501         self.torps = 0          # number of torpedoes
502         self.ship = 0           # ship type -- 'E' is Enterprise
503         self.abandoned = 0      # count of crew abandoned in space
504         self.length = 0         # length of game
505         self.klhere = 0         # klingons here
506         self.casual = 0         # causalties
507         self.nhelp = 0          # calls for help
508         self.nkinks = 0         # count of energy-barrier crossings
509         self.iplnet = None      # planet # in quadrant
510         self.inplan = 0         # initial planets
511         self.irhere = 0         # Romulans in quadrant
512         self.isatb = 0          # =1 if super commander is attacking base
513         self.tourn = None       # tournament number
514         self.nprobes = 0        # number of probes available
515         self.inresor = 0.0      # initial resources
516         self.intime = 0.0       # initial time
517         self.inenrg = 0.0       # initial/max energy
518         self.inshld = 0.0       # initial/max shield
519         self.inlsr = 0.0        # initial life support resources
520         self.indate = 0.0       # initial date
521         self.energy = 0.0       # energy level
522         self.shield = 0.0       # shield level
523         self.warpfac = 0.0      # warp speed
524         self.wfacsq = 0.0       # squared warp factor
525         self.lsupres = 0.0      # life support reserves
526         self.optime = 0.0       # time taken by current operation
527         self.docfac = 0.0       # repair factor when docking (constant?)
528         self.damfac = 0.0       # damage factor
529         self.lastchart = 0.0    # time star chart was last updated
530         self.cryprob = 0.0      # probability that crystal will work
531         self.probe = None       # object holding probe course info
532         self.height = 0.0       # height of orbit around planet
533     def recompute(self):
534         # Stas thinks this should be (C expression): 
535         # game.state.remkl + len(game.state.kcmdr) > 0 ?
536         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
537         # He says the existing expression is prone to divide-by-zero errors
538         # after killing the last klingon when score is shown -- perhaps also
539         # if the only remaining klingon is SCOM.
540         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
541
542 IHR = 'R'
543 IHK = 'K'
544 IHC = 'C'
545 IHS = 'S'
546 IHSTAR = '*'
547 IHP = 'P'
548 IHW = '@'
549 IHB = 'B'
550 IHBLANK = ' '
551 IHDOT = '.'
552 IHQUEST = '?'
553 IHE = 'E'
554 IHF = 'F'
555 IHT = 'T'
556 IHWEB = '#'
557 IHMATER0 = '-'
558 IHMATER1 = 'o'
559 IHMATER2 = '0'
560
561 FWON = 0
562 FDEPLETE = 1
563 FLIFESUP = 2
564 FNRG = 3
565 FBATTLE = 4
566 FNEG3 = 5
567 FNOVA = 6
568 FSNOVAED = 7
569 FABANDN = 8
570 FDILITHIUM = 9
571 FMATERIALIZE = 10
572 FPHASER = 11
573 FLOST = 12
574 FMINING = 13
575 FDPLANET = 14
576 FPNOVA = 15
577 FSSC = 16
578 FSTRACTOR = 17
579 FDRAY = 18
580 FTRIBBLE = 19
581 FHOLE = 20
582 FCREW = 21
583
584 def withprob(p):
585     v = random.random()
586     #logfp.write("# withprob(%s) -> %f (%s) at %s\n" % (p, v, v<p, traceback.extract_stack()[-2][1:]))
587     return v < p
588
589 def randrange(*args):
590     v = random.randrange(*args)
591     #logfp.write("# randrange%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
592     return v
593
594 def randreal(*args):
595     v = random.random()
596     if len(args) == 1:
597         v *= args[0]            # returns from [0, args[0])
598     elif len(args) == 2:
599         v = args[0] + v*(args[1]-args[0])       # returns from [args[0], args[1])
600     #logfp.write("# randreal%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
601     return v
602
603 # Code from ai.c begins here
604
605 def welcoming(iq):
606     "Would this quadrant welcome another Klingon?"
607     return iq.valid_quadrant() and \
608         not game.state.galaxy[iq.i][iq.j].supernova and \
609         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
610
611 def tryexit(enemy, look, irun):
612     "A bad guy attempts to bug out."
613     iq = coord()
614     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
615     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
616     if not welcoming(iq):
617         return False;
618     if enemy.type == IHR:
619         return False; # Romulans cannot escape! 
620     if not irun:
621         # avoid intruding on another commander's territory 
622         if enemy.type == IHC:
623             if iq in game.state.kcmdr:
624                 return False
625             # refuse to leave if currently attacking starbase 
626             if game.battle == game.quadrant:
627                 return False
628         # don't leave if over 1000 units of energy 
629         if enemy.kpower > 1000.0:
630             return False
631     # emit escape message and move out of quadrant.
632     # we know this if either short or long range sensors are working
633     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
634         game.condition == "docked":
635         prout(crmena(True, enemy.type, "sector", enemy.kloc) + \
636               (_(" escapes to Quadrant %s (and regains strength).") % q))
637     # handle local matters related to escape
638     enemy.move(None)
639     game.klhere -= 1
640     if game.condition != "docked":
641         newcnd()
642     # Handle global matters related to escape 
643     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
644     game.state.galaxy[iq.i][iq.j].klingons += 1
645     if enemy.type==IHS:
646         game.iscate = False
647         game.ientesc = False
648         game.isatb = 0
649         schedule(FSCMOVE, 0.2777)
650         unschedule(FSCDBAS)
651         game.state.kscmdr=iq
652     else:
653         for cmdr in game.state.kcmdr:
654             if cmdr == game.quadrant:
655                 game.state.kcmdr[n] = iq
656                 break
657     return True; # success 
658
659 # The bad-guy movement algorithm:
660
661 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
662 # If both are operating full strength, force is 1000. If both are damaged,
663 # force is -1000. Having shields down subtracts an additional 1000.
664
665 # 2. Enemy has forces equal to the energy of the attacker plus
666 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
667 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
668
669 # Attacker Initial energy levels (nominal):
670 # Klingon   Romulan   Commander   Super-Commander
671 # Novice    400        700        1200        
672 # Fair      425        750        1250
673 # Good      450        800        1300        1750
674 # Expert    475        850        1350        1875
675 # Emeritus  500        900        1400        2000
676 # VARIANCE   75        200         200         200
677
678 # Enemy vessels only move prior to their attack. In Novice - Good games
679 # only commanders move. In Expert games, all enemy vessels move if there
680 # is a commander present. In Emeritus games all enemy vessels move.
681
682 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
683 # forces are 1000 greater than Enterprise.
684
685 # Agressive action on average cuts the distance between the ship and
686 # the enemy to 1/4 the original.
687
688 # 4.  At lower energy advantage, movement units are proportional to the
689 # advantage with a 650 advantage being to hold ground, 800 to move forward
690 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
691
692 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
693 # retreat, especially at high skill levels.
694
695 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
696
697 def movebaddy(enemy):
698     "Tactical movement for the bad guys."
699     next = coord(); look = coord()
700     irun = False
701     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
702     if game.skill >= SKILL_EXPERT:
703         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
704     else:
705         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
706     dist1 = enemy.kdist
707     mdist = int(dist1 + 0.5); # Nearest integer distance 
708     # If SC, check with spy to see if should hi-tail it 
709     if enemy.type==IHS and \
710         (enemy.kpower <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
711         irun = True
712         motion = -QUADSIZE
713     else:
714         # decide whether to advance, retreat, or hold position 
715         forces = enemy.kpower+100.0*len(game.enemies)+400*(nbaddys-1)
716         if not game.shldup:
717             forces += 1000; # Good for enemy if shield is down! 
718         if not damaged(DPHASER) or not damaged(DPHOTON):
719             if damaged(DPHASER): # phasers damaged 
720                 forces += 300.0
721             else:
722                 forces -= 0.2*(game.energy - 2500.0)
723             if damaged(DPHOTON): # photon torpedoes damaged 
724                 forces += 300.0
725             else:
726                 forces -= 50.0*game.torps
727         else:
728             # phasers and photon tubes both out! 
729             forces += 1000.0
730         motion = 0
731         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
732             motion = ((forces + randreal(200))/150.0) - 5.0
733         else:
734             if forces > 1000.0: # Very strong -- move in for kill 
735                 motion = (1.0 - randreal())**2 * dist1 + 1.0
736             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
737                 motion -= game.skill*(2.0-randreal()**2)
738         if idebug:
739             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
740         # don't move if no motion 
741         if motion==0:
742             return
743         # Limit motion according to skill 
744         if abs(motion) > game.skill:
745             if motion < 0:
746                 motion = -game.skill
747             else:
748                 motion = game.skill
749     # calculate preferred number of steps 
750     nsteps = abs(int(motion))
751     if motion > 0 and nsteps > mdist:
752         nsteps = mdist; # don't overshoot 
753     if nsteps > QUADSIZE:
754         nsteps = QUADSIZE; # This shouldn't be necessary 
755     if nsteps < 1:
756         nsteps = 1; # This shouldn't be necessary 
757     if idebug:
758         proutn("NSTEPS = %d:" % nsteps)
759     # Compute preferred values of delta X and Y 
760     m = game.sector - enemy.kloc
761     if 2.0 * abs(m.i) < abs(m.j):
762         m.i = 0
763     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.kloc.i):
764         m.j = 0
765     m = (motion * m).sgn()
766     next = enemy.kloc
767     # main move loop 
768     for ll in range(nsteps):
769         if idebug:
770             proutn(" %d" % (ll+1))
771         # Check if preferred position available 
772         look = next + m
773         if m.i < 0:
774             krawli = 1
775         else:
776             krawli = -1
777         if m.j < 0:
778             krawlj = 1
779         else:
780             krawlj = -1
781         success = False
782         attempts = 0; # Settle mysterious hang problem 
783         while attempts < 20 and not success:
784             attempts += 1
785             if look.i < 0 or look.i >= QUADSIZE:
786                 if motion < 0 and tryexit(enemy, look, irun):
787                     return
788                 if krawli == m.i or m.j == 0:
789                     break
790                 look.i = next.i + krawli
791                 krawli = -krawli
792             elif look.j < 0 or look.j >= QUADSIZE:
793                 if motion < 0 and tryexit(enemy, look, irun):
794                     return
795                 if krawlj == m.j or m.i == 0:
796                     break
797                 look.j = next.j + krawlj
798                 krawlj = -krawlj
799             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != IHDOT:
800                 # See if enemy should ram ship 
801                 if game.quad[look.i][look.j] == game.ship and \
802                     (enemy.type == IHC or enemy.type == IHS):
803                     collision(rammed=True, enemy=enemy)
804                     return
805                 if krawli != m.i and m.j != 0:
806                     look.i = next.i + krawli
807                     krawli = -krawli
808                 elif krawlj != m.j and m.i != 0:
809                     look.j = next.j + krawlj
810                     krawlj = -krawlj
811                 else:
812                     break; # we have failed 
813             else:
814                 success = True
815         if success:
816             next = look
817             if idebug:
818                 proutn(`next`)
819         else:
820             break; # done early 
821     if idebug:
822         skip(1)
823     if enemy.move(next):
824         if not damaged(DSRSENS) or game.condition == "docked":
825             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.kloc))
826             if enemy.kdist < dist1:
827                 proutn(_(" advances to "))
828             else:
829                 proutn(_(" retreats to "))
830             prout("Sector %s." % next)
831
832 def moveklings():
833     "Sequence Klingon tactical movement."
834     if idebug:
835         prout("== MOVCOM")
836     # Figure out which Klingon is the commander (or Supercommander)
837     # and do move
838     if game.quadrant in game.state.kcmdr:
839         for enemy in game.enemies:
840             if enemy.type == IHC:
841                 movebaddy(enemy)
842     if game.state.kscmdr==game.quadrant:
843         for enemy in game.enemies:
844             if enemy.type == IHS:
845                 movebaddy(enemy)
846                 break
847     # If skill level is high, move other Klingons and Romulans too!
848     # Move these last so they can base their actions on what the
849     # commander(s) do.
850     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
851         for enemy in game.enemies:
852             if enemy.type in (IHK, IHR):
853                 movebaddy(enemy)
854     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
855
856 def movescom(iq, avoid):
857     "Commander movement helper." 
858     # Avoid quadrants with bases if we want to avoid Enterprise 
859     if not welcoming(iq) or (avoid and iq in game.state.baseq):
860         return False
861     if game.justin and not game.iscate:
862         return False
863     # do the move 
864     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
865     game.state.kscmdr = iq
866     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
867     if game.state.kscmdr==game.quadrant:
868         # SC has scooted, Remove him from current quadrant 
869         game.iscate=False
870         game.isatb=0
871         game.ientesc = False
872         unschedule(FSCDBAS)
873         for enemy in game.enemies:
874             if enemy.type == IHS:
875                 break
876         enemy.move(None)
877         game.klhere -= 1
878         if game.condition != "docked":
879             newcnd()
880         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
881     # check for a helpful planet 
882     for i in range(game.inplan):
883         if game.state.planets[i].quadrant == game.state.kscmdr and \
884             game.state.planets[i].crystals == "present":
885             # destroy the planet 
886             game.state.planets[i].pclass = "destroyed"
887             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
888             if communicating():
889                 announce()
890                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
891                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
892                 prout(_("   by the Super-commander.\""))
893             break
894     return True; # looks good! 
895                         
896 def supercommander():
897     "Move the Super Commander." 
898     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
899     basetbl = []
900     if idebug:
901         prout("== SUPERCOMMANDER")
902     # Decide on being active or passive 
903     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 \
904             (game.state.date-game.indate) < 3.0)
905     if not game.iscate and avoid:
906         # compute move away from Enterprise 
907         idelta = game.state.kscmdr-game.quadrant
908         if idelta.distance() > 2.0:
909             # circulate in space 
910             idelta.i = game.state.kscmdr.j-game.quadrant.j
911             idelta.j = game.quadrant.i-game.state.kscmdr.i
912     else:
913         # compute distances to starbases 
914         if not game.state.baseq:
915             # nothing left to do 
916             unschedule(FSCMOVE)
917             return
918         sc = game.state.kscmdr
919         for base in game.state.baseq:
920             basetbl.append((i, (base - sc).distance()))
921         if game.state.baseq > 1:
922             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
923         # look for nearest base without a commander, no Enterprise, and
924         # without too many Klingons, and not already under attack. 
925         ifindit = iwhichb = 0
926         for (i2, base) in enumerate(game.state.baseq):
927             i = basetbl[i2][0]; # bug in original had it not finding nearest
928             if base==game.quadrant or base==game.battle or not welcoming(base):
929                 continue
930             # if there is a commander, and no other base is appropriate,
931             # we will take the one with the commander
932             for cmdr in game.state.kcmdr:
933                 if base == cmdr and ifindit != 2:
934                     ifindit = 2
935                     iwhichb = i
936                     break
937             else:       # no commander -- use this one 
938                 ifindit = 1
939                 iwhichb = i
940                 break
941         if ifindit==0:
942             return # Nothing suitable -- wait until next time
943         ibq = game.state.baseq[iwhichb]
944         # decide how to move toward base 
945         idelta = ibq - game.state.kscmdr
946     # Maximum movement is 1 quadrant in either or both axes 
947     idelta = idelta.sgn()
948     # try moving in both x and y directions
949     # there was what looked like a bug in the Almy C code here,
950     # but it might be this translation is just wrong.
951     iq = game.state.kscmdr + idelta
952     if not movescom(iq, avoid):
953         # failed -- try some other maneuvers 
954         if idelta.i==0 or idelta.j==0:
955             # attempt angle move 
956             if idelta.i != 0:
957                 iq.j = game.state.kscmdr.j + 1
958                 if not movescom(iq, avoid):
959                     iq.j = game.state.kscmdr.j - 1
960                     movescom(iq, avoid)
961             elif idelta.j != 0:
962                 iq.i = game.state.kscmdr.i + 1
963                 if not movescom(iq, avoid):
964                     iq.i = game.state.kscmdr.i - 1
965                     movescom(iq, avoid)
966         else:
967             # try moving just in x or y 
968             iq.j = game.state.kscmdr.j
969             if not movescom(iq, avoid):
970                 iq.j = game.state.kscmdr.j + idelta.j
971                 iq.i = game.state.kscmdr.i
972                 movescom(iq, avoid)
973     # check for a base 
974     if len(game.state.baseq) == 0:
975         unschedule(FSCMOVE)
976     else:
977         for ibq in game.state.baseq:
978             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
979                 # attack the base 
980                 if avoid:
981                     return # no, don't attack base! 
982                 game.iseenit = False
983                 game.isatb = 1
984                 schedule(FSCDBAS, randreal(1.0, 3.0))
985                 if is_scheduled(FCDBAS):
986                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
987                 if not communicating():
988                     return # no warning 
989                 game.iseenit = True
990                 announce()
991                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
992                       % game.state.kscmdr)
993                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
994                 proutn(_("   It can survive until stardate %d.\"") \
995                        % int(scheduled(FSCDBAS)))
996                 if not game.resting:
997                     return
998                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
999                 if ja() == False:
1000                     return
1001                 game.resting = False
1002                 game.optime = 0.0; # actually finished 
1003                 return
1004     # Check for intelligence report 
1005     if not idebug and \
1006         (withprob(0.8) or \
1007          (not communicating()) or \
1008          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
1009         return
1010     announce()
1011     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
1012     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
1013     return
1014
1015 def movetholian():
1016     "Move the Tholian."
1017     if not game.tholian or game.justin:
1018         return
1019     id = coord()
1020     if game.tholian.kloc.i == 0 and game.tholian.kloc.j == 0:
1021         id.i = 0; id.j = QUADSIZE-1
1022     elif game.tholian.kloc.i == 0 and game.tholian.kloc.j == QUADSIZE-1:
1023         id.i = QUADSIZE-1; id.j = QUADSIZE-1
1024     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == QUADSIZE-1:
1025         id.i = QUADSIZE-1; id.j = 0
1026     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == 0:
1027         id.i = 0; id.j = 0
1028     else:
1029         # something is wrong! 
1030         game.tholian.move(None)
1031         prout("***Internal error: Tholian in a bad spot.")
1032         return
1033     # do nothing if we are blocked 
1034     if game.quad[id.i][id.j] not in (IHDOT, IHWEB):
1035         return
1036     here = copy.copy(game.tholian.kloc)
1037     delta = (id - game.tholian.kloc).sgn()
1038     # move in x axis 
1039     while here.i != id.i:
1040         here.i += delta.i
1041         if game.quad[here.i][here.j]==IHDOT:
1042             game.tholian.move(here)
1043     # move in y axis 
1044     while here.j != id.j:
1045         here.j += delta.j
1046         if game.quad[here.i][here.j]==IHDOT:
1047             game.tholian.move(here)
1048     # check to see if all holes plugged 
1049     for i in range(QUADSIZE):
1050         if game.quad[0][i]!=IHWEB and game.quad[0][i]!=IHT:
1051             return
1052         if game.quad[QUADSIZE-1][i]!=IHWEB and game.quad[QUADSIZE-1][i]!=IHT:
1053             return
1054         if game.quad[i][0]!=IHWEB and game.quad[i][0]!=IHT:
1055             return
1056         if game.quad[i][QUADSIZE-1]!=IHWEB and game.quad[i][QUADSIZE-1]!=IHT:
1057             return
1058     # All plugged up -- Tholian splits 
1059     game.quad[game.tholian.kloc.i][game.tholian.kloc.j]=IHWEB
1060     dropin(IHBLANK)
1061     prout(crmena(True, IHT, "sector", game.tholian) + _(" completes web."))
1062     game.tholian.move(None)
1063     return
1064
1065 # Code from battle.c begins here
1066
1067 def doshield(shraise):
1068     "Change shield status."
1069     action = "NONE"
1070     game.ididit = False
1071     if shraise:
1072         action = "SHUP"
1073     else:
1074         key = scanner.next()
1075         if key == "IHALPHA":
1076             if scanner.sees("transfer"):
1077                 action = "NRG"
1078             else:
1079                 if damaged(DSHIELD):
1080                     prout(_("Shields damaged and down."))
1081                     return
1082                 if scanner.sees("up"):
1083                     action = "SHUP"
1084                 elif scanner.sees("down"):
1085                     action = "SHDN"
1086         if action=="NONE":
1087             proutn(_("Do you wish to change shield energy? "))
1088             if ja() == True:
1089                 proutn(_("Energy to transfer to shields- "))
1090                 action = "NRG"
1091             elif damaged(DSHIELD):
1092                 prout(_("Shields damaged and down."))
1093                 return
1094             elif game.shldup:
1095                 proutn(_("Shields are up. Do you want them down? "))
1096                 if ja() == True:
1097                     action = "SHDN"
1098                 else:
1099                     scanner.chew()
1100                     return
1101             else:
1102                 proutn(_("Shields are down. Do you want them up? "))
1103                 if ja() == True:
1104                     action = "SHUP"
1105                 else:
1106                     scanner.chew()
1107                     return    
1108     if action == "SHUP": # raise shields 
1109         if game.shldup:
1110             prout(_("Shields already up."))
1111             return
1112         game.shldup = True
1113         game.shldchg = True
1114         if game.condition != "docked":
1115             game.energy -= 50.0
1116         prout(_("Shields raised."))
1117         if game.energy <= 0:
1118             skip(1)
1119             prout(_("Shields raising uses up last of energy."))
1120             finish(FNRG)
1121             return
1122         game.ididit=True
1123         return
1124     elif action == "SHDN":
1125         if not game.shldup:
1126             prout(_("Shields already down."))
1127             return
1128         game.shldup=False
1129         game.shldchg=True
1130         prout(_("Shields lowered."))
1131         game.ididit = True
1132         return
1133     elif action == "NRG":
1134         while scanner.next() != "IHREAL":
1135             scanner.chew()
1136             proutn(_("Energy to transfer to shields- "))
1137         scanner.chew()
1138         if scanner.real == 0:
1139             return
1140         if scanner.real > game.energy:
1141             prout(_("Insufficient ship energy."))
1142             return
1143         game.ididit = True
1144         if game.shield+scanner.real >= game.inshld:
1145             prout(_("Shield energy maximized."))
1146             if game.shield+scanner.real > game.inshld:
1147                 prout(_("Excess energy requested returned to ship energy"))
1148             game.energy -= game.inshld-game.shield
1149             game.shield = game.inshld
1150             return
1151         if scanner.real < 0.0 and game.energy-scanner.real > game.inenrg:
1152             # Prevent shield drain loophole 
1153             skip(1)
1154             prout(_("Engineering to bridge--"))
1155             prout(_("  Scott here. Power circuit problem, Captain."))
1156             prout(_("  I can't drain the shields."))
1157             game.ididit = False
1158             return
1159         if game.shield+scanner.real < 0:
1160             prout(_("All shield energy transferred to ship."))
1161             game.energy += game.shield
1162             game.shield = 0.0
1163             return
1164         proutn(_("Scotty- \""))
1165         if scanner.real > 0:
1166             prout(_("Transferring energy to shields.\""))
1167         else:
1168             prout(_("Draining energy from shields.\""))
1169         game.shield += scanner.real
1170         game.energy -= scanner.real
1171         return
1172
1173 def randdevice():
1174     "Choose a device to damage, at random."
1175     # Quoth Eric Allman in the code of BSD-Trek:
1176     # "Under certain conditions you can get a critical hit.  This
1177     # sort of hit damages devices.  The probability that a given
1178     # device is damaged depends on the device.  Well protected
1179     # devices (such as the computer, which is in the core of the
1180     # ship and has considerable redundancy) almost never get
1181     # damaged, whereas devices which are exposed (such as the
1182     # warp engines) or which are particularly delicate (such as
1183     # the transporter) have a much higher probability of being
1184     # damaged."
1185     # 
1186     # This is one place where OPTION_PLAIN does not restore the
1187     # original behavior, which was equiprobable damage across
1188     # all devices.  If we wanted that, we'd return randrange(NDEVICES)
1189     # and have done with it.  Also, in the original game, DNAVYS
1190     # and DCOMPTR were the same device. 
1191     # 
1192     # Instead, we use a table of weights similar to the one from BSD Trek.
1193     # BSD doesn't have the shuttle, shield controller, death ray, or probes. 
1194     # We don't have a cloaking device.  The shuttle got the allocation
1195     # for the cloaking device, then we shaved a half-percent off
1196     # everything to have some weight to give DSHCTRL/DDRAY/DDSP.
1197     weights = (
1198         105,    # DSRSENS: short range scanners 10.5% 
1199         105,    # DLRSENS: long range scanners          10.5% 
1200         120,    # DPHASER: phasers                      12.0% 
1201         120,    # DPHOTON: photon torpedoes             12.0% 
1202         25,     # DLIFSUP: life support          2.5% 
1203         65,     # DWARPEN: warp drive                    6.5% 
1204         70,     # DIMPULS: impulse engines               6.5% 
1205         145,    # DSHIELD: deflector shields            14.5% 
1206         30,     # DRADIO:  subspace radio                3.0% 
1207         45,     # DSHUTTL: shuttle                       4.5% 
1208         15,     # DCOMPTR: computer                      1.5% 
1209         20,     # NAVCOMP: navigation system             2.0% 
1210         75,     # DTRANSP: transporter                   7.5% 
1211         20,     # DSHCTRL: high-speed shield controller 2.0% 
1212         10,     # DDRAY: death ray                       1.0% 
1213         30,     # DDSP: deep-space probes                3.0% 
1214     )
1215     idx = randrange(1000)       # weights must sum to 1000 
1216     sum = 0
1217     for (i, w) in enumerate(weights):
1218         sum += w
1219         if idx < sum:
1220             return i
1221     return None;        # we should never get here
1222
1223 def collision(rammed, enemy):
1224     "Collision handling fot rammong events."
1225     prouts(_("***RED ALERT!  RED ALERT!"))
1226     skip(1)
1227     prout(_("***COLLISION IMMINENT."))
1228     skip(2)
1229     proutn("***")
1230     proutn(crmshp())
1231     hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(enemy.type, 1.0)
1232     if rammed:
1233         proutn(_(" rammed by "))
1234     else:
1235         proutn(_(" rams "))
1236     proutn(crmena(False, enemy.type, "sector", enemy.kloc))
1237     if rammed:
1238         proutn(_(" (original position)"))
1239     skip(1)
1240     deadkl(enemy.kloc, enemy.type, game.sector)
1241     proutn("***" + crmship() + " heavily damaged.")
1242     icas = randrange(10, 30)
1243     prout(_("***Sickbay reports %d casualties"), icas)
1244     game.casual += icas
1245     game.state.crew -= icas
1246     # In the pre-SST2K version, all devices got equiprobably damaged,
1247     # which was silly.  Instead, pick up to half the devices at
1248     # random according to our weighting table,
1249     ncrits = randrange(NDEVICES/2)
1250     for m in range(ncrits):
1251         dev = randdevice()
1252         if game.damage[dev] < 0:
1253             continue
1254         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1255         # Damage for at least time of travel! 
1256         game.damage[dev] += game.optime + extradm
1257     game.shldup = False
1258     prout(_("***Shields are down."))
1259     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1260         announce()
1261         damagereport()
1262     else:
1263         finish(FWON)
1264     return
1265
1266 def torpedo(origin, bearing, dispersion, number, nburst):
1267     "Let a photon torpedo fly" 
1268     if not damaged(DSRSENS) or game.condition=="docked":
1269         setwnd(srscan_window)
1270     else: 
1271         setwnd(message_window)
1272     ac = bearing + 0.25*dispersion      # dispersion is a random variable
1273     bullseye = (15.0 - bearing)*0.5235988
1274     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
1275     bumpto = coord(0, 0)
1276     # Loop to move a single torpedo 
1277     setwnd(message_window)
1278     for step in range(1, QUADSIZE*2):
1279         track.next()
1280         w = track.sector()
1281         if not w.valid_sector():
1282             break
1283         iquad=game.quad[w.i][w.j]
1284         tracktorpedo(origin, w, step, number, nburst, iquad)
1285         if iquad==IHDOT:
1286             continue
1287         # hit something 
1288         if not damaged(DSRSENS) or game.condition == "docked":
1289             skip(1);    # start new line after text track 
1290         if iquad in (IHE, IHF): # Hit our ship 
1291             skip(1)
1292             prout(_("Torpedo hits %s.") % crmshp())
1293             hit = 700.0 + randreal(100) - \
1294                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1295             newcnd(); # we're blown out of dock 
1296             if game.landed or game.condition=="docked":
1297                 return hit # Cheat if on a planet 
1298             ang = track.angle + 2.5*(randreal()-0.5)
1299             temp = math.fabs(math.sin(ang))
1300             if math.fabs(math.cos(ang)) > temp:
1301                 temp = math.fabs(math.cos(ang))
1302             xx = -math.sin(ang)/temp
1303             yy = math.cos(ang)/temp
1304             bumpto.i = int(w.i+xx+0.5)
1305             bumpto.j = int(w.j+yy+0.5)
1306             if not bumpto.valid_sector():
1307                 return hit
1308             if game.quad[bumpto.i][bumpto.j]==IHBLANK:
1309                 finish(FHOLE)
1310                 return hit
1311             if game.quad[bumpto.i][bumpto.j]!=IHDOT:
1312                 # can't move into object 
1313                 return hit
1314             game.sector = bumpto
1315             proutn(crmshp())
1316             game.quad[w.i][w.j]=IHDOT
1317             game.quad[bumpto.i][bumpto.j]=iquad
1318             prout(_(" displaced by blast to Sector %s ") % bumpto)
1319             for enemy in game.enemies:
1320                 enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
1321             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1322             return None
1323         elif iquad in (IHC, IHS, IHR, IHK): # Hit a regular enemy 
1324             # find the enemy 
1325             if iquad in (IHC, IHS) and withprob(0.05):
1326                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1327                 prout(_("   torpedo neutralized."))
1328                 return None
1329             for enemy in game.enemies:
1330                 if w == enemy.kloc:
1331                     break
1332             kp = math.fabs(enemy.kpower)
1333             h1 = 700.0 + randrange(100) - \
1334                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1335             h1 = math.fabs(h1)
1336             if kp < h1:
1337                 h1 = kp
1338             if enemy.kpower < 0:
1339                 enemy.kpower -= -h1
1340             else:
1341                 enemy.kpower -= h1
1342             if enemy.kpower == 0:
1343                 deadkl(w, iquad, w)
1344                 return None
1345             proutn(crmena(True, iquad, "sector", w))
1346             # If enemy damaged but not destroyed, try to displace 
1347             ang = track.angle + 2.5*(randreal()-0.5)
1348             temp = math.fabs(math.sin(ang))
1349             if math.fabs(math.cos(ang)) > temp:
1350                 temp = math.fabs(math.cos(ang))
1351             xx = -math.sin(ang)/temp
1352             yy = math.cos(ang)/temp
1353             bumpto.i = int(w.i+xx+0.5)
1354             bumpto.j = int(w.j+yy+0.5)
1355             if not bumpto.valid_sector():
1356                 prout(_(" damaged but not destroyed."))
1357                 return
1358             if game.quad[bumpto.i][bumpto.j] == IHBLANK:
1359                 prout(_(" buffeted into black hole."))
1360                 deadkl(w, iquad, bumpto)
1361             if game.quad[bumpto.i][bumpto.j] != IHDOT:
1362                 prout(_(" damaged but not destroyed."))
1363             else:
1364                 prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1365                 enemy.kloc = bumpto
1366                 game.quad[w.i][w.j]=IHDOT
1367                 game.quad[bumpto.i][bumpto.j]=iquad
1368                 for enemy in game.enemies:
1369                     enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
1370                 game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1371             return None
1372         elif iquad == IHB: # Hit a base 
1373             skip(1)
1374             prout(_("***STARBASE DESTROYED.."))
1375             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1376             game.quad[w.i][w.j]=IHDOT
1377             game.base.invalidate()
1378             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1379             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1380             game.state.basekl += 1
1381             newcnd()
1382             return None
1383         elif iquad == IHP: # Hit a planet 
1384             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1385             game.state.nplankl += 1
1386             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1387             game.iplnet.pclass = "destroyed"
1388             game.iplnet = None
1389             game.plnet.invalidate()
1390             game.quad[w.i][w.j] = IHDOT
1391             if game.landed:
1392                 # captain perishes on planet 
1393                 finish(FDPLANET)
1394             return None
1395         elif iquad == IHW: # Hit an inhabited world -- very bad! 
1396             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1397             game.state.nworldkl += 1
1398             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1399             game.iplnet.pclass = "destroyed"
1400             game.iplnet = None
1401             game.plnet.invalidate()
1402             game.quad[w.i][w.j] = IHDOT
1403             if game.landed:
1404                 # captain perishes on planet 
1405                 finish(FDPLANET)
1406             prout(_("You have just destroyed an inhabited planet."))
1407             prout(_("Celebratory rallies are being held on the Klingon homeworld."))
1408             return None
1409         elif iquad == IHSTAR: # Hit a star 
1410             if withprob(0.9):
1411                 nova(w)
1412             else:
1413                 prout(crmena(True, IHSTAR, "sector", w) + _(" unaffected by photon blast."))
1414             return None
1415         elif iquad == IHQUEST: # Hit a thingy 
1416             if not (game.options & OPTION_THINGY) or withprob(0.3):
1417                 skip(1)
1418                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1419                 skip(1)
1420                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1421                 skip(1)
1422                 proutn(_("Mr. Spock-"))
1423                 prouts(_("  \"Fascinating!\""))
1424                 skip(1)
1425                 deadkl(w, iquad, w)
1426             else:
1427                 # Stas Sergeev added the possibility that
1428                 # you can shove the Thingy and piss it off.
1429                 # It then becomes an enemy and may fire at you.
1430                 thing.angry = True
1431                 shoved = True
1432             return None
1433         elif iquad == IHBLANK: # Black hole 
1434             skip(1)
1435             prout(crmena(True, IHBLANK, "sector", w) + _(" swallows torpedo."))
1436             return None
1437         elif iquad == IHWEB: # hit the web 
1438             skip(1)
1439             prout(_("***Torpedo absorbed by Tholian web."))
1440             return None
1441         elif iquad == IHT:  # Hit a Tholian 
1442             h1 = 700.0 + randrange(100) - \
1443                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1444             h1 = math.fabs(h1)
1445             if h1 >= 600:
1446                 game.quad[w.i][w.j] = IHDOT
1447                 deadkl(w, iquad, w)
1448                 game.tholian = None
1449                 return None
1450             skip(1)
1451             proutn(crmena(True, IHT, "sector", w))
1452             if withprob(0.05):
1453                 prout(_(" survives photon blast."))
1454                 return None
1455             prout(_(" disappears."))
1456             game.tholian.move(None)
1457             game.quad[w.i][w.j] = IHWEB
1458             dropin(IHBLANK)
1459             return None
1460         else: # Problem!
1461             skip(1)
1462             proutn("Don't know how to handle torpedo collision with ")
1463             proutn(crmena(True, iquad, "sector", w))
1464             skip(1)
1465             return None
1466         break
1467     skip(1)
1468     prout(_("Torpedo missed."))
1469     return None;
1470
1471 def fry(hit):
1472     "Critical-hit resolution." 
1473     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1474         return
1475     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1476     proutn(_("***CRITICAL HIT--"))
1477     # Select devices and cause damage
1478     cdam = []
1479     for loop1 in range(ncrit):
1480         while True:
1481             j = randdevice()
1482             # Cheat to prevent shuttle damage unless on ship 
1483             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1484                 break
1485         cdam.append(j)
1486         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1487         game.damage[j] += extradm
1488     skipcount = 0
1489     for (i, j) in enumerate(cdam):
1490         proutn(device[j])
1491         if skipcount % 3 == 2 and i < len(cdam)-1:
1492             skip(1)
1493         skipcount += 1
1494         if i < len(cdam)-1:
1495             proutn(_(" and "))
1496     prout(_(" damaged."))
1497     if damaged(DSHIELD) and game.shldup:
1498         prout(_("***Shields knocked down."))
1499         game.shldup=False
1500
1501 def attack(torps_ok):
1502     # bad guy attacks us 
1503     # torps_ok == False forces use of phasers in an attack 
1504     # game could be over at this point, check
1505     if game.alldone:
1506         return
1507     attempt = False; ihurt = False;
1508     hitmax=0.0; hittot=0.0; chgfac=1.0
1509     where = "neither"
1510     if idebug:
1511         prout("=== ATTACK!")
1512     # Tholian gets to move before attacking 
1513     if game.tholian:
1514         movetholian()
1515     # if you have just entered the RNZ, you'll get a warning 
1516     if game.neutz: # The one chance not to be attacked 
1517         game.neutz = False
1518         return
1519     # commanders get a chance to tac-move towards you 
1520     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:
1521         moveklings()
1522     # if no enemies remain after movement, we're done 
1523     if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
1524         return
1525     # set up partial hits if attack happens during shield status change 
1526     pfac = 1.0/game.inshld
1527     if game.shldchg:
1528         chgfac = 0.25 + randreal(0.5)
1529     skip(1)
1530     # message verbosity control 
1531     if game.skill <= SKILL_FAIR:
1532         where = "sector"
1533     for enemy in game.enemies:
1534         if enemy.kpower < 0:
1535             continue;   # too weak to attack 
1536         # compute hit strength and diminish shield power 
1537         r = randreal()
1538         # Increase chance of photon torpedos if docked or enemy energy is low 
1539         if game.condition == "docked":
1540             r *= 0.25
1541         if enemy.kpower < 500:
1542             r *= 0.25; 
1543         if enemy.type==IHT or (enemy.type==IHQUEST and not thing.angry):
1544             continue
1545         # different enemies have different probabilities of throwing a torp 
1546         usephasers = not torps_ok or \
1547             (enemy.type == IHK and r > 0.0005) or \
1548             (enemy.type==IHC and r > 0.015) or \
1549             (enemy.type==IHR and r > 0.3) or \
1550             (enemy.type==IHS and r > 0.07) or \
1551             (enemy.type==IHQUEST and r > 0.05)
1552         if usephasers:      # Enemy uses phasers 
1553             if game.condition == "docked":
1554                 continue; # Don't waste the effort! 
1555             attempt = True; # Attempt to attack 
1556             dustfac = randreal(0.8, 0.85)
1557             hit = enemy.kpower*math.pow(dustfac,enemy.kavgd)
1558             enemy.kpower *= 0.75
1559         else: # Enemy uses photon torpedo 
1560             # We should be able to make the bearing() method work here
1561             course = 1.90985*math.atan2(game.sector.j-enemy.kloc.j, enemy.kloc.i-game.sector.i)
1562             hit = 0
1563             proutn(_("***TORPEDO INCOMING"))
1564             if not damaged(DSRSENS):
1565                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.kloc))
1566             attempt = True
1567             prout("  ")
1568             dispersion = (randreal()+randreal())*0.5 - 0.5
1569             dispersion += 0.002*enemy.kpower*dispersion
1570             hit = torpedo(enemy.kloc, course, dispersion, number=1, nburst=1)
1571             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1572                 finish(FWON); # Klingons did themselves in! 
1573             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1574                 return # Supernova or finished 
1575             if hit == None:
1576                 continue
1577         # incoming phaser or torpedo, shields may dissipate it 
1578         if game.shldup or game.shldchg or game.condition=="docked":
1579             # shields will take hits 
1580             propor = pfac * game.shield
1581             if game.condition =="docked":
1582                 propr *= 2.1
1583             if propor < 0.1:
1584                 propor = 0.1
1585             hitsh = propor*chgfac*hit+1.0
1586             absorb = 0.8*hitsh
1587             if absorb > game.shield:
1588                 absorb = game.shield
1589             game.shield -= absorb
1590             hit -= hitsh
1591             # taking a hit blasts us out of a starbase dock 
1592             if game.condition == "docked":
1593                 dock(False)
1594             # but the shields may take care of it 
1595             if propor > 0.1 and hit < 0.005*game.energy:
1596                 continue
1597         # hit from this opponent got through shields, so take damage 
1598         ihurt = True
1599         proutn(_("%d unit hit") % int(hit))
1600         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1601             proutn(_(" on the ") + crmshp())
1602         if not damaged(DSRSENS) and usephasers:
1603             prout(_(" from ") + crmena(False, enemy.type, where, enemy.kloc))
1604         skip(1)
1605         # Decide if hit is critical 
1606         if hit > hitmax:
1607             hitmax = hit
1608         hittot += hit
1609         fry(hit)
1610         game.energy -= hit
1611     if game.energy <= 0:
1612         # Returning home upon your shield, not with it... 
1613         finish(FBATTLE)
1614         return
1615     if not attempt and game.condition == "docked":
1616         prout(_("***Enemies decide against attacking your ship."))
1617     percent = 100.0*pfac*game.shield+0.5
1618     if not ihurt:
1619         # Shields fully protect ship 
1620         proutn(_("Enemy attack reduces shield strength to "))
1621     else:
1622         # Emit message if starship suffered hit(s) 
1623         skip(1)
1624         proutn(_("Energy left %2d    shields ") % int(game.energy))
1625         if game.shldup:
1626             proutn(_("up "))
1627         elif not damaged(DSHIELD):
1628             proutn(_("down "))
1629         else:
1630             proutn(_("damaged, "))
1631     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1632     # Check if anyone was hurt 
1633     if hitmax >= 200 or hittot >= 500:
1634         icas = randrange(int(hittot * 0.015))
1635         if icas >= 2:
1636             skip(1)
1637             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1638             prout(_("   in that last attack.\""))
1639             game.casual += icas
1640             game.state.crew -= icas
1641     # After attack, reset average distance to enemies 
1642     for enemy in game.enemies:
1643         enemy.kavgd = enemy.kdist
1644     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1645     return
1646                 
1647 def deadkl(w, type, mv):
1648     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1649     # Added mv to allow enemy to "move" before dying 
1650     proutn(crmena(True, type, "sector", mv))
1651     # Decide what kind of enemy it is and update appropriately 
1652     if type == IHR:
1653         # Chalk up a Romulan 
1654         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1655         game.irhere -= 1
1656         game.state.nromrem -= 1
1657     elif type == IHT:
1658         # Killed a Tholian 
1659         game.tholian = None
1660     elif type == IHQUEST:
1661         # Killed a Thingy
1662         global thing
1663         thing = None
1664     else:
1665         # Killed some type of Klingon 
1666         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1667         game.klhere -= 1
1668         if type == IHC:
1669             game.state.kcmdr.remove(game.quadrant)
1670             unschedule(FTBEAM)
1671             if game.state.kcmdr:
1672                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1673             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1674                 unschedule(FCDBAS)    
1675         elif type ==  IHK:
1676             game.state.remkl -= 1
1677         elif type ==  IHS:
1678             game.state.nscrem -= 1
1679             game.state.kscmdr.invalidate()
1680             game.isatb = 0
1681             game.iscate = False
1682             unschedule(FSCMOVE)
1683             unschedule(FSCDBAS)
1684     # For each kind of enemy, finish message to player 
1685     prout(_(" destroyed."))
1686     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1687         return
1688     game.recompute()
1689     # Remove enemy ship from arrays describing local conditions
1690     for e in game.enemies:
1691         if e.kloc == w:
1692             e.move(None)
1693             break
1694     return
1695
1696 def targetcheck(w):
1697     "Return None if target is invalid, otherwise return a course angle."
1698     if not w.valid_sector():
1699         huh()
1700         return None
1701     delta = coord()
1702     # FIXME: C code this was translated from is wacky -- why the sign reversal?
1703     delta.j = (w.j - game.sector.j);
1704     delta.i = (game.sector.i - w.i);
1705     if delta == coord(0, 0):
1706         skip(1)
1707         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1708         prout(_("  I recommend an immediate review of"))
1709         prout(_("  the Captain's psychological profile.\""))
1710         scanner.chew()
1711         return None
1712     return delta.bearing()
1713
1714 def photon():
1715     "Launch photon torpedo."
1716     course = []
1717     game.ididit = False
1718     if damaged(DPHOTON):
1719         prout(_("Photon tubes damaged."))
1720         scanner.chew()
1721         return
1722     if game.torps == 0:
1723         prout(_("No torpedoes left."))
1724         scanner.chew()
1725         return
1726     # First, get torpedo count
1727     while True:
1728         scanner.next()
1729         if scanner.token == "IHALPHA":
1730             huh()
1731             return
1732         elif scanner.token == "IHEOL" or not scanner.waiting():
1733             prout(_("%d torpedoes left.") % game.torps)
1734             scanner.chew()
1735             proutn(_("Number of torpedoes to fire- "))
1736             continue    # Go back around to get a number
1737         else: # key == "IHREAL"
1738             n = scanner.int()
1739             if n <= 0: # abort command 
1740                 scanner.chew()
1741                 return
1742             if n > MAXBURST:
1743                 scanner.chew()
1744                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1745                 return
1746             if n > game.torps:
1747                 scanner.chew()  # User requested more torps than available
1748                 continue        # Go back around
1749             break       # All is good, go to next stage
1750     # Next, get targets
1751     target = []
1752     for i in range(n):
1753         key = scanner.next()
1754         if i==0 and key == "IHEOL":
1755             break;      # no coordinate waiting, we will try prompting 
1756         if i==1 and key == "IHEOL":
1757             # direct all torpedoes at one target 
1758             while i < n:
1759                 target.append(target[0])
1760                 course.append(course[0])
1761                 i += 1
1762             break
1763         scanner.push(scanner.token)
1764         target.append(scanner.getcoord())
1765         if target[-1] == None:
1766             return
1767         course.append(targetcheck(target[-1]))
1768         if course[-1] == None:
1769             return
1770     scanner.chew()
1771     if len(target) == 0:
1772         # prompt for each one 
1773         for i in range(n):
1774             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1775             scanner.chew()
1776             target.append(scanner.getcoord())
1777             if target[-1] == None:
1778                 return
1779             course.append(targetcheck(target[-1]))
1780             if course[-1] == None:
1781                 return
1782     game.ididit = True
1783     # Loop for moving <n> torpedoes 
1784     for i in range(n):
1785         if game.condition != "docked":
1786             game.torps -= 1
1787         dispersion = (randreal()+randreal())*0.5 -0.5
1788         if math.fabs(dispersion) >= 0.47:
1789             # misfire! 
1790             dispersion *= randreal(1.2, 2.2)
1791             if n > 0:
1792                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1793             else:
1794                 prouts(_("***TORPEDO MISFIRES."))
1795             skip(1)
1796             if i < n:
1797                 prout(_("  Remainder of burst aborted."))
1798             if withprob(0.2):
1799                 prout(_("***Photon tubes damaged by misfire."))
1800                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1801             break
1802         if game.shldup or game.condition == "docked":
1803             dispersion *= 1.0 + 0.0001*game.shield
1804         torpedo(game.sector, course[i], dispersion, number=i, nburst=n)
1805         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1806             return
1807     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1808         finish(FWON);
1809
1810 def overheat(rpow):
1811     "Check for phasers overheating."
1812     if rpow > 1500:
1813         checkburn = (rpow-1500.0)*0.00038
1814         if withprob(checkburn):
1815             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1816             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1817
1818 def checkshctrl(rpow):
1819     "Check shield control."
1820     skip(1)
1821     if withprob(0.998):
1822         prout(_("Shields lowered."))
1823         return False
1824     # Something bad has happened 
1825     prouts(_("***RED ALERT!  RED ALERT!"))
1826     skip(2)
1827     hit = rpow*game.shield/game.inshld
1828     game.energy -= rpow+hit*0.8
1829     game.shield -= hit*0.2
1830     if game.energy <= 0.0:
1831         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1832         skip(1)
1833         stars()
1834         finish(FPHASER)
1835         return True
1836     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1837     skip(2)
1838     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1839     icas = randrange(int(hit*0.012))
1840     skip(1)
1841     fry(0.8*hit)
1842     if icas:
1843         skip(1)
1844         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1845         prout(_("  %d casualties so far.\"") % icas)
1846         game.casual += icas
1847         game.state.crew -= icas
1848     skip(1)
1849     prout(_("Phaser energy dispersed by shields."))
1850     prout(_("Enemy unaffected."))
1851     overheat(rpow)
1852     return True;
1853
1854 def hittem(hits):
1855     "Register a phaser hit on Klingons and Romulans."
1856     nenhr2 = len(game.enemies); kk=0
1857     w = coord()
1858     skip(1)
1859     for (k, wham) in enumerate(hits):
1860         if wham==0:
1861             continue
1862         dustfac = randreal(0.9, 1.0)
1863         hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
1864         kpini = game.enemies[kk].kpower
1865         kp = math.fabs(kpini)
1866         if PHASEFAC*hit < kp:
1867             kp = PHASEFAC*hit
1868         if game.enemies[kk].kpower < 0:
1869             game.enemies[kk].kpower -= -kp
1870         else:
1871             game.enemies[kk].kpower -= kp
1872         kpow = game.enemies[kk].kpower
1873         w = game.enemies[kk].kloc
1874         if hit > 0.005:
1875             if not damaged(DSRSENS):
1876                 boom(w)
1877             proutn(_("%d unit hit on ") % int(hit))
1878         else:
1879             proutn(_("Very small hit on "))
1880         ienm = game.quad[w.i][w.j]
1881         if ienm==IHQUEST:
1882             thing.angry = True
1883         proutn(crmena(False, ienm, "sector", w))
1884         skip(1)
1885         if kpow == 0:
1886             deadkl(w, ienm, w)
1887             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1888                 finish(FWON);           
1889             if game.alldone:
1890                 return
1891             kk -= 1     # don't do the increment
1892             continue
1893         else: # decide whether or not to emasculate klingon 
1894             if kpow>0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1895                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1896                 prout(_("   has just lost its firepower.\""))
1897                 game.enemies[kk].kpower = -kpow
1898         kk += 1
1899     return
1900
1901 def phasers():
1902     "Fire phasers at bad guys."
1903     hits = []
1904     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1905     ifast = False; no = False; itarg = True; msgflag = True; rpow=0
1906     automode = "NOTSET"
1907     key=0
1908     skip(1)
1909     # SR sensors and Computer are needed for automode 
1910     if damaged(DSRSENS) or damaged(DCOMPTR):
1911         itarg = False
1912     if game.condition == "docked":
1913         prout(_("Phasers can't be fired through base shields."))
1914         scanner.chew()
1915         return
1916     if damaged(DPHASER):
1917         prout(_("Phaser control damaged."))
1918         scanner.chew()
1919         return
1920     if game.shldup:
1921         if damaged(DSHCTRL):
1922             prout(_("High speed shield control damaged."))
1923             scanner.chew()
1924             return
1925         if game.energy <= 200.0:
1926             prout(_("Insufficient energy to activate high-speed shield control."))
1927             scanner.chew()
1928             return
1929         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1930         ifast = True
1931     # Original code so convoluted, I re-did it all
1932     # (That was Tom Almy talking about the C code, I think -- ESR)
1933     while automode=="NOTSET":
1934         key=scanner.next()
1935         if key == "IHALPHA":
1936             if scanner.sees("manual"):
1937                 if len(game.enemies)==0:
1938                     prout(_("There is no enemy present to select."))
1939                     scanner.chew()
1940                     key = "IHEOL"
1941                     automode="AUTOMATIC"
1942                 else:
1943                     automode = "MANUAL"
1944                     key = scanner.next()
1945             elif scanner.sees("automatic"):
1946                 if (not itarg) and len(game.enemies) != 0:
1947                     automode = "FORCEMAN"
1948                 else:
1949                     if len(game.enemies)==0:
1950                         prout(_("Energy will be expended into space."))
1951                     automode = "AUTOMATIC"
1952                     key = scanner.next()
1953             elif scanner.sees("no"):
1954                 no = True
1955             else:
1956                 huh()
1957                 return
1958         elif key == "IHREAL":
1959             if len(game.enemies)==0:
1960                 prout(_("Energy will be expended into space."))
1961                 automode = "AUTOMATIC"
1962             elif not itarg:
1963                 automode = "FORCEMAN"
1964             else:
1965                 automode = "AUTOMATIC"
1966         else:
1967             # "IHEOL" 
1968             if len(game.enemies)==0:
1969                 prout(_("Energy will be expended into space."))
1970                 automode = "AUTOMATIC"
1971             elif not itarg:
1972                 automode = "FORCEMAN"
1973             else: 
1974                 proutn(_("Manual or automatic? "))
1975                 scanner.chew()
1976     avail = game.energy
1977     if ifast:
1978         avail -= 200.0
1979     if automode == "AUTOMATIC":
1980         if key == "IHALPHA" and scanner.sees("no"):
1981             no = True
1982             key = scanner.next()
1983         if key != "IHREAL" and len(game.enemies) != 0:
1984             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1985         irec=0
1986         while True:
1987             scanner.chew()
1988             if not kz:
1989                 for i in range(len(game.enemies)):
1990                     irec += math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1991             kz=1
1992             proutn(_("%d units required. ") % irec)
1993             scanner.chew()
1994             proutn(_("Units to fire= "))
1995             key = scanner.next()
1996             if key!="IHREAL":
1997                 return
1998             rpow = scanner.real
1999             if rpow > avail:
2000                 proutn(_("Energy available= %.2f") % avail)
2001                 skip(1)
2002                 key = "IHEOL"
2003             if not rpow > avail:
2004                 break
2005         if rpow<=0:
2006             # chicken out 
2007             scanner.chew()
2008             return
2009         key=scanner.next()
2010         if key == "IHALPHA" and scanner.sees("no"):
2011             no = True
2012         if ifast:
2013             game.energy -= 200; # Go and do it! 
2014             if checkshctrl(rpow):
2015                 return
2016         scanner.chew()
2017         game.energy -= rpow
2018         extra = rpow
2019         if len(game.enemies):
2020             extra = 0.0
2021             powrem = rpow
2022             for i in range(len(game.enemies)):
2023                 hits.append(0.0)
2024                 if powrem <= 0:
2025                     continue
2026                 hits[i] = math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
2027                 over = randreal(1.01, 1.06) * hits[i]
2028                 temp = powrem
2029                 powrem -= hits[i] + over
2030                 if powrem <= 0 and temp < hits[i]:
2031                     hits[i] = temp
2032                 if powrem <= 0:
2033                     over = 0.0
2034                 extra += over
2035             if powrem > 0.0:
2036                 extra += powrem
2037             hittem(hits)
2038             game.ididit = True
2039         if extra > 0 and not game.alldone:
2040             if game.tholian:
2041                 proutn(_("*** Tholian web absorbs "))
2042                 if len(game.enemies)>0:
2043                     proutn(_("excess "))
2044                 prout(_("phaser energy."))
2045             else:
2046                 prout(_("%d expended on empty space.") % int(extra))
2047     elif automode == "FORCEMAN":
2048         scanner.chew()
2049         key = "IHEOL"
2050         if damaged(DCOMPTR):
2051             prout(_("Battle computer damaged, manual fire only."))
2052         else:
2053             skip(1)
2054             prouts(_("---WORKING---"))
2055             skip(1)
2056             prout(_("Short-range-sensors-damaged"))
2057             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2058             prout(_("Manual-fire-must-be-used"))
2059             skip(1)
2060     elif automode == "MANUAL":
2061         rpow = 0.0
2062         for k in range(len(game.enemies)):
2063             aim = game.enemies[k].kloc
2064             ienm = game.quad[aim.i][aim.j]
2065             if msgflag:
2066                 proutn(_("Energy available= %.2f") % (avail-0.006))
2067                 skip(1)
2068                 msgflag = False
2069                 rpow = 0.0
2070             if damaged(DSRSENS) and \
2071                not game.sector.distance(aim)<2**0.5 and ienm in (IHC, IHS):
2072                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2073                 scanner.chew()
2074                 key = "IHEOL"
2075                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
2076                 k += 1
2077                 continue
2078             if key == "IHEOL":
2079                 scanner.chew()
2080                 if itarg and k > kz:
2081                     irec=(abs(game.enemies[k].kpower)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) * randreal(1.01, 1.06) + 1.0
2082                 kz = k
2083                 proutn("(")
2084                 if not damaged(DCOMPTR):
2085                     proutn("%d" % irec)
2086                 else:
2087                     proutn("??")
2088                 proutn(")  ")
2089                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
2090                 key = scanner.next()
2091             if key == "IHALPHA" and scanner.sees("no"):
2092                 no = True
2093                 key = scanner.next()
2094                 continue
2095             if key == "IHALPHA":
2096                 huh()
2097                 return
2098             if key == "IHEOL":
2099                 if k==1: # Let me say I'm baffled by this 
2100                     msgflag = True
2101                 continue
2102             if scanner.real < 0:
2103                 # abort out 
2104                 scanner.chew()
2105                 return
2106             hits[k] = scanner.real
2107             rpow += scanner.real
2108             # If total requested is too much, inform and start over 
2109             if rpow > avail:
2110                 prout(_("Available energy exceeded -- try again."))
2111                 scanner.chew()
2112                 return
2113             key = scanner.next(); # scan for next value 
2114             k += 1
2115         if rpow == 0.0:
2116             # zero energy -- abort 
2117             scanner.chew()
2118             return
2119         if key == "IHALPHA" and scanner.sees("no"):
2120             no = True
2121         game.energy -= rpow
2122         scanner.chew()
2123         if ifast:
2124             game.energy -= 200.0
2125             if checkshctrl(rpow):
2126                 return
2127         hittem(hits)
2128         game.ididit = True
2129      # Say shield raised or malfunction, if necessary 
2130     if game.alldone:
2131         return
2132     if ifast:
2133         skip(1)
2134         if no == 0:
2135             if withprob(0.01):
2136                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2137                 prouts(_("         CLICK   CLICK   POP  . . ."))
2138                 prout(_(" No response, sir!"))
2139                 game.shldup = False
2140             else:
2141                 prout(_("Shields raised."))
2142         else:
2143             game.shldup = False
2144     overheat(rpow);
2145
2146 # Code from events,c begins here.
2147
2148 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
2149 # event of each type active at any given time.  Mostly these means we can 
2150 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2151 # BSD Trek, from which we swiped the idea, can have up to 5.
2152
2153 def unschedule(evtype):
2154     "Remove an event from the schedule."
2155     game.future[evtype].date = FOREVER
2156     return game.future[evtype]
2157
2158 def is_scheduled(evtype):
2159     "Is an event of specified type scheduled."
2160     return game.future[evtype].date != FOREVER
2161
2162 def scheduled(evtype):
2163     "When will this event happen?"
2164     return game.future[evtype].date
2165
2166 def schedule(evtype, offset):
2167     "Schedule an event of specified type."
2168     game.future[evtype].date = game.state.date + offset
2169     return game.future[evtype]
2170
2171 def postpone(evtype, offset):
2172     "Postpone a scheduled event."
2173     game.future[evtype].date += offset
2174
2175 def cancelrest():
2176     "Rest period is interrupted by event."
2177     if game.resting:
2178         skip(1)
2179         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2180         if ja() == True:
2181             game.resting = False
2182             game.optime = 0.0
2183             return True
2184     return False
2185
2186 def events():
2187     "Run through the event queue looking for things to do."
2188     i=0
2189     fintim = game.state.date + game.optime; yank=0
2190     ictbeam = False; istract = False
2191     w = coord(); hold = coord()
2192     ev = event(); ev2 = event()
2193
2194     def tractorbeam(yank):
2195         "Tractor-beaming cases merge here." 
2196         announce()
2197         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
2198         skip(1)
2199         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2200         # If Kirk & Co. screwing around on planet, handle 
2201         atover(True) # atover(true) is Grab 
2202         if game.alldone:
2203             return
2204         if game.icraft: # Caught in Galileo? 
2205             finish(FSTRACTOR)
2206             return
2207         # Check to see if shuttle is aboard 
2208         if game.iscraft == "offship":
2209             skip(1)
2210             if withprob(0.5):
2211                 prout(_("Galileo, left on the planet surface, is captured"))
2212                 prout(_("by aliens and made into a flying McDonald's."))
2213                 game.damage[DSHUTTL] = -10
2214                 game.iscraft = "removed"
2215             else:
2216                 prout(_("Galileo, left on the planet surface, is well hidden."))
2217         if evcode == FSPY:
2218             game.quadrant = game.state.kscmdr
2219         else:
2220             game.quadrant = game.state.kcmdr[i]
2221         game.sector = randplace(QUADSIZE)
2222         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2223                % (game.quadrant, game.sector))
2224         if game.resting:
2225             prout(_("(Remainder of rest/repair period cancelled.)"))
2226             game.resting = False
2227         if not game.shldup:
2228             if not damaged(DSHIELD) and game.shield > 0:
2229                 doshield(shraise=True) # raise shields 
2230                 game.shldchg = False
2231             else:
2232                 prout(_("(Shields not currently useable.)"))
2233         newqad()
2234         # Adjust finish time to time of tractor beaming 
2235         fintim = game.state.date+game.optime
2236         attack(torps_ok=False)
2237         if not game.state.kcmdr:
2238             unschedule(FTBEAM)
2239         else: 
2240             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2241
2242     def destroybase():
2243         "Code merges here for any commander destroying a starbase." 
2244         # Not perfect, but will have to do 
2245         # Handle case where base is in same quadrant as starship 
2246         if game.battle == game.quadrant:
2247             game.state.chart[game.battle.i][game.battle.j].starbase = False
2248             game.quad[game.base.i][game.base.j] = IHDOT
2249             game.base.invalidate()
2250             newcnd()
2251             skip(1)
2252             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2253         elif game.state.baseq and communicating():
2254             # Get word via subspace radio 
2255             announce()
2256             skip(1)
2257             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2258             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2259             if game.isatb == 2: 
2260                 prout(_("the Klingon Super-Commander"))
2261             else:
2262                 prout(_("a Klingon Commander"))
2263             game.state.chart[game.battle.i][game.battle.j].starbase = False
2264         # Remove Starbase from galaxy 
2265         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2266         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2267         if game.isatb == 2:
2268             # reinstate a commander's base attack 
2269             game.battle = hold
2270             game.isatb = 0
2271         else:
2272             game.battle.invalidate()
2273     if idebug:
2274         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2275         for i in range(1, NEVENTS):
2276             if   i == FSNOVA:  proutn("=== Supernova       ")
2277             elif i == FTBEAM:  proutn("=== T Beam          ")
2278             elif i == FSNAP:   proutn("=== Snapshot        ")
2279             elif i == FBATTAK: proutn("=== Base Attack     ")
2280             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2281             elif i == FSCMOVE: proutn("=== SC Move         ")
2282             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2283             elif i == FDSPROB: proutn("=== Probe Move      ")
2284             elif i == FDISTR:  proutn("=== Distress Call   ")
2285             elif i == FENSLV:  proutn("=== Enslavement     ")
2286             elif i == FREPRO:  proutn("=== Klingon Build   ")
2287             if is_scheduled(i):
2288                 prout("%.2f" % (scheduled(i)))
2289             else:
2290                 prout("never")
2291     radio_was_broken = damaged(DRADIO)
2292     hold.i = hold.j = 0
2293     while True:
2294         # Select earliest extraneous event, evcode==0 if no events 
2295         evcode = FSPY
2296         if game.alldone:
2297             return
2298         datemin = fintim
2299         for l in range(1, NEVENTS):
2300             if game.future[l].date < datemin:
2301                 evcode = l
2302                 if idebug:
2303                     prout("== Event %d fires" % evcode)
2304                 datemin = game.future[l].date
2305         xtime = datemin-game.state.date
2306         game.state.date = datemin
2307         # Decrement Federation resources and recompute remaining time 
2308         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2309         game.recompute()
2310         if game.state.remtime <=0:
2311             finish(FDEPLETE)
2312             return
2313         # Any crew left alive? 
2314         if game.state.crew <=0:
2315             finish(FCREW)
2316             return
2317         # Is life support adequate? 
2318         if damaged(DLIFSUP) and game.condition != "docked":
2319             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2320                 finish(FLIFESUP)
2321                 return
2322             game.lsupres -= xtime
2323             if game.damage[DLIFSUP] <= xtime:
2324                 game.lsupres = game.inlsr
2325         # Fix devices 
2326         repair = xtime
2327         if game.condition == "docked":
2328             repair /= game.docfac
2329         # Don't fix Deathray here 
2330         for l in range(NDEVICES):
2331             if game.damage[l] > 0.0 and l != DDRAY:
2332                 if game.damage[l]-repair > 0.0:
2333                     game.damage[l] -= repair
2334                 else:
2335                     game.damage[l] = 0.0
2336         # If radio repaired, update star chart and attack reports 
2337         if radio_was_broken and not damaged(DRADIO):
2338             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2339             prout(_("   surveillance reports are coming in."))
2340             skip(1)
2341             if not game.iseenit:
2342                 attackreport(False)
2343                 game.iseenit = True
2344             rechart()
2345             prout(_("   The star chart is now up to date.\""))
2346             skip(1)
2347         # Cause extraneous event EVCODE to occur 
2348         game.optime -= xtime
2349         if evcode == FSNOVA: # Supernova 
2350             announce()
2351             supernova(None)
2352             schedule(FSNOVA, expran(0.5*game.intime))
2353             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2354                 return
2355         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2356             if game.state.nscrem == 0 or \
2357                 ictbeam or istract or \
2358                 game.condition=="docked" or game.isatb==1 or game.iscate:
2359                 return
2360             if game.ientesc or \
2361                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2362                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2363                 (damaged(DSHIELD) and \
2364                  (game.energy < 2500 or damaged(DPHASER)) and \
2365                  (game.torps < 5 or damaged(DPHOTON))):
2366                 # Tractor-beam her! 
2367                 istract = ictbeam = True
2368                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2369             else:
2370                 return
2371         elif evcode == FTBEAM: # Tractor beam 
2372             if not game.state.kcmdr:
2373                 unschedule(FTBEAM)
2374                 continue
2375             i = randrange(len(game.state.kcmdr))
2376             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2377             if istract or game.condition == "docked" or yank == 0:
2378                 # Drats! Have to reschedule 
2379                 schedule(FTBEAM, 
2380                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2381                 continue
2382             ictbeam = True
2383             tractorbeam(yank)
2384         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2385             game.snapsht = copy.deepcopy(game.state)
2386             game.state.snap = True
2387             schedule(FSNAP, expran(0.5 * game.intime))
2388         elif evcode == FBATTAK: # Commander attacks starbase 
2389             if not game.state.kcmdr or not game.state.baseq:
2390                 # no can do 
2391                 unschedule(FBATTAK)
2392                 unschedule(FCDBAS)
2393                 continue
2394             try:
2395                 for ibq in game.state.baseq:
2396                    for cmdr in game.state.kcmdr: 
2397                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2398                            raise ibq
2399                 else:
2400                     # no match found -- try later 
2401                     schedule(FBATTAK, expran(0.3*game.intime))
2402                     unschedule(FCDBAS)
2403                     continue
2404             except coord:
2405                 pass
2406             # commander + starbase combination found -- launch attack 
2407             game.battle = ibq
2408             schedule(FCDBAS, randreal(1.0, 4.0))
2409             if game.isatb: # extra time if SC already attacking 
2410                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2411             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2412             game.iseenit = False
2413             if not communicating():
2414                 continue # No warning :-( 
2415             game.iseenit = True
2416             announce()
2417             skip(1)
2418             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2419             prout(_("   reports that it is under attack and that it can"))
2420             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2421             if cancelrest():
2422                 return
2423         elif evcode == FSCDBAS: # Supercommander destroys base 
2424             unschedule(FSCDBAS)
2425             game.isatb = 2
2426             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2427                 continue # WAS RETURN! 
2428             hold = game.battle
2429             game.battle = game.state.kscmdr
2430             destroybase()
2431         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2432             if evcode==FCDBAS:
2433                 unschedule(FCDBAS)
2434                 if not game.state.baseq() \
2435                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2436                     game.battle.invalidate()
2437                     continue
2438                 # find the lucky pair 
2439                 for cmdr in game.state.kcmdr:
2440                     if cmdr == game.battle: 
2441                         break
2442                 else:
2443                     # No action to take after all 
2444                     continue
2445             destroybase()
2446         elif evcode == FSCMOVE: # Supercommander moves 
2447             schedule(FSCMOVE, 0.2777)
2448             if not game.ientesc and not istract and game.isatb != 1 and \
2449                    (not game.iscate or not game.justin): 
2450                 supercommander()
2451         elif evcode == FDSPROB: # Move deep space probe 
2452             schedule(FDSPROB, 0.01)
2453             if game.probe.next(grain=QUADSIZE):
2454                 if not game.probe.quadrant().valid_quadrant() or \
2455                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2456                     # Left galaxy or ran into supernova
2457                     if communicating():
2458                         announce()
2459                         skip(1)
2460                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2461                         if not game.probe.quadrant().valid_quadrant():
2462                             prout(_("has left the galaxy.\""))
2463                         else:
2464                             prout(_("is no longer transmitting.\""))
2465                     unschedule(FDSPROB)
2466                     continue
2467                 if communicating():
2468                     #announce()
2469                     skip(1)
2470                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2471             pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
2472             if communicating():
2473                 chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
2474                 chp.klingons = pdest.klingons
2475                 chp.starbase = pdest.starbase
2476                 chp.stars = pdest.stars
2477                 pdest.charted = True
2478             game.probe.moves -= 1 # One less to travel
2479             if game.probe.moves == 0 and game.isarmed and pdest.stars:
2480                 supernova(game.probe)           # fire in the hole!
2481                 unschedule(FDSPROB)
2482                 if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
2483                     return
2484         elif evcode == FDISTR: # inhabited system issues distress call 
2485             unschedule(FDISTR)
2486             # try a whole bunch of times to find something suitable 
2487             for i in range(100):
2488                 # need a quadrant which is not the current one,
2489                 # which has some stars which are inhabited and
2490                 # not already under attack, which is not
2491                 # supernova'ed, and which has some Klingons in it
2492                 w = randplace(GALSIZE)
2493                 q = game.state.galaxy[w.i][w.j]
2494                 if not (game.quadrant == w or q.planet == None or \
2495                       not q.planet.inhabited or \
2496                       q.supernova or q.status!="secure" or q.klingons<=0):
2497                     break
2498             else:
2499                 # can't seem to find one; ignore this call 
2500                 if idebug:
2501                     prout("=== Couldn't find location for distress event.")
2502                 continue
2503             # got one!!  Schedule its enslavement 
2504             ev = schedule(FENSLV, expran(game.intime))
2505             ev.quadrant = w
2506             q.status = "distressed"
2507             # tell the captain about it if we can 
2508             if communicating():
2509                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2510                         % (q.planet, `w`))
2511                 prout(_("by a Klingon invasion fleet."))
2512                 if cancelrest():
2513                     return
2514         elif evcode == FENSLV:          # starsystem is enslaved 
2515             ev = unschedule(FENSLV)
2516             # see if current distress call still active 
2517             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2518             if q.klingons <= 0:
2519                 q.status = "secure"
2520                 continue
2521             q.status = "enslaved"
2522
2523             # play stork and schedule the first baby 
2524             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2525             ev2.quadrant = ev.quadrant
2526
2527             # report the disaster if we can 
2528             if communicating():
2529                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2530                         q.planet)
2531                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2532         elif evcode == FREPRO:          # Klingon reproduces 
2533             # If we ever switch to a real event queue, we'll need to
2534             # explicitly retrieve and restore the x and y.
2535             ev = schedule(FREPRO, expran(1.0 * game.intime))
2536             # see if current distress call still active 
2537             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2538             if q.klingons <= 0:
2539                 q.status = "secure"
2540                 continue
2541             if game.state.remkl >=MAXKLGAME:
2542                 continue                # full right now 
2543             # reproduce one Klingon 
2544             w = ev.quadrant
2545             m = coord()
2546             if game.klhere >= MAXKLQUAD:
2547                 try:
2548                     # this quadrant not ok, pick an adjacent one 
2549                     for m.i in range(w.i - 1, w.i + 2):
2550                         for m.j in range(w.j - 1, w.j + 2):
2551                             if not m.valid_quadrant():
2552                                 continue
2553                             q = game.state.galaxy[m.i][m.j]
2554                             # check for this quad ok (not full & no snova) 
2555                             if q.klingons >= MAXKLQUAD or q.supernova:
2556                                 continue
2557                             raise "FOUNDIT"
2558                     else:
2559                         continue        # search for eligible quadrant failed
2560                 except "FOUNDIT":
2561                     w = m
2562             # deliver the child 
2563             game.state.remkl += 1
2564             q.klingons += 1
2565             if game.quadrant == w:
2566                 game.klhere += 1
2567                 game.enemies.append(newkling())
2568             # recompute time left
2569             game.recompute()
2570             if communicating():
2571                 if game.quadrant == w:
2572                     prout(_("Spock- sensors indicate the Klingons have"))
2573                     prout(_("launched a warship from %s.") % q.planet)
2574                 else:
2575                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2576                     if q.planet != None:
2577                         proutn(_("near %s") % q.planet)
2578                     prout(_("in Quadrant %s.") % w)
2579                                 
2580 def wait():
2581     "Wait on events."
2582     game.ididit = False
2583     while True:
2584         key = scanner.next()
2585         if key  != "IHEOL":
2586             break
2587         proutn(_("How long? "))
2588     scanner.chew()
2589     if key != "IHREAL":
2590         huh()
2591         return
2592     origTime = delay = scanner.real
2593     if delay <= 0.0:
2594         return
2595     if delay >= game.state.remtime or len(game.enemies) != 0:
2596         proutn(_("Are you sure? "))
2597         if ja() == False:
2598             return
2599     # Alternate resting periods (events) with attacks 
2600     game.resting = True
2601     while True:
2602         if delay <= 0:
2603             game.resting = False
2604         if not game.resting:
2605             prout(_("%d stardates left.") % int(game.state.remtime))
2606             return
2607         temp = game.optime = delay
2608         if len(game.enemies):
2609             rtime = randreal(1.0, 2.0)
2610             if rtime < temp:
2611                 temp = rtime
2612             game.optime = temp
2613         if game.optime < delay:
2614             attack(torps_ok=False)
2615         if game.alldone:
2616             return
2617         events()
2618         game.ididit = True
2619         if game.alldone:
2620             return
2621         delay -= temp
2622         # Repair Deathray if long rest at starbase 
2623         if origTime-delay >= 9.99 and game.condition == "docked":
2624             game.damage[DDRAY] = 0.0
2625         # leave if quadrant supernovas
2626         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2627             break
2628     game.resting = False
2629     game.optime = 0
2630
2631 def nova(nov):
2632     "Star goes nova." 
2633     course = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2634     newc = coord(); neighbor = coord(); bump = coord(0, 0)
2635     if withprob(0.05):
2636         # Wow! We've supernova'ed 
2637         supernova(game.quadrant)
2638         return
2639     # handle initial nova 
2640     game.quad[nov.i][nov.j] = IHDOT
2641     prout(crmena(False, IHSTAR, "sector", nov) + _(" novas."))
2642     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2643     game.state.starkl += 1
2644     # Set up queue to recursively trigger adjacent stars 
2645     hits = [nov]
2646     kount = 0
2647     while hits:
2648         offset = coord()
2649         start = hits.pop()
2650         for offset.i in range(-1, 1+1):
2651             for offset.j in range(-1, 1+1):
2652                 if offset.j==0 and offset.i==0:
2653                     continue
2654                 neighbor = start + offset
2655                 if not neighbor.valid_sector():
2656                     continue
2657                 iquad = game.quad[neighbor.i][neighbor.j]
2658                 # Empty space ends reaction
2659                 if iquad in (IHDOT, IHQUEST, IHBLANK, IHT, IHWEB):
2660                     pass
2661                 elif iquad == IHSTAR: # Affect another star 
2662                     if withprob(0.05):
2663                         # This star supernovas 
2664                         supernova(game.quadrant)
2665                         return
2666                     else:
2667                         hits.append(neighbor)
2668                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2669                         game.state.starkl += 1
2670                         proutn(crmena(True, IHSTAR, "sector", neighbor))
2671                         prout(_(" novas."))
2672                         game.quad[neighbor.i][neighbor.j] = IHDOT
2673                         kount += 1
2674                 elif iquad in (IHP, IHW): # Destroy planet 
2675                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2676                     if iquad == IHP:
2677                         game.state.nplankl += 1
2678                     else:
2679                         game.state.worldkl += 1
2680                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2681                     game.iplnet.pclass = "destroyed"
2682                     game.iplnet = None
2683                     game.plnet.invalidate()
2684                     if game.landed:
2685                         finish(FPNOVA)
2686                         return
2687                     game.quad[neighbor.i][neighbor.j] = IHDOT
2688                 elif iquad == IHB: # Destroy base 
2689                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2690                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2691                     game.base.invalidate()
2692                     game.state.basekl += 1
2693                     newcnd()
2694                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2695                     game.quad[neighbor.i][neighbor.j] = IHDOT
2696                 elif iquad in (IHE, IHF): # Buffet ship 
2697                     prout(_("***Starship buffeted by nova."))
2698                     if game.shldup:
2699                         if game.shield >= 2000.0:
2700                             game.shield -= 2000.0
2701                         else:
2702                             diff = 2000.0 - game.shield
2703                             game.energy -= diff
2704                             game.shield = 0.0
2705                             game.shldup = False
2706                             prout(_("***Shields knocked out."))
2707                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2708                     else:
2709                         game.energy -= 2000.0
2710                     if game.energy <= 0:
2711                         finish(FNOVA)
2712                         return
2713                     # add in course nova contributes to kicking starship
2714                     bump += (game.sector-hits[mm]).sgn()
2715                 elif iquad == IHK: # kill klingon 
2716                     deadkl(neighbor, iquad, neighbor)
2717                 elif iquad in (IHC,IHS,IHR): # Damage/destroy big enemies 
2718                     for ll in range(len(game.enemies)):
2719                         if game.enemies[ll].kloc == neighbor:
2720                             break
2721                     game.enemies[ll].kpower -= 800.0 # If firepower is lost, die 
2722                     if game.enemies[ll].kpower <= 0.0:
2723                         deadkl(neighbor, iquad, neighbor)
2724                         break
2725                     newc = neighbor + neighbor - hits[mm]
2726                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2727                     if not newc.valid_sector():
2728                         # can't leave quadrant 
2729                         skip(1)
2730                         break
2731                     iquad1 = game.quad[newc.i][newc.j]
2732                     if iquad1 == IHBLANK:
2733                         proutn(_(", blasted into ") + crmena(False, IHBLANK, "sector", newc))
2734                         skip(1)
2735                         deadkl(neighbor, iquad, newc)
2736                         break
2737                     if iquad1 != IHDOT:
2738                         # can't move into something else 
2739                         skip(1)
2740                         break
2741                     proutn(_(", buffeted to Sector %s") % newc)
2742                     game.quad[neighbor.i][neighbor.j] = IHDOT
2743                     game.quad[newc.i][newc.j] = iquad
2744                     game.enemies[ll].move(newc)
2745     # Starship affected by nova -- kick it away. 
2746     dist = kount*0.1
2747     direc = course[3*(bump.i+1)+bump.j+2]
2748     if direc == 0.0:
2749         dist = 0.0
2750     if dist == 0.0:
2751         return
2752     course = course(bearing=direc, distance=dist)
2753     game.optime = course.time(warp=4)
2754     skip(1)
2755     prout(_("Force of nova displaces starship."))
2756     imove(course, novapush=True)
2757     game.optime = course.time(warp=4)
2758     return
2759         
2760 def supernova(w):
2761     "Star goes supernova."
2762     num = 0; npdead = 0
2763     if w != None: 
2764         nq = copy.copy(w)
2765     else:
2766         # Scheduled supernova -- select star at random. 
2767         stars = 0
2768         nq = coord()
2769         for nq.i in range(GALSIZE):
2770             for nq.j in range(GALSIZE):
2771                 stars += game.state.galaxy[nq.i][nq.j].stars
2772         if stars == 0:
2773             return # nothing to supernova exists 
2774         num = randrange(stars) + 1
2775         for nq.i in range(GALSIZE):
2776             for nq.j in range(GALSIZE):
2777                 num -= game.state.galaxy[nq.i][nq.j].stars
2778                 if num <= 0:
2779                     break
2780             if num <=0:
2781                 break
2782         if idebug:
2783             proutn("=== Super nova here?")
2784             if ja() == True:
2785                 nq = game.quadrant
2786     if not nq == game.quadrant or game.justin:
2787         # it isn't here, or we just entered (treat as enroute) 
2788         if communicating():
2789             skip(1)
2790             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2791             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2792     else:
2793         ns = coord()
2794         # we are in the quadrant! 
2795         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2796         for ns.i in range(QUADSIZE):
2797             for ns.j in range(QUADSIZE):
2798                 if game.quad[ns.i][ns.j]==IHSTAR:
2799                     num -= 1
2800                     if num==0:
2801                         break
2802             if num==0:
2803                 break
2804         skip(1)
2805         prouts(_("***RED ALERT!  RED ALERT!"))
2806         skip(1)
2807         prout(_("***Incipient supernova detected at Sector %s") % ns)
2808         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2809             proutn(_("Emergency override attempts t"))
2810             prouts("***************")
2811             skip(1)
2812             stars()
2813             game.alldone = True
2814     # destroy any Klingons in supernovaed quadrant
2815     kldead = game.state.galaxy[nq.i][nq.j].klingons
2816     game.state.galaxy[nq.i][nq.j].klingons = 0
2817     if nq == game.state.kscmdr:
2818         # did in the Supercommander! 
2819         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2820         game.iscate = False
2821         unschedule(FSCMOVE)
2822         unschedule(FSCDBAS)
2823     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2824     comkills = len(game.state.kcmdr) - len(survivors)
2825     game.state.kcmdr = survivors
2826     kldead -= comkills
2827     if not game.state.kcmdr:
2828         unschedule(FTBEAM)
2829     game.state.remkl -= kldead
2830     # destroy Romulans and planets in supernovaed quadrant 
2831     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2832     game.state.galaxy[nq.i][nq.j].romulans = 0
2833     game.state.nromrem -= nrmdead
2834     # Destroy planets 
2835     for loop in range(game.inplan):
2836         if game.state.planets[loop].quadrant == nq:
2837             game.state.planets[loop].pclass = "destroyed"
2838             npdead += 1
2839     # Destroy any base in supernovaed quadrant
2840     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2841     # If starship caused supernova, tally up destruction 
2842     if w != None:
2843         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2844         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2845         game.state.nplankl += npdead
2846     # mark supernova in galaxy and in star chart 
2847     if game.quadrant == nq or communicating():
2848         game.state.galaxy[nq.i][nq.j].supernova = True
2849     # If supernova destroys last Klingons give special message 
2850     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2851         skip(2)
2852         if w == None:
2853             prout(_("Lucky you!"))
2854         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2855         finish(FWON)
2856         return
2857     # if some Klingons remain, continue or die in supernova 
2858     if game.alldone:
2859         finish(FSNOVAED)
2860     return
2861
2862 # Code from finish.c ends here.
2863
2864 def selfdestruct():
2865     "Self-destruct maneuver. Finish with a BANG!" 
2866     scanner.chew()
2867     if damaged(DCOMPTR):
2868         prout(_("Computer damaged; cannot execute destruct sequence."))
2869         return
2870     prouts(_("---WORKING---")); skip(1)
2871     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2872     prouts("   10"); skip(1)
2873     prouts("       9"); skip(1)
2874     prouts("          8"); skip(1)
2875     prouts("             7"); skip(1)
2876     prouts("                6"); skip(1)
2877     skip(1)
2878     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2879     skip(1)
2880     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2881     skip(1)
2882     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2883     skip(1)
2884     scanner.next()
2885     scanner.chew()
2886     if game.passwd != scanner.token:
2887         prouts(_("PASSWORD-REJECTED;"))
2888         skip(1)
2889         prouts(_("CONTINUITY-EFFECTED"))
2890         skip(2)
2891         return
2892     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2893     prouts("                   5"); skip(1)
2894     prouts("                      4"); skip(1)
2895     prouts("                         3"); skip(1)
2896     prouts("                            2"); skip(1)
2897     prouts("                              1"); skip(1)
2898     if withprob(0.15):
2899         prouts(_("GOODBYE-CRUEL-WORLD"))
2900         skip(1)
2901     kaboom()
2902
2903 def kaboom():
2904     stars()
2905     if game.ship==IHE:
2906         prouts("***")
2907     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2908     skip(1)
2909     stars()
2910     skip(1)
2911     if len(game.enemies) != 0:
2912         whammo = 25.0 * game.energy
2913         l=1
2914         while l <= len(game.enemies):
2915             if game.enemies[l].kpower*game.enemies[l].kdist <= whammo: 
2916                 deadkl(game.enemies[l].kloc, game.quad[game.enemies[l].kloc.i][game.enemies[l].kloc.j], game.enemies[l].kloc)
2917             l += 1
2918     finish(FDILITHIUM)
2919                                 
2920 def killrate():
2921     "Compute our rate of kils over time."
2922     elapsed = game.state.date - game.indate
2923     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2924         return 0
2925     else:
2926         starting = (game.inkling + game.incom + game.inscom)
2927         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2928         return (starting - remaining)/elapsed
2929
2930 def badpoints():
2931     "Compute demerits."
2932     badpt = 5.0*game.state.starkl + \
2933             game.casual + \
2934             10.0*game.state.nplankl + \
2935             300*game.state.nworldkl + \
2936             45.0*game.nhelp +\
2937             100.0*game.state.basekl +\
2938             3.0*game.abandoned
2939     if game.ship == IHF:
2940         badpt += 100.0
2941     elif game.ship == None:
2942         badpt += 200.0
2943     return badpt
2944
2945 def finish(ifin):
2946     # end the game, with appropriate notfications 
2947     igotit = False
2948     game.alldone = True
2949     skip(3)
2950     prout(_("It is stardate %.1f.") % game.state.date)
2951     skip(1)
2952     if ifin == FWON: # Game has been won
2953         if game.state.nromrem != 0:
2954             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2955                   game.state.nromrem)
2956
2957         prout(_("You have smashed the Klingon invasion fleet and saved"))
2958         prout(_("the Federation."))
2959         game.gamewon = True
2960         if game.alive:
2961             badpt = badpoints()
2962             if badpt < 100.0:
2963                 badpt = 0.0     # Close enough!
2964             # killsPerDate >= RateMax
2965             if game.state.date-game.indate < 5.0 or \
2966                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2967                 skip(1)
2968                 prout(_("In fact, you have done so well that Starfleet Command"))
2969                 if game.skill == SKILL_NOVICE:
2970                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2971                 elif game.skill == SKILL_FAIR:
2972                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2973                 elif game.skill == SKILL_GOOD:
2974                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2975                 elif game.skill == SKILL_EXPERT:
2976                     prout(_("promotes you to Commodore Emeritus."))
2977                     skip(1)
2978                     prout(_("Now that you think you're really good, try playing"))
2979                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2980                 elif game.skill == SKILL_EMERITUS:
2981                     skip(1)
2982                     proutn(_("Computer-  "))
2983                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2984                     skip(2)
2985                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2986                     skip(1)
2987                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2988                     skip(1)
2989                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2990                     skip(1)
2991                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2992                     skip(1)
2993                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2994                     skip(2)
2995                     prout(_("Now you can retire and write your own Star Trek game!"))
2996                     skip(1)
2997                 elif game.skill >= SKILL_EXPERT:
2998                     if game.thawed and not idebug:
2999                         prout(_("You cannot get a citation, so..."))
3000                     else:
3001                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3002                         scanner.chew()
3003                         if ja() == True:
3004                             igotit = True
3005             # Only grant long life if alive (original didn't!)
3006             skip(1)
3007             prout(_("LIVE LONG AND PROSPER."))
3008         score()
3009         if igotit:
3010             plaque()        
3011         return
3012     elif ifin == FDEPLETE: # Federation Resources Depleted
3013         prout(_("Your time has run out and the Federation has been"))
3014         prout(_("conquered.  Your starship is now Klingon property,"))
3015         prout(_("and you are put on trial as a war criminal.  On the"))
3016         proutn(_("basis of your record, you are "))
3017         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
3018             prout(_("acquitted."))
3019             skip(1)
3020             prout(_("LIVE LONG AND PROSPER."))
3021         else:
3022             prout(_("found guilty and"))
3023             prout(_("sentenced to death by slow torture."))
3024             game.alive = False
3025         score()
3026         return
3027     elif ifin == FLIFESUP:
3028         prout(_("Your life support reserves have run out, and"))
3029         prout(_("you die of thirst, starvation, and asphyxiation."))
3030         prout(_("Your starship is a derelict in space."))
3031     elif ifin == FNRG:
3032         prout(_("Your energy supply is exhausted."))
3033         skip(1)
3034         prout(_("Your starship is a derelict in space."))
3035     elif ifin == FBATTLE:
3036         prout(_("The %s has been destroyed in battle.") % crmshp())
3037         skip(1)
3038         prout(_("Dulce et decorum est pro patria mori."))
3039     elif ifin == FNEG3:
3040         prout(_("You have made three attempts to cross the negative energy"))
3041         prout(_("barrier which surrounds the galaxy."))
3042         skip(1)
3043         prout(_("Your navigation is abominable."))
3044         score()
3045     elif ifin == FNOVA:
3046         prout(_("Your starship has been destroyed by a nova."))
3047         prout(_("That was a great shot."))
3048         skip(1)
3049     elif ifin == FSNOVAED:
3050         prout(_("The %s has been fried by a supernova.") % crmshp())
3051         prout(_("...Not even cinders remain..."))
3052     elif ifin == FABANDN:
3053         prout(_("You have been captured by the Klingons. If you still"))
3054         prout(_("had a starbase to be returned to, you would have been"))
3055         prout(_("repatriated and given another chance. Since you have"))
3056         prout(_("no starbases, you will be mercilessly tortured to death."))
3057     elif ifin == FDILITHIUM:
3058         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3059     elif ifin == FMATERIALIZE:
3060         prout(_("Starbase was unable to re-materialize your starship."))
3061         prout(_("Sic transit gloria mundi"))
3062     elif ifin == FPHASER:
3063         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3064     elif ifin == FLOST:
3065         prout(_("You and your landing party have been"))
3066         prout(_("converted to energy, disipating through space."))
3067     elif ifin == FMINING:
3068         prout(_("You are left with your landing party on"))
3069         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3070         skip(1)
3071         prout(_("They are very fond of \"Captain Kirk\" soup."))
3072         skip(1)
3073         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3074     elif ifin == FDPLANET:
3075         prout(_("You and your mining party perish."))
3076         skip(1)
3077         prout(_("That was a great shot."))
3078         skip(1)
3079     elif ifin == FSSC:
3080         prout(_("The Galileo is instantly annihilated by the supernova."))
3081         prout(_("You and your mining party are atomized."))
3082         skip(1)
3083         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3084         prout(_("joins the Romulans, wreaking terror on the Federation."))
3085     elif ifin == FPNOVA:
3086         prout(_("You and your mining party are atomized."))
3087         skip(1)
3088         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3089         prout(_("joins the Romulans, wreaking terror on the Federation."))
3090     elif ifin == FSTRACTOR:
3091         prout(_("The shuttle craft Galileo is also caught,"))
3092         prout(_("and breaks up under the strain."))
3093         skip(1)
3094         prout(_("Your debris is scattered for millions of miles."))
3095         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3096     elif ifin == FDRAY:
3097         prout(_("The mutants attack and kill Spock."))
3098         prout(_("Your ship is captured by Klingons, and"))
3099         prout(_("your crew is put on display in a Klingon zoo."))
3100     elif ifin == FTRIBBLE:
3101         prout(_("Tribbles consume all remaining water,"))
3102         prout(_("food, and oxygen on your ship."))
3103         skip(1)
3104         prout(_("You die of thirst, starvation, and asphyxiation."))
3105         prout(_("Your starship is a derelict in space."))
3106     elif ifin == FHOLE:
3107         prout(_("Your ship is drawn to the center of the black hole."))
3108         prout(_("You are crushed into extremely dense matter."))
3109     elif ifin == FCREW:
3110         prout(_("Your last crew member has died."))
3111     if game.ship == IHF:
3112         game.ship = None
3113     elif game.ship == IHE:
3114         game.ship = IHF
3115     game.alive = False
3116     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
3117         goodies = game.state.remres/game.inresor
3118         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3119         if goodies/baddies >= randreal(1.0, 1.5):
3120             prout(_("As a result of your actions, a treaty with the Klingon"))
3121             prout(_("Empire has been signed. The terms of the treaty are"))
3122             if goodies/baddies >= randreal(3.0):
3123                 prout(_("favorable to the Federation."))
3124                 skip(1)
3125                 prout(_("Congratulations!"))
3126             else:
3127                 prout(_("highly unfavorable to the Federation."))
3128         else:
3129             prout(_("The Federation will be destroyed."))
3130     else:
3131         prout(_("Since you took the last Klingon with you, you are a"))
3132         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3133         prout(_("statue in your memory. Rest in peace, and try not"))
3134         prout(_("to think about pigeons."))
3135         game.gamewon = True
3136     score()
3137
3138 def score():
3139     "Compute player's score."
3140     timused = game.state.date - game.indate
3141     iskill = game.skill
3142     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
3143         timused = 5.0
3144     perdate = killrate()
3145     ithperd = 500*perdate + 0.5
3146     iwon = 0
3147     if game.gamewon:
3148         iwon = 100*game.skill
3149     if game.ship == IHE: 
3150         klship = 0
3151     elif game.ship == IHF: 
3152         klship = 1
3153     else:
3154         klship = 2
3155     if not game.gamewon:
3156         game.state.nromrem = 0 # None captured if no win
3157     iscore = 10*(game.inkling - game.state.remkl) \
3158              + 50*(game.incom - len(game.state.kcmdr)) \
3159              + ithperd + iwon \
3160              + 20*(game.inrom - game.state.nromrem) \
3161              + 200*(game.inscom - game.state.nscrem) \
3162              - game.state.nromrem \
3163              - badpoints()
3164     if not game.alive:
3165         iscore -= 200
3166     skip(2)
3167     prout(_("Your score --"))
3168     if game.inrom - game.state.nromrem:
3169         prout(_("%6d Romulans destroyed                 %5d") %
3170               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3171     if game.state.nromrem:
3172         prout(_("%6d Romulans captured                  %5d") %
3173               (game.state.nromrem, game.state.nromrem))
3174     if game.inkling - game.state.remkl:
3175         prout(_("%6d ordinary Klingons destroyed        %5d") %
3176               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3177     if game.incom - len(game.state.kcmdr):
3178         prout(_("%6d Klingon commanders destroyed       %5d") %
3179               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3180     if game.inscom - game.state.nscrem:
3181         prout(_("%6d Super-Commander destroyed          %5d") %
3182               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3183     if ithperd:
3184         prout(_("%6.2f Klingons per stardate              %5d") %
3185               (perdate, ithperd))
3186     if game.state.starkl:
3187         prout(_("%6d stars destroyed by your action     %5d") %
3188               (game.state.starkl, -5*game.state.starkl))
3189     if game.state.nplankl:
3190         prout(_("%6d planets destroyed by your action   %5d") %
3191               (game.state.nplankl, -10*game.state.nplankl))
3192     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3193         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3194               (game.state.nplankl, -300*game.state.nworldkl))
3195     if game.state.basekl:
3196         prout(_("%6d bases destroyed by your action     %5d") %
3197               (game.state.basekl, -100*game.state.basekl))
3198     if game.nhelp:
3199         prout(_("%6d calls for help from starbase       %5d") %
3200               (game.nhelp, -45*game.nhelp))
3201     if game.casual:
3202         prout(_("%6d casualties incurred                %5d") %
3203               (game.casual, -game.casual))
3204     if game.abandoned:
3205         prout(_("%6d crew abandoned in space            %5d") %
3206               (game.abandoned, -3*game.abandoned))
3207     if klship:
3208         prout(_("%6d ship(s) lost or destroyed          %5d") %
3209               (klship, -100*klship))
3210     if not game.alive:
3211         prout(_("Penalty for getting yourself killed        -200"))
3212     if game.gamewon:
3213         proutn(_("Bonus for winning "))
3214         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3215         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3216         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3217         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
3218         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
3219         prout("           %5d" % iwon)
3220     skip(1)
3221     prout(_("TOTAL SCORE                               %5d") % iscore)
3222
3223 def plaque():
3224     "Emit winner's commemmorative plaque." 
3225     skip(2)
3226     while True:
3227         proutn(_("File or device name for your plaque: "))
3228         winner = cgetline()
3229         try:
3230             fp = open(winner, "w")
3231             break
3232         except IOError:
3233             prout(_("Invalid name."))
3234
3235     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3236     winner = cgetline()
3237     # The 38 below must be 64 for 132-column paper 
3238     nskip = 38 - len(winner)/2
3239     fp.write("\n\n\n\n")
3240     # --------DRAW ENTERPRISE PICTURE. 
3241     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3242     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3243     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3244     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3245     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3246     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3247     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3248     fp.write("                                      EEE           E          E            E  E\n")
3249     fp.write("                                                       E         E          E  E\n")
3250     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3251     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3252     fp.write("                                                    :E    :                 EEEE       E\n")
3253     fp.write("                                                   .-E   -:-----                       E\n")
3254     fp.write("                                                    :E    :                            E\n")
3255     fp.write("                                                      EE  :                    EEEEEEEE\n")
3256     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3257     fp.write("\n\n\n")
3258     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3259     fp.write("\n\n\n\n")
3260     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3261     fp.write("\n")
3262     fp.write(_("                                                Starfleet Command bestows to you\n"))
3263     fp.write("\n")
3264     fp.write("%*s%s\n\n" % (nskip, "", winner))
3265     fp.write(_("                                                           the rank of\n\n"))
3266     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3267     fp.write("                                                          ")
3268     if game.skill ==  SKILL_EXPERT:
3269         fp.write(_(" Expert level\n\n"))
3270     elif game.skill == SKILL_EMERITUS:
3271         fp.write(_("Emeritus level\n\n"))
3272     else:
3273         fp.write(_(" Cheat level\n\n"))
3274     timestring = time.ctime()
3275     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3276                     (timestring+4, timestring+20, timestring+11))
3277     fp.write(_("                                                        Your score:  %d\n\n") % iscore)
3278     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
3279     fp.close()
3280
3281 # Code from io.c begins here
3282
3283 rows = linecount = 0    # for paging 
3284 stdscr = None
3285 replayfp = None
3286 fullscreen_window = None
3287 srscan_window     = None
3288 report_window     = None
3289 status_window     = None
3290 lrscan_window     = None
3291 message_window    = None
3292 prompt_window     = None
3293 curwnd = None
3294
3295 def iostart():
3296     global stdscr, rows
3297     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3298     gettext.textdomain("sst")
3299     if not (game.options & OPTION_CURSES):
3300         ln_env = os.getenv("LINES")
3301         if ln_env:
3302             rows = ln_env
3303         else:
3304             rows = 25
3305     else:
3306         stdscr = curses.initscr()
3307         stdscr.keypad(True)
3308         curses.nonl()
3309         curses.cbreak()
3310         global fullscreen_window, srscan_window, report_window, status_window
3311         global lrscan_window, message_window, prompt_window
3312         (rows, columns)   = stdscr.getmaxyx()
3313         fullscreen_window = stdscr
3314         srscan_window     = curses.newwin(12, 25, 0,       0)
3315         report_window     = curses.newwin(11, 0,  1,       25)
3316         status_window     = curses.newwin(10, 0,  1,       39)
3317         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3318         message_window    = curses.newwin(0,  0,  12,      0)
3319         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3320         message_window.scrollok(True)
3321         setwnd(fullscreen_window)
3322
3323 def ioend():
3324     "Wrap up I/O."
3325     if game.options & OPTION_CURSES:
3326         stdscr.keypad(False)
3327         curses.echo()
3328         curses.nocbreak()
3329         curses.endwin()
3330
3331 def waitfor():
3332     "Wait for user action -- OK to do nothing if on a TTY"
3333     if game.options & OPTION_CURSES:
3334         stdscr.getch()
3335
3336 def announce():
3337     skip(1)
3338     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3339     skip(1)
3340
3341 def pause_game():
3342     if game.skill > SKILL_FAIR:
3343         prompt = _("[CONTINUE?]")
3344     else:
3345         prompt = _("[PRESS ENTER TO CONTINUE]")
3346
3347     if game.options & OPTION_CURSES:
3348         drawmaps(0)
3349         setwnd(prompt_window)
3350         prompt_window.clear()
3351         prompt_window.addstr(prompt)
3352         prompt_window.getstr()
3353         prompt_window.clear()
3354         prompt_window.refresh()
3355         setwnd(message_window)
3356     else:
3357         global linecount
3358         sys.stdout.write('\n')
3359         proutn(prompt)
3360         raw_input()
3361         for j in range(rows):
3362             sys.stdout.write('\n')
3363         linecount = 0
3364
3365 def skip(i):
3366     "Skip i lines.  Pause game if this would cause a scrolling event."
3367     for dummy in range(i):
3368         if game.options & OPTION_CURSES:
3369             (y, x) = curwnd.getyx()
3370             (my, mx) = curwnd.getmaxyx()
3371             if curwnd == message_window and y >= my - 3:
3372                 pause_game()
3373                 clrscr()
3374             else:
3375                 try:
3376                     curwnd.move(y+1, 0)
3377                 except curses.error:
3378                     pass
3379         else:
3380             global linecount
3381             linecount += 1
3382             if rows and linecount >= rows:
3383                 pause_game()
3384             else:
3385                 sys.stdout.write('\n')
3386
3387 def proutn(line):
3388     "Utter a line with no following line feed."
3389     if game.options & OPTION_CURSES:
3390         curwnd.addstr(line)
3391         curwnd.refresh()
3392     else:
3393         sys.stdout.write(line)
3394         sys.stdout.flush()
3395
3396 def prout(line):
3397     proutn(line)
3398     skip(1)
3399
3400 def prouts(line):
3401     "Emit slowly!" 
3402     for c in line:
3403         if not replayfp or replayfp.closed:     # Don't slow down replays
3404             time.sleep(0.03)
3405         proutn(c)
3406         if game.options & OPTION_CURSES:
3407             curwnd.refresh()
3408         else:
3409             sys.stdout.flush()
3410     if not replayfp or replayfp.closed:
3411         time.sleep(0.03)
3412
3413 def cgetline():
3414     "Get a line of input."
3415     if game.options & OPTION_CURSES:
3416         line = curwnd.getstr() + "\n"
3417         curwnd.refresh()
3418     else:
3419         if replayfp and not replayfp.closed:
3420             while True:
3421                 line = replayfp.readline()
3422                 proutn(line)
3423                 if line == '':
3424                     prout("*** Replay finished")
3425                     replayfp.close()
3426                     break
3427                 elif line[0] != "#":
3428                     break
3429         else:
3430             line = raw_input() + "\n"
3431     if logfp:
3432         logfp.write(line)
3433     return line
3434
3435 def setwnd(wnd):
3436     "Change windows -- OK for this to be a no-op in tty mode."
3437     global curwnd
3438     if game.options & OPTION_CURSES:
3439         curwnd = wnd
3440         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3441
3442 def clreol():
3443     "Clear to end of line -- can be a no-op in tty mode" 
3444     if game.options & OPTION_CURSES:
3445         curwnd.clrtoeol()
3446         curwnd.refresh()
3447
3448 def clrscr():
3449     "Clear screen -- can be a no-op in tty mode."
3450     global linecount
3451     if game.options & OPTION_CURSES:
3452        curwnd.clear()
3453        curwnd.move(0, 0)
3454        curwnd.refresh()
3455     linecount = 0
3456     
3457 def highvideo():
3458     "Set highlight video, if this is reasonable."
3459     if game.options & OPTION_CURSES:
3460         curwnd.attron(curses.A_REVERSE)
3461  
3462 #
3463 # Things past this point have policy implications.
3464
3465
3466 def drawmaps(mode):
3467     "Hook to be called after moving to redraw maps."
3468     if game.options & OPTION_CURSES:
3469         if mode == 1:
3470             sensor()
3471         setwnd(srscan_window)
3472         curwnd.move(0, 0)
3473         srscan()
3474         if mode != 2:
3475             setwnd(status_window)
3476             status_window.clear()
3477             status_window.move(0, 0)
3478             setwnd(report_window)
3479             report_window.clear()
3480             report_window.move(0, 0)
3481             status()
3482             setwnd(lrscan_window)
3483             lrscan_window.clear()
3484             lrscan_window.move(0, 0)
3485             lrscan(silent=False)
3486
3487 def put_srscan_sym(w, sym):
3488     "Emit symbol for short-range scan."
3489     srscan_window.move(w.i+1, w.j*2+2)
3490     srscan_window.addch(sym)
3491     srscan_window.refresh()
3492
3493 def boom(w):
3494     "Enemy fall down, go boom."  
3495     if game.options & OPTION_CURSES:
3496         drawmaps(2)
3497         setwnd(srscan_window)
3498         srscan_window.attron(curses.A_REVERSE)
3499         put_srscan_sym(w, game.quad[w.i][w.j])
3500         #sound(500)
3501         #time.sleep(1.0)
3502         #nosound()
3503         srscan_window.attroff(curses.A_REVERSE)
3504         put_srscan_sym(w, game.quad[w.i][w.j])
3505         curses.delay_output(500)
3506         setwnd(message_window) 
3507
3508 def warble():
3509     "Sound and visual effects for teleportation."
3510     if game.options & OPTION_CURSES:
3511         drawmaps(2)
3512         setwnd(message_window)
3513         #sound(50)
3514     prouts("     . . . . .     ")
3515     if game.options & OPTION_CURSES:
3516         #curses.delay_output(1000)
3517         #nosound()
3518         pass
3519
3520 def tracktorpedo(origin, w, step, i, n, iquad):
3521     "Torpedo-track animation." 
3522     if not game.options & OPTION_CURSES:
3523         if step == 1:
3524             if n != 1:
3525                 skip(1)
3526                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3527             else:
3528                 skip(1)
3529                 proutn(_("Torpedo track- "))
3530         elif step==4 or step==9: 
3531             skip(1)
3532         proutn("%s   " % w)
3533     else:
3534         if not damaged(DSRSENS) or game.condition=="docked":
3535             if i != 0 and step == 1:
3536                 drawmaps(2)
3537                 time.sleep(0.4)
3538             if (iquad==IHDOT) or (iquad==IHBLANK):
3539                 put_srscan_sym(w, '+')
3540                 #sound(step*10)
3541                 #time.sleep(0.1)
3542                 #nosound()
3543                 put_srscan_sym(w, iquad)
3544             else:
3545                 curwnd.attron(curses.A_REVERSE)
3546                 put_srscan_sym(w, iquad)
3547                 #sound(500)
3548                 #time.sleep(1.0)
3549                 #nosound()
3550                 curwnd.attroff(curses.A_REVERSE)
3551                 put_srscan_sym(w, iquad)
3552         else:
3553             proutn("%s   " % w)
3554
3555 def makechart():
3556     "Display the current galaxy chart."
3557     if game.options & OPTION_CURSES:
3558         setwnd(message_window)
3559         message_window.clear()
3560     chart()
3561     if game.options & OPTION_TTY:
3562         skip(1)
3563
3564 NSYM    = 14
3565
3566 def prstat(txt, data):
3567     proutn(txt)
3568     if game.options & OPTION_CURSES:
3569         skip(1)
3570         setwnd(status_window)
3571     else:
3572         proutn(" " * (NSYM - len(txt)))
3573     proutn(data)
3574     skip(1)
3575     if game.options & OPTION_CURSES:
3576         setwnd(report_window)
3577
3578 # Code from moving.c begins here
3579
3580 def imove(course=None, novapush=False):
3581     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3582     w = coord(); final = coord()
3583     trbeam = False
3584
3585     def no_quad_change():
3586         # No quadrant change -- compute new average enemy distances 
3587         game.quad[game.sector.i][game.sector.j] = game.ship
3588         if game.enemies:
3589             for enemy in game.enemies:
3590                 finald = (w-enemy.kloc).distance()
3591                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3592                 enemy.kdist = finald
3593             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
3594             if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3595                 attack(torps_ok=False)
3596             for enemy in game.enemies:
3597                 enemy.kavgd = enemy.kdist
3598         newcnd()
3599         drawmaps(0)
3600         setwnd(message_window)
3601     w.i = w.j = 0
3602     if game.inorbit:
3603         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3604         game.inorbit = False
3605     angle = ((15.0 - course.bearing) * 0.5235988)
3606     deltax = -math.sin(angle)
3607     deltay = math.cos(angle)
3608     if math.fabs(deltax) > math.fabs(deltay):
3609         bigger = math.fabs(deltax)
3610     else:
3611         bigger = math.fabs(deltay)
3612     deltay /= bigger
3613     deltax /= bigger
3614     # If tractor beam is to occur, don't move full distance 
3615     if game.state.date+game.optime >= scheduled(FTBEAM):
3616         trbeam = True
3617         game.condition = "red"
3618         course.distance = course.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3619         game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3620     # Move within the quadrant 
3621     game.quad[game.sector.i][game.sector.j] = IHDOT
3622     x = game.sector.i
3623     y = game.sector.j
3624     for m in range(course.moves):
3625         x += deltax
3626         y += deltay
3627         w.i = int(round(x))
3628         w.j = int(round(y))
3629         if not w.valid_sector():
3630             # Leaving quadrant -- allow final enemy attack 
3631             # Don't do it if being pushed by Nova 
3632             if len(game.enemies) != 0 and not novapush:
3633                 newcnd()
3634                 for enemy in game.enemies:
3635                     finald = (w - enemy.kloc).distance()
3636                     enemy.kavgd = 0.5 * (finald + enemy.kdist)
3637                 #
3638                 # Stas Sergeev added the condition
3639                 # that attacks only happen if Klingons
3640                 # are present and your skill is good.
3641                 # 
3642                 if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3643                     attack(torps_ok=False)
3644                 if game.alldone:
3645                     return
3646             # check for edge of galaxy 
3647             w = course.final
3648             kinks = 0
3649             while True:
3650                 kink = False
3651                 if w.i < 0:
3652                     w.i = -w.i
3653                     kink = True
3654                 if w.j < 0:
3655                     w.j = -w.j
3656                     kink = True
3657                 if w.i >= GALSIZE*QUADSIZE:
3658                     w.i = (GALSIZE*QUADSIZE*2) - w.i
3659                     kink = True
3660                 if w.j >= GALSIZE*QUADSIZE:
3661                     w.j = (GALSIZE*QUADSIZE*2) - w.j
3662                     kink = True
3663                 if kink:
3664                     kinks += 1
3665                 else:
3666                     break
3667             if kinks:
3668                 game.nkinks += 1
3669                 if game.nkinks == 3:
3670                     # Three strikes -- you're out! 
3671                     finish(FNEG3)
3672                     return
3673                 skip(1)
3674                 prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3675                 prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3676                 prout(_("YOU WILL BE DESTROYED."))
3677             # Compute final position in new quadrant 
3678             if trbeam: # Don't bother if we are to be beamed 
3679                 return
3680             game.quadrant.i = w.i/QUADSIZE
3681             game.quadrant.j = w.j/QUADSIZE
3682             game.sector.i = w.i - (QUADSIZE*game.quadrant.i)
3683             game.sector.j = w.j - (QUADSIZE*game.quadrant.j)
3684             skip(1)
3685             prout(_("Entering Quadrant %s.") % game.quadrant)
3686             game.quad[game.sector.i][game.sector.j] = game.ship
3687             newqad()
3688             if game.skill>SKILL_NOVICE:
3689                 attack(torps_ok=False)  
3690             return
3691         iquad = game.quad[w.i][w.j]
3692         if iquad != IHDOT:
3693             # object encountered in flight path 
3694             stopegy = 50.0*course.distance/game.optime
3695             course.distance = (game.sector - w).distance() / (QUADSIZE * 1.0)
3696             if iquad in (IHT, IHK, IHC, IHS, IHR, IHQUEST):
3697                 game.sector = w
3698                 for enemy in game.enemies:
3699                     if enemy.kloc == game.sector:
3700                         break
3701                 collision(rammed=False, enemy=enemy)
3702                 final = game.sector
3703             elif iquad == IHBLANK:
3704                 skip(1)
3705                 prouts(_("***RED ALERT!  RED ALERT!"))
3706                 skip(1)
3707                 proutn("***" + crmshp())
3708                 proutn(_(" pulled into black hole at Sector %s") % w)
3709                 # Getting pulled into a black hole was certain
3710                 # death in Almy's original.  Stas Sergeev added a
3711                 # possibility that you'll get timewarped instead.
3712                 n=0
3713                 for m in range(NDEVICES):
3714                     if game.damage[m]>0: 
3715                         n += 1
3716                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3717                 if (game.options & OPTION_BLKHOLE) and withprob(1-probf): 
3718                     timwrp()
3719                 else: 
3720                     finish(FHOLE)
3721                 return
3722             else:
3723                 # something else 
3724                 skip(1)
3725                 proutn(crmshp())
3726                 if iquad == IHWEB:
3727                     prout(_(" encounters Tholian web at %s;") % w)
3728                 else:
3729                     prout(_(" blocked by object at %s;") % w)
3730                 proutn(_("Emergency stop required "))
3731                 prout(_("%2d units of energy.") % int(stopegy))
3732                 game.energy -= stopegy
3733                 final.i = int(round(deltax))
3734                 final.j = int(round(deltay))
3735                 game.sector = final
3736                 if game.energy <= 0:
3737                     finish(FNRG)
3738                     return
3739             # We're here!
3740             no_quad_change()
3741             return
3742         course.distance = (game.sector - w).distance() / (QUADSIZE * 1.0)
3743         game.sector = w
3744     final = game.sector
3745     no_quad_change()
3746     return
3747
3748 def dock(verbose):
3749     "Dock our ship at a starbase."
3750     scanner.chew()
3751     if game.condition == "docked" and verbose:
3752         prout(_("Already docked."))
3753         return
3754     if game.inorbit:
3755         prout(_("You must first leave standard orbit."))
3756         return
3757     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3758         prout(crmshp() + _(" not adjacent to base."))
3759         return
3760     game.condition = "docked"
3761     if "verbose":
3762         prout(_("Docked."))
3763     game.ididit = True
3764     if game.energy < game.inenrg:
3765         game.energy = game.inenrg
3766     game.shield = game.inshld
3767     game.torps = game.intorps
3768     game.lsupres = game.inlsr
3769     game.state.crew = FULLCREW
3770     if not damaged(DRADIO) and \
3771         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3772         # get attack report from base 
3773         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3774         attackreport(False)
3775         game.iseenit = True
3776  
3777 # This program originally required input in terms of a (clock)
3778 # direction and distance. Somewhere in history, it was changed to
3779 # cartesian coordinates. So we need to convert.  Probably
3780 # "manual" input should still be done this way -- it's a real
3781 # pain if the computer isn't working! Manual mode is still confusing
3782 # because it involves giving x and y motions, yet the coordinates
3783 # are always displayed y - x, where +y is downward!
3784
3785 def cartesian(loc1=None, loc2=None):
3786     if loc1 is None:
3787         return game.quadrant * QUADSIZE + game.sector
3788     elif loc2 is None:
3789         return game.quadrant * QUADSIZE + loc1
3790     else:
3791         return loc1 * QUADSIZE + loc2
3792
3793 def getcourse(isprobe):
3794     "Get a course and distance from the user."
3795     key = 0
3796     dquad = copy.copy(game.quadrant)
3797     navmode = "unspecified"
3798     itemp = "curt"
3799     dsect = coord()
3800     iprompt = False
3801     if game.landed and not isprobe:
3802         prout(_("Dummy! You can't leave standard orbit until you"))
3803         proutn(_("are back aboard the ship."))
3804         scanner.chew()
3805         raise TrekError
3806     while navmode == "unspecified":
3807         if damaged(DNAVSYS):
3808             if isprobe:
3809                 prout(_("Computer damaged; manual navigation only"))
3810             else:
3811                 prout(_("Computer damaged; manual movement only"))
3812             scanner.chew()
3813             navmode = "manual"
3814             key = "IHEOL"
3815             break
3816         key = scanner.next()
3817         if key == "IHEOL":
3818             proutn(_("Manual or automatic- "))
3819             iprompt = True
3820             scanner.chew()
3821         elif key == "IHALPHA":
3822             if scanner.sees("manual"):
3823                 navmode = "manual"
3824                 key = scanner.next()
3825                 break
3826             elif scanner.sees("automatic"):
3827                 navmode = "automatic"
3828                 key = scanner.next()
3829                 break
3830             else:
3831                 huh()
3832                 scanner.chew()
3833                 raise TrekError
3834         else: # numeric 
3835             if isprobe:
3836                 prout(_("(Manual navigation assumed.)"))
3837             else:
3838                 prout(_("(Manual movement assumed.)"))
3839             navmode = "manual"
3840             break
3841     delta = coord()
3842     if navmode == "automatic":
3843         while key == "IHEOL":
3844             if isprobe:
3845                 proutn(_("Target quadrant or quadrant&sector- "))
3846             else:
3847                 proutn(_("Destination sector or quadrant&sector- "))
3848             scanner.chew()
3849             iprompt = True
3850             key = scanner.next()
3851         if key != "IHREAL":
3852             huh()
3853             raise TrekError
3854         xi = int(round(scanner.real))-1
3855         key = scanner.next()
3856         if key != "IHREAL":
3857             huh()
3858             raise TrekError
3859         xj = int(round(scanner.real))-1
3860         key = scanner.next()
3861         if key == "IHREAL":
3862             # both quadrant and sector specified 
3863             xk = int(round(scanner.real))-1
3864             key = scanner.next()
3865             if key != "IHREAL":
3866                 huh()
3867                 raise TrekError
3868             xl = int(round(scanner.real))-1
3869             dquad.i = xi
3870             dquad.j = xj
3871             dsect.i = xk
3872             dsect.j = xl
3873         else:
3874             # only one pair of numbers was specified
3875             if isprobe:
3876                 # only quadrant specified -- go to center of dest quad 
3877                 dquad.i = xi
3878                 dquad.j = xj
3879                 dsect.j = dsect.i = 4   # preserves 1-origin behavior
3880             else:
3881                 # only sector specified
3882                 dsect.i = xi
3883                 dsect.j = xj
3884             itemp = "normal"
3885         if not dquad.valid_quadrant() or not dsect.valid_sector():
3886             huh()
3887             raise TrekError
3888         skip(1)
3889         if not isprobe:
3890             if itemp > "curt":
3891                 if iprompt:
3892                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
3893             else:
3894                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
3895         # the actual deltas get computed here
3896         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
3897         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
3898     else: # manual 
3899         while key == "IHEOL":
3900             proutn(_("X and Y displacements- "))
3901             scanner.chew()
3902             iprompt = True
3903             key = scanner.next()
3904         itemp = "verbose"
3905         if key != "IHREAL":
3906             huh()
3907             raise TrekError
3908         delta.j = scanner.real
3909         key = scanner.next()
3910         if key != "IHREAL":
3911             huh()
3912             raise TrekError
3913         delta.i = scanner.real
3914     # Check for zero movement 
3915     if delta.i == 0 and delta.j == 0:
3916         scanner.chew()
3917         raise TrekError
3918     if itemp == "verbose" and not isprobe:
3919         skip(1)
3920         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
3921     scanner.chew()
3922     return course(bearing=delta.bearing(), distance=delta.distance())
3923
3924 class course:
3925     def __init__(self, bearing, distance, origin=None): 
3926         self.distance = distance
3927         self.bearing = bearing
3928         if origin is None:
3929             self.origin = cartesian(game.quadrant, game.sector)
3930         else:
3931             self.origin = origin
3932         # The bearing() code we inherited from FORTRAN is actually computing
3933         # clockface directions!
3934         if self.bearing < 0.0:
3935             self.bearing += 12.0
3936         self.angle = ((15.0 - self.bearing) * 0.5235988)
3937         if origin is None:
3938             self.location = cartesian(game.quadrant, game.sector)
3939         else:
3940             self.location = cartesian(game.quadrant, origin)
3941         self.increment = coord(-math.sin(self.angle), math.cos(self.angle))
3942         bigger = max(abs(self.increment.i), abs(self.increment.j))
3943         self.increment /= bigger
3944         self.moves = int(round(10*self.distance*bigger))
3945         self.final = (self.location + self.moves*self.increment).roundtogrid()
3946     def next(self, grain=1):
3947         "Next step on course."
3948         self.moves -=1
3949         self.nextlocation = self.location + self.increment
3950         oldloc = (self.location/grain).roundtogrid()
3951         newloc = (self.nextlocation/grain).roundtogrid()
3952         self.location = self.nextlocation
3953         if newloc != oldloc:
3954             return True
3955         else:
3956             return False
3957     def quadrant(self):
3958         return self.location.quadrant()
3959     def sector(self):
3960         return self.location.sector()
3961     def power(self, warp):
3962         return self.distance*(warp**3)*(game.shldup+1)
3963     def time(self, warp):
3964         return 10.0*self.distance/warp**2
3965
3966 def impulse():
3967     "Move under impulse power."
3968     game.ididit = False
3969     if damaged(DIMPULS):
3970         scanner.chew()
3971         skip(1)
3972         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
3973         return
3974     if game.energy > 30.0:
3975         try:
3976             course = getcourse(isprobe=False)
3977         except TrekError:
3978             return
3979         power = 20.0 + 100.0*course.distance
3980     else:
3981         power = 30.0
3982     if power >= game.energy:
3983         # Insufficient power for trip 
3984         skip(1)
3985         prout(_("First Officer Spock- \"Captain, the impulse engines"))
3986         prout(_("require 20.0 units to engage, plus 100.0 units per"))
3987         if game.energy > 30:
3988             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
3989                      int(0.01 * (game.energy-20.0)-0.05))
3990             prout(_(" quadrants.\""))
3991         else:
3992             prout(_("quadrant.  They are, therefore, useless.\""))
3993         scanner.chew()
3994         return
3995     # Make sure enough time is left for the trip 
3996     game.optime = course.dist/0.095
3997     if game.optime >= game.state.remtime:
3998         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
3999         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4000         proutn(_("we dare spend the time?\" "))
4001         if ja() == False:
4002             return
4003     # Activate impulse engines and pay the cost 
4004     imove(course, novapush=False)
4005     game.ididit = True
4006     if game.alldone:
4007         return
4008     power = 20.0 + 100.0*course.dist
4009     game.energy -= power
4010     game.optime = course.dist/0.095
4011     if game.energy <= 0:
4012         finish(FNRG)
4013     return
4014
4015 def warp(course, involuntary):
4016     "ove under warp drive."
4017     blooey = False; twarp = False
4018     if not involuntary: # Not WARPX entry 
4019         game.ididit = False
4020         if game.damage[DWARPEN] > 10.0:
4021             scanner.chew()
4022             skip(1)
4023             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4024             return
4025         if damaged(DWARPEN) and game.warpfac > 4.0:
4026             scanner.chew()
4027             skip(1)
4028             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4029             prout(_("  is repaired, I can only give you warp 4.\""))
4030             return
4031         # Read in course and distance
4032         if course==None:
4033             try:
4034                 course = getcourse(isprobe=False)
4035             except TrekError:
4036                 return
4037         # Make sure starship has enough energy for the trip
4038         # Note: this formula is slightly different from the C version,
4039         # and lets you skate a bit closer to the edge.
4040         if course.power(game.warpfac) >= game.energy:
4041             # Insufficient power for trip 
4042             game.ididit = False
4043             skip(1)
4044             prout(_("Engineering to bridge--"))
4045             if not game.shldup or 0.5*power > game.energy:
4046                 iwarp = (game.energy/(course.dist+0.05)) ** 0.333333333
4047                 if iwarp <= 0:
4048                     prout(_("We can't do it, Captain. We don't have enough energy."))
4049                 else:
4050                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4051                     if game.shldup:
4052                         prout(",")
4053                         prout(_("if you'll lower the shields."))
4054                     else:
4055                         prout(".")
4056             else:
4057                 prout(_("We haven't the energy to go that far with the shields up."))
4058             return                              
4059         # Make sure enough time is left for the trip 
4060         game.optime = course.time(game.warpfac)
4061         if game.optime >= 0.8*game.state.remtime:
4062             skip(1)
4063             prout(_("First Officer Spock- \"Captain, I compute that such"))
4064             proutn(_("  a trip would require approximately %2.0f") %
4065                    (100.0*game.optime/game.state.remtime))
4066             prout(_(" percent of our"))
4067             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4068             if ja() == False:
4069                 game.ididit = False
4070                 game.optime=0 
4071                 return
4072     # Entry WARPX 
4073     if game.warpfac > 6.0:
4074         # Decide if engine damage will occur
4075         # ESR: Seems wrong. Probability of damage goes *down* with distance? 
4076         prob = course.dist*(6.0-game.warpfac)**2/66.666666666
4077         if prob > randreal():
4078             blooey = True
4079             course.distance = randreal(course.distance)
4080         # Decide if time warp will occur 
4081         if 0.5*course.dist*math.pow(7.0,game.warpfac-10.0) > randreal():
4082             twarp = True
4083         if idebug and game.warpfac==10 and not twarp:
4084             blooey = False
4085             proutn("=== Force time warp? ")
4086             if ja() == True:
4087                 twarp = True
4088         if blooey or twarp:
4089             # If time warp or engine damage, check path 
4090             # If it is obstructed, don't do warp or damage 
4091             angle = ((15.0-course.bearing)*0.5235998)
4092             deltax = -math.sin(angle)
4093             deltay = math.cos(angle)
4094             if math.fabs(deltax) > math.fabs(deltay):
4095                 bigger = math.fabs(deltax)
4096             else:
4097                 bigger = math.fabs(deltay)
4098             deltax /= bigger
4099             deltay /= bigger
4100             n = 10.0 * course.distance * bigger +0.5
4101             x = game.sector.i
4102             y = game.sector.j
4103             for l in range(1, n+1):
4104                 x += deltax
4105                 ix = x + 0.5
4106                 y += deltay
4107                 iy = y +0.5
4108                 if not coord(ix, iy).valid_sector():
4109                     break
4110                 if game.quad[ix][iy] != IHDOT:
4111                     blooey = False
4112                     twarp = False
4113     # Activate Warp Engines and pay the cost 
4114     imove(course, novapush=False)
4115     if game.alldone:
4116         return
4117     game.energy -= course.power(game.warpfac)
4118     if game.energy <= 0:
4119         finish(FNRG)
4120     game.optime = course.time(game.warpfac)
4121     if twarp:
4122         timwrp()
4123     if blooey:
4124         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4125         skip(1)
4126         prout(_("Engineering to bridge--"))
4127         prout(_("  Scott here.  The warp engines are damaged."))
4128         prout(_("  We'll have to reduce speed to warp 4."))
4129     game.ididit = True
4130     return
4131
4132 def setwarp():
4133     "Change the warp factor."
4134     while True:
4135         key=scanner.next()
4136         if key != "IHEOL":
4137             break
4138         scanner.chew()
4139         proutn(_("Warp factor- "))
4140     scanner.chew()
4141     if key != "IHREAL":
4142         huh()
4143         return
4144     if game.damage[DWARPEN] > 10.0:
4145         prout(_("Warp engines inoperative."))
4146         return
4147     if damaged(DWARPEN) and scanner.real > 4.0:
4148         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4149         prout(_("  but right now we can only go warp 4.\""))
4150         return
4151     if scanner.real > 10.0:
4152         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4153         return
4154     if scanner.real < 1.0:
4155         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4156         return
4157     oldfac = game.warpfac
4158     game.warpfac = scanner.real
4159     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4160         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4161                int(game.warpfac))
4162         return
4163     if game.warpfac < 8.00:
4164         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4165         return
4166     if game.warpfac == 10.0:
4167         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4168         return
4169     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4170     return
4171
4172 def atover(igrab):
4173     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4174     scanner.chew()
4175     # is captain on planet? 
4176     if game.landed:
4177         if damaged(DTRANSP):
4178             finish(FPNOVA)
4179             return
4180         prout(_("Scotty rushes to the transporter controls."))
4181         if game.shldup:
4182             prout(_("But with the shields up it's hopeless."))
4183             finish(FPNOVA)
4184         prouts(_("His desperate attempt to rescue you . . ."))
4185         if withprob(0.5):
4186             prout(_("fails."))
4187             finish(FPNOVA)
4188             return
4189         prout(_("SUCCEEDS!"))
4190         if game.imine:
4191             game.imine = False
4192             proutn(_("The crystals mined were "))
4193             if withprob(0.25):
4194                 prout(_("lost."))
4195             else:
4196                 prout(_("saved."))
4197                 game.icrystl = True
4198     if igrab:
4199         return
4200     # Check to see if captain in shuttle craft 
4201     if game.icraft:
4202         finish(FSTRACTOR)
4203     if game.alldone:
4204         return
4205     # Inform captain of attempt to reach safety 
4206     skip(1)
4207     while True:
4208         if game.justin:
4209             prouts(_("***RED ALERT!  RED ALERT!"))
4210             skip(1)
4211             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4212             prouts(_("   a supernova."))
4213             skip(2)
4214         proutn(_("***Emergency automatic override attempts to hurl ")+crmshp())
4215         prout(_("safely out of quadrant."))
4216         if not damaged(DRADIO):
4217             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4218         # Try to use warp engines 
4219         if damaged(DWARPEN):
4220             skip(1)
4221             prout(_("Warp engines damaged."))
4222             finish(FSNOVAED)
4223             return
4224         game.warpfac = randreal(6.0, 8.0)
4225         prout(_("Warp factor set to %d") % int(game.warpfac))
4226         power = 0.75*game.energy
4227         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4228         distreq = randreal(math.sqrt(2))
4229         if distreq < game.dist:
4230             dist = distreq
4231         course = course(bearing=randreal(12), distance=dist)    # How dumb!
4232         game.optime = course.time()
4233         game.justin = False
4234         game.inorbit = False
4235         warp(course, involuntary=True)
4236         if not game.justin:
4237             # This is bad news, we didn't leave quadrant. 
4238             if game.alldone:
4239                 return
4240             skip(1)
4241             prout(_("Insufficient energy to leave quadrant."))
4242             finish(FSNOVAED)
4243             return
4244         # Repeat if another snova
4245         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4246             break
4247     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0: 
4248         finish(FWON) # Snova killed remaining enemy. 
4249
4250 def timwrp():
4251     "Let's do the time warp again."
4252     prout(_("***TIME WARP ENTERED."))
4253     if game.state.snap and withprob(0.5):
4254         # Go back in time 
4255         prout(_("You are traveling backwards in time %d stardates.") %
4256               int(game.state.date-game.snapsht.date))
4257         game.state = game.snapsht
4258         game.state.snap = False
4259         if len(game.state.kcmdr):
4260             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4261             schedule(FBATTAK, expran(0.3*game.intime))
4262         schedule(FSNOVA, expran(0.5*game.intime))
4263         # next snapshot will be sooner 
4264         schedule(FSNAP, expran(0.25*game.state.remtime))
4265                                 
4266         if game.state.nscrem:
4267             schedule(FSCMOVE, 0.2777)       
4268         game.isatb = 0
4269         unschedule(FCDBAS)
4270         unschedule(FSCDBAS)
4271         game.battle.invalidate()
4272         # Make sure Galileo is consistant -- Snapshot may have been taken
4273         # when on planet, which would give us two Galileos! 
4274         gotit = False
4275         for l in range(game.inplan):
4276             if game.state.planets[l].known == "shuttle_down":
4277                 gotit = True
4278                 if game.iscraft == "onship" and game.ship==IHE:
4279                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4280                     game.iscraft = "offship"
4281         # Likewise, if in the original time the Galileo was abandoned, but
4282         # was on ship earlier, it would have vanished -- let's restore it.
4283         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4284             prout(_("Checkov-  \"Security reports the Galileo has reappeared in the dock!\""))
4285             game.iscraft = "onship"
4286         # There used to be code to do the actual reconstrction here,
4287         # but the starchart is now part of the snapshotted galaxy state.
4288         prout(_("Spock has reconstructed a correct star chart from memory"))
4289     else:
4290         # Go forward in time 
4291         game.optime = -0.5*game.intime*math.log(randreal())
4292         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4293         # cheat to make sure no tractor beams occur during time warp 
4294         postpone(FTBEAM, game.optime)
4295         game.damage[DRADIO] += game.optime
4296     newqad()
4297     events()    # Stas Sergeev added this -- do pending events 
4298
4299 def probe():
4300     "Launch deep-space probe." 
4301     # New code to launch a deep space probe 
4302     if game.nprobes == 0:
4303         scanner.chew()
4304         skip(1)
4305         if game.ship == IHE: 
4306             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4307         else:
4308             prout(_("Ye Faerie Queene has no deep space probes."))
4309         return
4310     if damaged(DDSP):
4311         scanner.chew()
4312         skip(1)
4313         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4314         return
4315     if is_scheduled(FDSPROB):
4316         scanner.chew()
4317         skip(1)
4318         if damaged(DRADIO) and game.condition != "docked":
4319             prout(_("Spock-  \"Records show the previous probe has not yet"))
4320             prout(_("   reached its destination.\""))
4321         else:
4322             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4323         return
4324     key = scanner.next()
4325     if key == "IHEOL":
4326         if game.nprobes == 1:
4327             prout(_("1 probe left."))
4328         else:
4329             prout(_("%d probes left") % game.nprobes)
4330         proutn(_("Are you sure you want to fire a probe? "))
4331         if ja() == False:
4332             return
4333     game.isarmed = False
4334     if key == "IHALPHA" and scanner.token == "armed":
4335         game.isarmed = True
4336         key = scanner.next()
4337     elif key == "IHEOL":
4338         proutn(_("Arm NOVAMAX warhead? "))
4339         game.isarmed = ja()
4340     elif key == "IHREAL":               # first element of course
4341         scanner.push(scanner.token)
4342     try:
4343         game.probe = getcourse(isprobe=True)
4344     except TrekError:
4345         return
4346     game.nprobes -= 1
4347     schedule(FDSPROB, 0.01) # Time to move one sector
4348     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4349     game.ididit = True
4350     return
4351
4352 def mayday():
4353     "Yell for help from nearest starbase."
4354     # There's more than one way to move in this game! 
4355     scanner.chew()
4356     # Test for conditions which prevent calling for help 
4357     if game.condition == "docked":
4358         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4359         return
4360     if damaged(DRADIO):
4361         prout(_("Subspace radio damaged."))
4362         return
4363     if not game.state.baseq:
4364         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4365         return
4366     if game.landed:
4367         prout(_("You must be aboard the %s.") % crmshp())
4368         return
4369     # OK -- call for help from nearest starbase 
4370     game.nhelp += 1
4371     if game.base.i!=0:
4372         # There's one in this quadrant 
4373         ddist = (game.base - game.sector).distance()
4374     else:
4375         ddist = FOREVER
4376         for ibq in game.state.baseq:
4377             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4378             if xdist < ddist:
4379                 ddist = xdist
4380         # Since starbase not in quadrant, set up new quadrant 
4381         game.quadrant = ibq
4382         newqad()
4383     # dematerialize starship 
4384     game.quad[game.sector.i][game.sector.j]=IHDOT
4385     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4386            % (game.quadrant, crmshp()))
4387     game.sector.invalidate()
4388     for m in range(1, 5+1):
4389         w = game.base.scatter() 
4390         if w.valid_sector() and game.quad[w.i][w.j]==IHDOT:
4391             # found one -- finish up 
4392             game.sector = w
4393             break
4394     if not game.sector.is_valid():
4395         prout(_("You have been lost in space..."))
4396         finish(FMATERIALIZE)
4397         return
4398     # Give starbase three chances to rematerialize starship 
4399     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4400     for m in range(1, 3+1):
4401         if m == 1: proutn(_("1st"))
4402         elif m == 2: proutn(_("2nd"))
4403         elif m == 3: proutn(_("3rd"))
4404         proutn(_(" attempt to re-materialize ") + crmshp())
4405         game.quad[ix][iy]=(IHMATER0,IHMATER1,IHMATER2)[m-1]
4406         #textcolor("red")
4407         warble()
4408         if randreal() > probf:
4409             break
4410         prout(_("fails."))
4411         curses.delay_output(500)
4412         #textcolor(None)
4413     if m > 3:
4414         game.quad[ix][iy]=IHQUEST
4415         game.alive = False
4416         drawmaps(1)
4417         setwnd(message_window)
4418         finish(FMATERIALIZE)
4419         return
4420     game.quad[ix][iy]=game.ship
4421     #textcolor("green")
4422     prout(_("succeeds."))
4423     #textcolor(None)
4424     dock(False)
4425     skip(1)
4426     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4427
4428 def abandon():
4429     "Abandon ship."
4430     scanner.chew()
4431     if game.condition=="docked":
4432         if game.ship!=IHE:
4433             prout(_("You cannot abandon Ye Faerie Queene."))
4434             return
4435     else:
4436         # Must take shuttle craft to exit 
4437         if game.damage[DSHUTTL]==-1:
4438             prout(_("Ye Faerie Queene has no shuttle craft."))
4439             return
4440         if game.damage[DSHUTTL]<0:
4441             prout(_("Shuttle craft now serving Big Macs."))
4442             return
4443         if game.damage[DSHUTTL]>0:
4444             prout(_("Shuttle craft damaged."))
4445             return
4446         if game.landed:
4447             prout(_("You must be aboard the ship."))
4448             return
4449         if game.iscraft != "onship":
4450             prout(_("Shuttle craft not currently available."))
4451             return
4452         # Emit abandon ship messages 
4453         skip(1)
4454         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4455         skip(1)
4456         prouts(_("***ALL HANDS ABANDON SHIP!"))
4457         skip(2)
4458         prout(_("Captain and crew escape in shuttle craft."))
4459         if not game.state.baseq:
4460             # Oops! no place to go... 
4461             finish(FABANDN)
4462             return
4463         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4464         # Dispose of crew 
4465         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4466             prout(_("Remainder of ship's complement beam down"))
4467             prout(_("to nearest habitable planet."))
4468         elif q.planet != None and not damaged(DTRANSP):
4469             prout(_("Remainder of ship's complement beam down to %s.") %
4470                     q.planet)
4471         else:
4472             prout(_("Entire crew of %d left to die in outer space.") %
4473                     game.state.crew)
4474             game.casual += game.state.crew
4475             game.abandoned += game.state.crew
4476         # If at least one base left, give 'em the Faerie Queene 
4477         skip(1)
4478         game.icrystl = False # crystals are lost 
4479         game.nprobes = 0 # No probes 
4480         prout(_("You are captured by Klingons and released to"))
4481         prout(_("the Federation in a prisoner-of-war exchange."))
4482         nb = randrange(len(game.state.baseq))
4483         # Set up quadrant and position FQ adjacient to base 
4484         if not game.quadrant == game.state.baseq[nb]:
4485             game.quadrant = game.state.baseq[nb]
4486             game.sector.i = game.sector.j = 5
4487             newqad()
4488         while True:
4489             # position next to base by trial and error 
4490             game.quad[game.sector.i][game.sector.j] = IHDOT
4491             for l in range(QUADSIZE):
4492                 game.sector = game.base.scatter()
4493                 if game.sector.valid_sector() and \
4494                        game.quad[game.sector.i][game.sector.j] == IHDOT:
4495                     break
4496             if l < QUADSIZE+1:
4497                 break # found a spot 
4498             game.sector.i=QUADSIZE/2
4499             game.sector.j=QUADSIZE/2
4500             newqad()
4501     # Get new commission 
4502     game.quad[game.sector.i][game.sector.j] = game.ship = IHF
4503     game.state.crew = FULLCREW
4504     prout(_("Starfleet puts you in command of another ship,"))
4505     prout(_("the Faerie Queene, which is antiquated but,"))
4506     prout(_("still useable."))
4507     if game.icrystl:
4508         prout(_("The dilithium crystals have been moved."))
4509     game.imine = False
4510     game.iscraft = "offship" # Galileo disappears 
4511     # Resupply ship 
4512     game.condition="docked"
4513     for l in range(NDEVICES): 
4514         game.damage[l] = 0.0
4515     game.damage[DSHUTTL] = -1
4516     game.energy = game.inenrg = 3000.0
4517     game.shield = game.inshld = 1250.0
4518     game.torps = game.intorps = 6
4519     game.lsupres=game.inlsr=3.0
4520     game.shldup=False
4521     game.warpfac=5.0
4522     return
4523
4524 # Code from planets.c begins here.
4525
4526 def consumeTime():
4527     "Abort a lengthy operation if an event interrupts it." 
4528     game.ididit = True
4529     events()
4530     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin: 
4531         return True
4532     return False
4533
4534 def survey():
4535     "Report on (uninhabited) planets in the galaxy."
4536     iknow = False
4537     skip(1)
4538     scanner.chew()
4539     prout(_("Spock-  \"Planet report follows, Captain.\""))
4540     skip(1)
4541     for i in range(game.inplan):
4542         if game.state.planets[i].pclass == "destroyed":
4543             continue
4544         if (game.state.planets[i].known != "unknown" \
4545             and not game.state.planets[i].inhabited) \
4546             or idebug:
4547             iknow = True
4548             if idebug and game.state.planets[i].known=="unknown":
4549                 proutn("(Unknown) ")
4550             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4551             proutn(_("   class "))
4552             proutn(game.state.planets[i].pclass)
4553             proutn("   ")
4554             if game.state.planets[i].crystals != present:
4555                 proutn(_("no "))
4556             prout(_("dilithium crystals present."))
4557             if game.state.planets[i].known=="shuttle_down": 
4558                 prout(_("    Shuttle Craft Galileo on surface."))
4559     if not iknow:
4560         prout(_("No information available."))
4561
4562 def orbit():
4563     "Enter standard orbit." 
4564     skip(1)
4565     scanner.chew()
4566     if game.inorbit:
4567         prout(_("Already in standard orbit."))
4568         return
4569     if damaged(DWARPEN) and damaged(DIMPULS):
4570         prout(_("Both warp and impulse engines damaged."))
4571         return
4572     if not game.plnet.is_valid():
4573         prout("There is no planet in this sector.")
4574         return
4575     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4576         prout(crmshp() + _(" not adjacent to planet."))
4577         skip(1)
4578         return
4579     game.optime = randreal(0.02, 0.05)
4580     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4581     newcnd()
4582     if consumeTime():
4583         return
4584     game.height = randreal(1400, 8600)
4585     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4586     game.inorbit = True
4587     game.ididit = True
4588
4589 def sensor():
4590     "Examine planets in this quadrant."
4591     if damaged(DSRSENS):
4592         if game.options & OPTION_TTY:
4593             prout(_("Short range sensors damaged."))
4594         return
4595     if game.iplnet == None:
4596         if game.options & OPTION_TTY:
4597             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4598         return
4599     if game.iplnet.known == "unknown":
4600         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4601         skip(1)
4602         prout(_("         Planet at Sector %s is of class %s.") %
4603               (game.plnet, game.iplnet.pclass))
4604         if game.iplnet.known=="shuttle_down": 
4605             prout(_("         Sensors show Galileo still on surface."))
4606         proutn(_("         Readings indicate"))
4607         if game.iplnet.crystals != "present":
4608             proutn(_(" no"))
4609         prout(_(" dilithium crystals present.\""))
4610         if game.iplnet.known == "unknown":
4611             game.iplnet.known = "known"
4612     elif game.iplnet.inhabited:
4613         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4614         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4615
4616 def beam():
4617     "Use the transporter."
4618     nrgneed = 0
4619     scanner.chew()
4620     skip(1)
4621     if damaged(DTRANSP):
4622         prout(_("Transporter damaged."))
4623         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4624             skip(1)
4625             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4626             if ja() == True:
4627                 shuttle()
4628         return
4629     if not game.inorbit:
4630         prout(crmshp() + _(" not in standard orbit."))
4631         return
4632     if game.shldup:
4633         prout(_("Impossible to transport through shields."))
4634         return
4635     if game.iplnet.known=="unknown":
4636         prout(_("Spock-  \"Captain, we have no information on this planet"))
4637         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4638         prout(_("  you may not go down.\""))
4639         return
4640     if not game.landed and game.iplnet.crystals=="absent":
4641         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4642         prout(_("  exploring a planet with no dilithium crystals."))
4643         proutn(_("  Are you sure this is wise?\" "))
4644         if ja() == False:
4645             scanner.chew()
4646             return
4647     if not (game.options & OPTION_PLAIN):
4648         nrgneed = 50 * game.skill + game.height / 100.0
4649         if nrgneed > game.energy:
4650             prout(_("Engineering to bridge--"))
4651             prout(_("  Captain, we don't have enough energy for transportation."))
4652             return
4653         if not game.landed and nrgneed * 2 > game.energy:
4654             prout(_("Engineering to bridge--"))
4655             prout(_("  Captain, we have enough energy only to transport you down to"))
4656             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4657             if game.iplnet.known == "shuttle_down":
4658                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4659             proutn(_("  Are you sure this is wise?\" "))
4660             if ja() == False:
4661                 scanner.chew()
4662                 return
4663     if game.landed:
4664         # Coming from planet 
4665         if game.iplnet.known=="shuttle_down":
4666             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4667             if ja() == True:
4668                 scanner.chew()
4669                 return
4670             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4671         prout(_("Landing party assembled, ready to beam up."))
4672         skip(1)
4673         prout(_("Kirk whips out communicator..."))
4674         prouts(_("BEEP  BEEP  BEEP"))
4675         skip(2)
4676         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4677     else:
4678         # Going to planet 
4679         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4680         skip(1)
4681         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4682         skip(1)
4683         prout(_("Kirk-  \"Energize.\""))
4684     game.ididit = True
4685     skip(1)
4686     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4687     skip(2)
4688     if withprob(0.98):
4689         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4690         skip(2)
4691         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4692         finish(FLOST)
4693         return
4694     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4695     game.landed = not game.landed
4696     game.energy -= nrgneed
4697     skip(2)
4698     prout(_("Transport complete."))
4699     if game.landed and game.iplnet.known=="shuttle_down":
4700         prout(_("The shuttle craft Galileo is here!"))
4701     if not game.landed and game.imine:
4702         game.icrystl = True
4703         game.cryprob = 0.05
4704     game.imine = False
4705     return
4706
4707 def mine():
4708     "Strip-mine a world for dilithium."
4709     skip(1)
4710     scanner.chew()
4711     if not game.landed:
4712         prout(_("Mining party not on planet."))
4713         return
4714     if game.iplnet.crystals == "mined":
4715         prout(_("This planet has already been strip-mined for dilithium."))
4716         return
4717     elif game.iplnet.crystals == "absent":
4718         prout(_("No dilithium crystals on this planet."))
4719         return
4720     if game.imine:
4721         prout(_("You've already mined enough crystals for this trip."))
4722         return
4723     if game.icrystl and game.cryprob == 0.05:
4724         prout(_("With all those fresh crystals aboard the ") + crmshp())
4725         prout(_("there's no reason to mine more at this time."))
4726         return
4727     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4728     if consumeTime():
4729         return
4730     prout(_("Mining operation complete."))
4731     game.iplnet.crystals = "mined"
4732     game.imine = game.ididit = True
4733
4734 def usecrystals():
4735     "Use dilithium crystals."
4736     game.ididit = False
4737     skip(1)
4738     scanner.chew()
4739     if not game.icrystl:
4740         prout(_("No dilithium crystals available."))
4741         return
4742     if game.energy >= 1000:
4743         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4744         prout(_("  except when Condition Yellow exists."))
4745         return
4746     prout(_("Spock- \"Captain, I must warn you that loading"))
4747     prout(_("  raw dilithium crystals into the ship's power"))
4748     prout(_("  system may risk a severe explosion."))
4749     proutn(_("  Are you sure this is wise?\" "))
4750     if ja() == False:
4751         scanner.chew()
4752         return
4753     skip(1)
4754     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4755     prout(_("  Mr. Spock and I will try it.\""))
4756     skip(1)
4757     prout(_("Spock-  \"Crystals in place, Sir."))
4758     prout(_("  Ready to activate circuit.\""))
4759     skip(1)
4760     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4761     skip(1)
4762     if with(game.cryprob):
4763         prouts(_("  \"Activating now! - - No good!  It's***"))
4764         skip(2)
4765         prouts(_("***RED ALERT!  RED A*L********************************"))
4766         skip(1)
4767         stars()
4768         prouts(_("******************   KA-BOOM!!!!   *******************"))
4769         skip(1)
4770         kaboom()
4771         return
4772     game.energy += randreal(5000.0, 5500.0)
4773     prouts(_("  \"Activating now! - - "))
4774     prout(_("The instruments"))
4775     prout(_("   are going crazy, but I think it's"))
4776     prout(_("   going to work!!  Congratulations, Sir!\""))
4777     game.cryprob *= 2.0
4778     game.ididit = True
4779
4780 def shuttle():
4781     "Use shuttlecraft for planetary jaunt."
4782     scanner.chew()
4783     skip(1)
4784     if damaged(DSHUTTL):
4785         if game.damage[DSHUTTL] == -1.0:
4786             if game.inorbit and game.iplnet.known == "shuttle_down":
4787                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4788             else:
4789                 prout(_("Ye Faerie Queene had no shuttle craft."))
4790         elif game.damage[DSHUTTL] > 0:
4791             prout(_("The Galileo is damaged."))
4792         else: # game.damage[DSHUTTL] < 0  
4793             prout(_("Shuttle craft is now serving Big Macs."))
4794         return
4795     if not game.inorbit:
4796         prout(crmshp() + _(" not in standard orbit."))
4797         return
4798     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4799         prout(_("Shuttle craft not currently available."))
4800         return
4801     if not game.landed and game.iplnet.known=="shuttle_down":
4802         prout(_("You will have to beam down to retrieve the shuttle craft."))
4803         return
4804     if game.shldup or game.condition == "docked":
4805         prout(_("Shuttle craft cannot pass through shields."))
4806         return
4807     if game.iplnet.known=="unknown":
4808         prout(_("Spock-  \"Captain, we have no information on this planet"))
4809         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4810         prout(_("  you may not fly down.\""))
4811         return
4812     game.optime = 3.0e-5*game.height
4813     if game.optime >= 0.8*game.state.remtime:
4814         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4815         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4816                int(100*game.optime/game.state.remtime))
4817         prout(_("remaining time."))
4818         proutn(_("Are you sure this is wise?\" "))
4819         if ja() == False:
4820             game.optime = 0.0
4821             return
4822     if game.landed:
4823         # Kirk on planet 
4824         if game.iscraft == "onship":
4825             # Galileo on ship! 
4826             if not damaged(DTRANSP):
4827                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4828                 if ja() == True:
4829                     beam()
4830                     return
4831                 proutn(_("Shuttle crew"))
4832             else:
4833                 proutn(_("Rescue party"))
4834             prout(_(" boards Galileo and swoops toward planet surface."))
4835             game.iscraft = "offship"
4836             skip(1)
4837             if consumeTime():
4838                 return
4839             game.iplnet.known="shuttle_down"
4840             prout(_("Trip complete."))
4841             return
4842         else:
4843             # Ready to go back to ship 
4844             prout(_("You and your mining party board the"))
4845             prout(_("shuttle craft for the trip back to the Enterprise."))
4846             skip(1)
4847             prouts(_("The short hop begins . . ."))
4848             skip(1)
4849             game.iplnet.known="known"
4850             game.icraft = True
4851             skip(1)
4852             game.landed = False
4853             if consumeTime():
4854                 return
4855             game.iscraft = "onship"
4856             game.icraft = False
4857             if game.imine:
4858                 game.icrystl = True
4859                 game.cryprob = 0.05
4860             game.imine = False
4861             prout(_("Trip complete."))
4862             return
4863     else:
4864         # Kirk on ship and so is Galileo 
4865         prout(_("Mining party assembles in the hangar deck,"))
4866         prout(_("ready to board the shuttle craft \"Galileo\"."))
4867         skip(1)
4868         prouts(_("The hangar doors open; the trip begins."))
4869         skip(1)
4870         game.icraft = True
4871         game.iscraft = "offship"
4872         if consumeTime():
4873             return
4874         game.iplnet.known = "shuttle_down"
4875         game.landed = True
4876         game.icraft = False
4877         prout(_("Trip complete."))
4878         return
4879
4880 def deathray():
4881     "Use the big zapper."
4882     game.ididit = False
4883     skip(1)
4884     scanner.chew()
4885     if game.ship != IHE:
4886         prout(_("Ye Faerie Queene has no death ray."))
4887         return
4888     if len(game.enemies)==0:
4889         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4890         return
4891     if damaged(DDRAY):
4892         prout(_("Death Ray is damaged."))
4893         return
4894     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4895     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4896     proutn(_("  are you sure this is wise?\" "))
4897     if ja() == False:
4898         return
4899     prout(_("Spock-  \"Acknowledged.\""))
4900     skip(1)
4901     game.ididit = True
4902     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4903     skip(1)
4904     prout(_("Crew scrambles in emergency preparation."))
4905     prout(_("Spock and Scotty ready the death ray and"))
4906     prout(_("prepare to channel all ship's power to the device."))
4907     skip(1)
4908     prout(_("Spock-  \"Preparations complete, sir.\""))
4909     prout(_("Kirk-  \"Engage!\""))
4910     skip(1)
4911     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4912     skip(1)
4913     dprob = 0.30
4914     if game.options & OPTION_PLAIN:
4915         dprob = 0.5
4916     r = randreal()
4917     if r > dprob:
4918         prouts(_("Sulu- \"Captain!  It's working!\""))
4919         skip(2)
4920         while len(game.enemies) > 0:
4921             deadkl(game.enemies[1].kloc, game.quad[game.enemies[1].kloc.i][game.enemies[1].kloc.j],game.enemies[1].kloc)
4922         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4923         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4924             finish(FWON)    
4925         if (game.options & OPTION_PLAIN) == 0:
4926             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4927             if withprob(0.05):
4928                 prout(_("   is still operational.\""))
4929             else:
4930                 prout(_("   has been rendered nonfunctional.\""))
4931                 game.damage[DDRAY] = 39.95
4932         return
4933     r = randreal()      # Pick failure method 
4934     if r <= 0.30:
4935         prouts(_("Sulu- \"Captain!  It's working!\""))
4936         skip(1)
4937         prouts(_("***RED ALERT!  RED ALERT!"))
4938         skip(1)
4939         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4940         skip(1)
4941         prouts(_("***RED ALERT!  RED A*L********************************"))
4942         skip(1)
4943         stars()
4944         prouts(_("******************   KA-BOOM!!!!   *******************"))
4945         skip(1)
4946         kaboom()
4947         return
4948     if r <= 0.55:
4949         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4950         skip(1)
4951         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4952         skip(1)
4953         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4954         prout(_("  have apparently been transformed into strange mutations."))
4955         prout(_("  Vulcans do not seem to be affected."))
4956         skip(1)
4957         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4958         finish(FDRAY)
4959         return
4960     if r <= 0.75:
4961         intj
4962         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4963         skip(2)
4964         proutn(_("Spock-  \"I believe the word is"))
4965         prouts(_(" *ASTONISHING*"))
4966         prout(_(" Mr. Sulu."))
4967         for i in range(QUADSIZE):
4968             for j in range(QUADSIZE):
4969                 if game.quad[i][j] == IHDOT:
4970                     game.quad[i][j] = IHQUEST
4971         prout(_("  Captain, our quadrant is now infested with"))
4972         prouts(_(" - - - - - -  *THINGS*."))
4973         skip(1)
4974         prout(_("  I have no logical explanation.\""))
4975         return
4976     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4977     skip(1)
4978     prout(_("Scotty-  \"There are so many tribbles down here"))
4979     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4980     finish(FTRIBBLE)
4981     return
4982
4983 # Code from reports.c begins here
4984
4985 def attackreport(curt):
4986     "eport status of bases under attack."
4987     if not curt:
4988         if is_scheduled(FCDBAS):
4989             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4990             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4991         elif game.isatb == 1:
4992             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4993             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4994         else:
4995             prout(_("No Starbase is currently under attack."))
4996     else:
4997         if is_scheduled(FCDBAS):
4998             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4999         if game.isatb:
5000             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5001         clreol()
5002
5003 def report():
5004     # report on general game status 
5005     scanner.chew()
5006     s1 = "" and game.thawed and _("thawed ")
5007     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5008     s3 = (None, _("novice"). _("fair"),
5009           _("good"), _("expert"), _("emeritus"))[game.skill]
5010     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5011     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5012         prout(_("No plaque is allowed."))
5013     if game.tourn:
5014         prout(_("This is tournament game %d.") % game.tourn)
5015     prout(_("Your secret password is \"%s\"") % game.passwd)
5016     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)), 
5017            (game.inkling + game.incom + game.inscom)))
5018     if game.incom - len(game.state.kcmdr):
5019         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5020     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
5021         prout(_(", but no Commanders."))
5022     else:
5023         prout(".")
5024     if game.skill > SKILL_FAIR:
5025         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5026     if len(game.state.baseq) != game.inbase:
5027         proutn(_("There "))
5028         if game.inbase-len(game.state.baseq)==1:
5029             proutn(_("has been 1 base"))
5030         else:
5031             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5032         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5033     else:
5034         prout(_("There are %d bases.") % game.inbase)
5035     if communicating() or game.iseenit:
5036         # Don't report this if not seen and
5037         # either the radio is dead or not at base!
5038         attackreport(False)
5039         game.iseenit = True
5040     if game.casual: 
5041         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5042     if game.nhelp:
5043         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5044     if game.ship == IHE:
5045         proutn(_("You have "))
5046         if game.nprobes:
5047             proutn("%d" % (game.nprobes))
5048         else:
5049             proutn(_("no"))
5050         proutn(_(" deep space probe"))
5051         if game.nprobes!=1:
5052             proutn(_("s"))
5053         prout(".")
5054     if communicating() and is_scheduled(FDSPROB):
5055         if game.isarmed: 
5056             proutn(_("An armed deep space probe is in "))
5057         else:
5058             proutn(_("A deep space probe is in "))
5059         prout("Quadrant %s." % game.probec)
5060     if game.icrystl:
5061         if game.cryprob <= .05:
5062             prout(_("Dilithium crystals aboard ship... not yet used."))
5063         else:
5064             i=0
5065             ai = 0.05
5066             while game.cryprob > ai:
5067                 ai *= 2.0
5068                 i += 1
5069             prout(_("Dilithium crystals have been used %d time%s.") % \
5070                   (i, (_("s"), "")[i==1]))
5071     skip(1)
5072         
5073 def lrscan(silent):
5074     "Long-range sensor scan."
5075     if damaged(DLRSENS):
5076         # Now allow base's sensors if docked 
5077         if game.condition != "docked":
5078             if not silent:
5079                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5080             return
5081         if not silent:
5082             prout(_("Starbase's long-range scan"))
5083     elif not silent:
5084         prout(_("Long-range scan"))
5085     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5086         if not silent:
5087             proutn(" ")
5088         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5089             if not coord(x, y).valid_quadrant():
5090                 if not silent:
5091                     proutn("  -1")
5092             else:
5093                 if not damaged(DRADIO):
5094                     game.state.galaxy[x][y].charted = True
5095                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5096                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5097                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5098                 if not silent and game.state.galaxy[x][y].supernova: 
5099                     proutn(" ***")
5100                 elif not silent:
5101                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
5102         prout(" ")
5103
5104 def damagereport():
5105     "Damage report."
5106     jdam = False
5107     scanner.chew()
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 w1.valid_quadrant() or not w2.valid_sector():
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 ""