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