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