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