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