Add BSD Terek CAPTURE from Tom Almy's 2013 changes.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K 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.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 import os, sys, math, curses, time, pickle, random, copy, gettext, getpass
15 import getopt, socket, locale
16
17 # This import only works on Unixes.  The intention is to enable
18 # Ctrl-P, Ctrl-N, and friends in Cmd.
19 try:
20     import readline
21 except ImportError:
22     pass
23
24 version = "2.1"
25
26 docpath         = (".", "doc/", "/usr/share/doc/sst/")
27
28 def _(st):
29     return gettext.gettext(st)
30
31 GALSIZE         = 8             # Galaxy size in quadrants
32 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
33 MAXUNINHAB      = 10            # Maximum uninhabited worlds
34 QUADSIZE        = 10            # Quadrant size in sectors
35 BASEMIN         = 2                             # Minimum starbases
36 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
37 MAXKLGAME       = 127           # Maximum Klingons per game
38 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
39 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
40 FOREVER         = 1e30          # Time for the indefinite future
41 MAXBURST        = 3             # Max # of torps you can launch in one turn
42 MINCMDR         = 10            # Minimum number of Klingon commanders
43 DOCKFAC         = 0.25          # Repair faster when docked
44 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
45
46 DEFAULT      = -1
47 BLACK        = 0
48 BLUE         = 1
49 GREEN        = 2
50 CYAN         = 3
51 RED          = 4
52 MAGENTA      = 5
53 BROWN        = 6
54 LIGHTGRAY    = 7
55 DARKGRAY     = 8
56 LIGHTBLUE    = 9
57 LIGHTGREEN   = 10
58 LIGHTCYAN    = 11
59 LIGHTRED     = 12
60 LIGHTMAGENTA = 13
61 YELLOW       = 14
62 WHITE        = 15
63
64 class TrekError(Exception):
65     pass
66
67 class JumpOut(Exception):
68     pass
69
70 class Coord:
71     def __init__(self, x=None, y=None):
72         self.i = x
73         self.j = y
74     def valid_quadrant(self):
75         return self.i >= 0 and self.i < GALSIZE and self.j >= 0 and self.j < GALSIZE
76     def valid_sector(self):
77         return self.i >= 0 and self.i < QUADSIZE and self.j >= 0 and self.j < QUADSIZE
78     def invalidate(self):
79         self.i = self.j = None
80     def is_valid(self):
81         return self.i != None and self.j != None
82     def __eq__(self, other):
83         return other != None and self.i == other.i and self.j == other.j
84     def __ne__(self, other):
85         return other is None or self.i != other.i or self.j != other.j
86     def __add__(self, other):
87         return Coord(self.i+other.i, self.j+other.j)
88     def __sub__(self, other):
89         return Coord(self.i-other.i, self.j-other.j)
90     def __mul__(self, other):
91         return Coord(self.i*other, self.j*other)
92     def __rmul__(self, other):
93         return Coord(self.i*other, self.j*other)
94     def __div__(self, other):
95         return Coord(self.i/other, self.j/other)
96     def __mod__(self, other):
97         return Coord(self.i % other, self.j % other)
98     def __rdiv__(self, other):
99         return Coord(self.i/other, self.j/other)
100     def roundtogrid(self):
101         return Coord(int(round(self.i)), int(round(self.j)))
102     def distance(self, other=None):
103         if not other:
104             other = Coord(0, 0)
105         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
106     def bearing(self):
107         return 1.90985*math.atan2(self.j, self.i)
108     def sgn(self):
109         s = Coord()
110         if self.i == 0:
111             s.i = 0
112         else:
113             s.i = self.i / abs(self.i)
114         if self.j == 0:
115             s.j = 0
116         else:
117             s.j = self.j / abs(self.j)
118         return s
119     def quadrant(self):
120         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
121         return self.roundtogrid() / QUADSIZE
122     def sector(self):
123         return self.roundtogrid() % QUADSIZE
124     def scatter(self):
125         s = Coord()
126         s.i = self.i + randrange(-1, 2)
127         s.j = self.j + randrange(-1, 2)
128         return s
129     def __str__(self):
130         if self.i is None or self.j is None:
131             return "Nowhere"
132         return "%s - %s" % (self.i+1, self.j+1)
133     __repr__ = __str__
134
135 class Thingy(Coord):
136     "Do not anger the Space Thingy!"
137     def __init__(self):
138         Coord.__init__(self)
139         self.angered = False
140     def angry(self):
141         self.angered = True
142     def at(self, q):
143         return (q.i, q.j) == (self.i, self.j)
144
145 class Planet:
146     def __init__(self):
147         self.name = None        # string-valued if inhabited
148         self.quadrant = Coord()        # quadrant located
149         self.pclass = None        # could be ""M", "N", "O", or "destroyed"
150         self.crystals = "absent"# could be "mined", "present", "absent"
151         self.known = "unknown"        # could be "unknown", "known", "shuttle_down"
152         self.inhabited = False        # is it inhabited?
153     def __str__(self):
154         return self.name
155
156 class Quadrant:
157     def __init__(self):
158         self.stars = 0
159         self.planet = None
160         self.starbase = False
161         self.klingons = 0
162         self.romulans = 0
163         self.supernova = False
164         self.charted = False
165         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
166
167 class Page:
168     def __init__(self):
169         self.stars = None
170         self.starbase = False
171         self.klingons = None
172     def __repr__(self):
173         return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)
174
175 def fill2d(size, fillfun):
176     "Fill an empty list in 2D."
177     lst = []
178     for i in range(size):
179         lst.append([])
180         for j in range(size):
181             lst[i].append(fillfun(i, j))
182     return lst
183
184 class Snapshot:
185     def __init__(self):
186         self.snap = False        # snapshot taken
187         self.crew = 0           # crew complement
188         self.remkl = 0          # remaining klingons
189         self.nscrem = 0                # remaining super commanders
190         self.starkl = 0         # destroyed stars
191         self.basekl = 0         # destroyed bases
192         self.nromrem = 0        # Romulans remaining
193         self.nplankl = 0        # destroyed uninhabited planets
194         self.nworldkl = 0        # destroyed inhabited planets
195         self.planets = []        # Planet information
196         self.date = 0.0           # stardate
197         self.remres = 0         # remaining resources
198         self.remtime = 0        # remaining time
199         self.baseq = []         # Base quadrant coordinates
200         self.kcmdr = []         # Commander quadrant coordinates
201         self.kscmdr = Coord()        # Supercommander quadrant coordinates
202         # the galaxy
203         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
204         # the starchart
205         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
206
207 class Event:
208     def __init__(self):
209         self.date = None        # A real number
210         self.quadrant = None        # A coord structure
211
212 # game options
213 OPTION_ALL        = 0xffffffff
214 OPTION_TTY        = 0x00000001        # old interface
215 OPTION_CURSES     = 0x00000002        # new interface
216 OPTION_IOMODES    = 0x00000003        # cover both interfaces
217 OPTION_PLANETS    = 0x00000004        # planets and mining
218 OPTION_THOLIAN    = 0x00000008        # Tholians and their webs (UT 1979 version)
219 OPTION_THINGY     = 0x00000010        # Space Thingy can shoot back (Stas, 2005)
220 OPTION_PROBE      = 0x00000020        # deep-space probes (DECUS version, 1980)
221 OPTION_SHOWME     = 0x00000040        # bracket Enterprise in chart
222 OPTION_RAMMING    = 0x00000080        # enemies may ram Enterprise (Almy)
223 OPTION_MVBADDY    = 0x00000100        # more enemies can move (Almy)
224 OPTION_BLKHOLE    = 0x00000200        # black hole may timewarp you (Stas, 2005)
225 OPTION_BASE       = 0x00000400        # bases have good shields (Stas, 2005)
226 OPTION_WORLDS     = 0x00000800        # logic for inhabited worlds (ESR, 2006)
227 OPTION_AUTOSCAN   = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
228 OPTION_PLAIN      = 0x01000000        # user chose plain game
229 OPTION_ALMY       = 0x02000000        # user chose Almy variant
230 OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
231 OPTION_CAPTURE    = 0x80000000        # Enable BSD-Trek capture (Almy, 2013).
232
233 # Define devices
234 DSRSENS         = 0
235 DLRSENS         = 1
236 DPHASER         = 2
237 DPHOTON         = 3
238 DLIFSUP         = 4
239 DWARPEN         = 5
240 DIMPULS         = 6
241 DSHIELD         = 7
242 DRADIO         = 0
243 DSHUTTL  = 9
244 DCOMPTR  = 10
245 DNAVSYS         = 11
246 DTRANSP  = 12
247 DSHCTRL         = 13
248 DDRAY         = 14
249 DDSP         = 15
250 NDEVICES = 16        # Number of devices
251
252 SKILL_NONE        = 0
253 SKILL_NOVICE        = 1
254 SKILL_FAIR        = 2
255 SKILL_GOOD        = 3
256 SKILL_EXPERT        = 4
257 SKILL_EMERITUS        = 5
258
259 def damaged(dev):
260     return (game.damage[dev] != 0.0)
261 def communicating():
262     return not damaged(DRADIO) or game.condition=="docked"
263
264 # Define future events
265 FSPY        = 0        # Spy event happens always (no future[] entry)
266                 # can cause SC to tractor beam Enterprise
267 FSNOVA  = 1        # Supernova
268 FTBEAM  = 2        # Commander tractor beams Enterprise
269 FSNAP   = 3        # Snapshot for time warp
270 FBATTAK = 4        # Commander attacks base
271 FCDBAS  = 5        # Commander destroys base
272 FSCMOVE = 6        # Supercommander moves (might attack base)
273 FSCDBAS = 7        # Supercommander destroys base
274 FDSPROB = 8        # Move deep space probe
275 FDISTR        = 9        # Emit distress call from an inhabited world
276 FENSLV        = 10        # Inhabited word is enslaved */
277 FREPRO        = 11        # Klingons build a ship in an enslaved system
278 NEVENTS        = 12
279
280 # Abstract out the event handling -- underlying data structures will change
281 # when we implement stateful events
282 def findevent(evtype):
283     return game.future[evtype]
284
285 class Enemy:
286     def __init__(self, etype=None, loc=None, power=None):
287         self.type = etype
288         self.location = Coord()
289         self.kdist = None
290         self.kavgd = None
291         if loc:
292             self.move(loc)
293         self.power = power        # enemy energy level
294         game.enemies.append(self)
295     def move(self, loc):
296         motion = (loc != self.location)
297         if self.location.i is not None and self.location.j is not None:
298             if motion:
299                 if self.type == 'T':
300                     game.quad[self.location.i][self.location.j] = '#'
301                 else:
302                     game.quad[self.location.i][self.location.j] = '.'
303         if loc:
304             self.location = copy.copy(loc)
305             game.quad[self.location.i][self.location.j] = self.type
306             self.kdist = self.kavgd = (game.sector - loc).distance()
307         else:
308             self.location = Coord()
309             self.kdist = self.kavgd = None
310             game.enemies.remove(self)
311         return motion
312     def __repr__(self):
313         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
314
315 class Gamestate:
316     def __init__(self):
317         self.options = None        # Game options
318         self.state = Snapshot()        # A snapshot structure
319         self.snapsht = Snapshot()        # Last snapshot taken for time-travel purposes
320         self.quad = None        # contents of our quadrant
321         self.damage = [0.0] * NDEVICES        # damage encountered
322         self.future = []        # future events
323         i = NEVENTS
324         while i > 0:
325             i -= 1
326             self.future.append(Event())
327         self.passwd  = None        # Self Destruct password
328         self.enemies = []
329         self.quadrant = None        # where we are in the large
330         self.sector = None        # where we are in the small
331         self.tholian = None        # Tholian enemy object
332         self.base = None        # position of base in current quadrant
333         self.battle = None        # base coordinates being attacked
334         self.plnet = None        # location of planet in quadrant
335         self.gamewon = False        # Finished!
336         self.ididit = False        # action taken -- allows enemy to attack
337         self.alive = False        # we are alive (not killed)
338         self.justin = False        # just entered quadrant
339         self.shldup = False        # shields are up
340         self.shldchg = False        # shield is changing (affects efficiency)
341         self.iscate = False        # super commander is here
342         self.ientesc = False        # attempted escape from supercommander
343         self.resting = False        # rest time
344         self.icraft = False        # Kirk in Galileo
345         self.landed = False        # party on planet (true), on ship (false)
346         self.alldone = False        # game is now finished
347         self.neutz = False        # Romulan Neutral Zone
348         self.isarmed = False        # probe is armed
349         self.inorbit = False        # orbiting a planet
350         self.imine = False        # mining
351         self.icrystl = False        # dilithium crystals aboard
352         self.iseenit = False        # seen base attack report
353         self.thawed = False        # thawed game
354         self.condition = None        # "green", "yellow", "red", "docked", "dead"
355         self.iscraft = None        # "onship", "offship", "removed"
356         self.skill = None        # Player skill level
357         self.inkling = 0        # initial number of klingons
358         self.inbase = 0                # initial number of bases
359         self.incom = 0                # initial number of commanders
360         self.inscom = 0                # initial number of commanders
361         self.inrom = 0                # initial number of commanders
362         self.instar = 0                # initial stars
363         self.intorps = 0        # initial/max torpedoes
364         self.torps = 0                # number of torpedoes
365         self.ship = 0                # ship type -- 'E' is Enterprise
366         self.abandoned = 0        # count of crew abandoned in space
367         self.length = 0                # length of game
368         self.klhere = 0                # klingons here
369         self.casual = 0                # causalties
370         self.nhelp = 0                # calls for help
371         self.nkinks = 0                # count of energy-barrier crossings
372         self.iplnet = None        # planet # in quadrant
373         self.inplan = 0                # initial planets
374         self.irhere = 0                # Romulans in quadrant
375         self.isatb = 0                # =2 if super commander is attacking base
376         self.tourn = None        # tournament number
377         self.nprobes = 0        # number of probes available
378         self.inresor = 0.0        # initial resources
379         self.intime = 0.0        # initial time
380         self.inenrg = 0.0        # initial/max energy
381         self.inshld = 0.0        # initial/max shield
382         self.inlsr = 0.0        # initial life support resources
383         self.indate = 0.0        # initial date
384         self.energy = 0.0        # energy level
385         self.shield = 0.0        # shield level
386         self.warpfac = 0.0        # warp speed
387         self.lsupres = 0.0        # life support reserves
388         self.optime = 0.0        # time taken by current operation
389         self.damfac = 0.0        # damage factor
390         self.lastchart = 0.0        # time star chart was last updated
391         self.cryprob = 0.0        # probability that crystal will work
392         self.probe = None        # object holding probe course info
393         self.height = 0.0        # height of orbit around planet
394         self.score = 0.0        # overall score
395         self.perdate = 0.0        # rate of kills
396         self.idebug = False        # Debugging instrumentation enabled?
397         self.statekscmdr = None # No SuperCommander coordinates yet.
398         self.brigcapacity = 400     # Enterprise brig capacity
399         self.brigfree = 400       # How many klingons can we put in the brig?
400         self.kcaptured = 0      # Total Klingons captured, for scoring.
401     def recompute(self):
402         # Stas thinks this should be (C expression):
403         # game.state.remkl + len(game.state.kcmdr) > 0 ?
404         #        game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
405         # He says the existing expression is prone to divide-by-zero errors
406         # after killing the last klingon when score is shown -- perhaps also
407         # if the only remaining klingon is SCOM.
408         self.state.remtime = self.state.remres/(self.state.remkl + 4*len(self.state.kcmdr))
409
410 FWON = 0
411 FDEPLETE = 1
412 FLIFESUP = 2
413 FNRG = 3
414 FBATTLE = 4
415 FNEG3 = 5
416 FNOVA = 6
417 FSNOVAED = 7
418 FABANDN = 8
419 FDILITHIUM = 9
420 FMATERIALIZE = 10
421 FPHASER = 11
422 FLOST = 12
423 FMINING = 13
424 FDPLANET = 14
425 FPNOVA = 15
426 FSSC = 16
427 FSTRACTOR = 17
428 FDRAY = 18
429 FTRIBBLE = 19
430 FHOLE = 20
431 FCREW = 21
432
433 def withprob(p):
434     return random.random() < p
435
436 def randrange(*args):
437     return random.randrange(*args)
438
439 def randreal(*args):
440     v = random.random()
441     if len(args) == 1:
442         v *= args[0]                 # from [0, args[0])
443     elif len(args) == 2:
444         v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
445     return v
446
447 # Code from ai.c begins here
448
449 def welcoming(iq):
450     "Would this quadrant welcome another Klingon?"
451     return iq.valid_quadrant() and \
452         not game.state.galaxy[iq.i][iq.j].supernova and \
453         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
454
455 def tryexit(enemy, look, irun):
456     "A bad guy attempts to bug out."
457     iq = Coord()
458     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
459     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
460     if not welcoming(iq):
461         return False
462     if enemy.type == 'R':
463         return False # Romulans cannot escape!
464     if not irun:
465         # avoid intruding on another commander's territory
466         if enemy.type == 'C':
467             if iq in game.state.kcmdr:
468                 return []
469             # refuse to leave if currently attacking starbase
470             if game.battle == game.quadrant:
471                 return []
472         # don't leave if over 1000 units of energy
473         if enemy.power > 1000.0:
474             return []
475     oldloc = copy.copy(enemy.location)
476     # handle local matters related to escape
477     enemy.move(None)
478     game.klhere -= 1
479     if game.condition != "docked":
480         newcnd()
481     # Handle global matters related to escape
482     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
483     game.state.galaxy[iq.i][iq.j].klingons += 1
484     if enemy.type == 'S':
485         game.iscate = False
486         game.ientesc = False
487         game.isatb = 0
488         schedule(FSCMOVE, 0.2777)
489         unschedule(FSCDBAS)
490         game.state.kscmdr = iq
491     else:
492         for cmdr in game.state.kcmdr:
493             if cmdr == game.quadrant:
494                 game.state.kcmdr.append(iq)
495                 break
496     # report move out of quadrant.
497     return [(True, enemy, oldloc, iq)]
498
499 # The bad-guy movement algorithm:
500 #
501 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
502 # If both are operating full strength, force is 1000. If both are damaged,
503 # force is -1000. Having shields down subtracts an additional 1000.
504 #
505 # 2. Enemy has forces equal to the energy of the attacker plus
506 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
507 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
508 #
509 # Attacker Initial energy levels (nominal):
510 # Klingon   Romulan   Commander   Super-Commander
511 # Novice    400        700        1200
512 # Fair      425        750        1250
513 # Good      450        800        1300        1750
514 # Expert    475        850        1350        1875
515 # Emeritus  500        900        1400        2000
516 # VARIANCE   75        200         200         200
517 #
518 # Enemy vessels only move prior to their attack. In Novice - Good games
519 # only commanders move. In Expert games, all enemy vessels move if there
520 # is a commander present. In Emeritus games all enemy vessels move.
521 #
522 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
523 # forces are 1000 greater than Enterprise.
524 #
525 # Agressive action on average cuts the distance between the ship and
526 # the enemy to 1/4 the original.
527 #
528 # 4.  At lower energy advantage, movement units are proportional to the
529 # advantage with a 650 advantage being to hold ground, 800 to move forward
530 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
531 #
532 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
533 # retreat, especially at high skill levels.
534 #
535 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
536
537 def movebaddy(enemy):
538     "Tactical movement for the bad guys."
539     goto = Coord()
540     look = Coord()
541     irun = False
542     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
543     if game.skill >= SKILL_EXPERT:
544         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
545     else:
546         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
547     old_dist = enemy.kdist
548     mdist = int(old_dist + 0.5) # Nearest integer distance
549     # If SC, check with spy to see if should hi-tail it
550     if enemy.type == 'S' and \
551         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
552         irun = True
553         motion = -QUADSIZE
554     else:
555         # decide whether to advance, retreat, or hold position
556         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
557         if not game.shldup:
558             forces += 1000 # Good for enemy if shield is down!
559         if not damaged(DPHASER) or not damaged(DPHOTON):
560             if damaged(DPHASER): # phasers damaged
561                 forces += 300.0
562             else:
563                 forces -= 0.2*(game.energy - 2500.0)
564             if damaged(DPHOTON): # photon torpedoes damaged
565                 forces += 300.0
566             else:
567                 forces -= 50.0*game.torps
568         else:
569             # phasers and photon tubes both out!
570             forces += 1000.0
571         motion = 0
572         if forces <= 1000.0 and game.condition != "docked": # Typical situation
573             motion = ((forces + randreal(200))/150.0) - 5.0
574         else:
575             if forces > 1000.0: # Very strong -- move in for kill
576                 motion = (1.0 - randreal())**2 * old_dist + 1.0
577             if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off !
578                 motion -= game.skill*(2.0-randreal()**2)
579         if game.idebug:
580             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
581         # don't move if no motion
582         if motion == 0:
583             return []
584         # Limit motion according to skill
585         if abs(motion) > game.skill:
586             if motion < 0:
587                 motion = -game.skill
588             else:
589                 motion = game.skill
590     # calculate preferred number of steps
591     nsteps = abs(int(motion))
592     if motion > 0 and nsteps > mdist:
593         nsteps = mdist # don't overshoot
594     if nsteps > QUADSIZE:
595         nsteps = QUADSIZE # This shouldn't be necessary
596     if nsteps < 1:
597         nsteps = 1 # This shouldn't be necessary
598     if game.idebug:
599         proutn("NSTEPS = %d:" % nsteps)
600     # Compute preferred values of delta X and Y
601     m = game.sector - enemy.location
602     if 2.0 * abs(m.i) < abs(m.j):
603         m.i = 0
604     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
605         m.j = 0
606     m = (motion * m).sgn()
607     goto = enemy.location
608     # main move loop
609     for ll in range(nsteps):
610         if game.idebug:
611             proutn(" %d" % (ll+1))
612         # Check if preferred position available
613         look = goto + m
614         if m.i < 0:
615             krawli = 1
616         else:
617             krawli = -1
618         if m.j < 0:
619             krawlj = 1
620         else:
621             krawlj = -1
622         success = False
623         attempts = 0 # Settle mysterious hang problem
624         while attempts < 20 and not success:
625             attempts += 1
626             if look.i < 0 or look.i >= QUADSIZE:
627                 if motion < 0:
628                     return tryexit(enemy, look, irun)
629                 if krawli == m.i or m.j == 0:
630                     break
631                 look.i = goto.i + krawli
632                 krawli = -krawli
633             elif look.j < 0 or look.j >= QUADSIZE:
634                 if motion < 0:
635                     return tryexit(enemy, look, irun)
636                 if krawlj == m.j or m.i == 0:
637                     break
638                 look.j = goto.j + krawlj
639                 krawlj = -krawlj
640             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
641                 # See if enemy should ram ship
642                 if game.quad[look.i][look.j] == game.ship and \
643                     (enemy.type == 'C' or enemy.type == 'S'):
644                     collision(rammed=True, enemy=enemy)
645                     return []
646                 if krawli != m.i and m.j != 0:
647                     look.i = goto.i + krawli
648                     krawli = -krawli
649                 elif krawlj != m.j and m.i != 0:
650                     look.j = goto.j + krawlj
651                     krawlj = -krawlj
652                 else:
653                     break # we have failed
654             else:
655                 success = True
656         if success:
657             goto = look
658             if game.idebug:
659                 proutn(repr(goto))
660         else:
661             break # done early
662     if game.idebug:
663         skip(1)
664     # Enemy moved, but is still in sector
665     return [(False, enemy, old_dist, goto)]
666
667 def moveklings():
668     "Sequence Klingon tactical movement."
669     if game.idebug:
670         prout("== MOVCOM")
671     # Figure out which Klingon is the commander (or Supercommander)
672     # and do move
673     tacmoves = []
674     if game.quadrant in game.state.kcmdr:
675         for enemy in game.enemies:
676             if enemy.type == 'C':
677                 tacmoves += movebaddy(enemy)
678     if game.state.kscmdr == game.quadrant:
679         for enemy in game.enemies:
680             if enemy.type == 'S':
681                 tacmoves += movebaddy(enemy)
682                 break
683     # If skill level is high, move other Klingons and Romulans too!
684     # Move these last so they can base their actions on what the
685     # commander(s) do.
686     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
687         for enemy in game.enemies:
688             if enemy.type in ('K', 'R'):
689                 tacmoves += movebaddy(enemy)
690     return tacmoves
691
692 def movescom(iq, avoid):
693     "Commander movement helper."
694     # Avoid quadrants with bases if we want to avoid Enterprise
695     if not welcoming(iq) or (avoid and iq in game.state.baseq):
696         return False
697     if game.justin and not game.iscate:
698         return False
699     # do the move
700     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
701     game.state.kscmdr = iq
702     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
703     if game.state.kscmdr == game.quadrant:
704         # SC has scooted, remove him from current quadrant
705         game.iscate = False
706         game.isatb = 0
707         game.ientesc = False
708         unschedule(FSCDBAS)
709         for enemy in game.enemies:
710             if enemy.type == 'S':
711                 enemy.move(None)
712         game.klhere -= 1
713         if game.condition != "docked":
714             newcnd()
715         sortenemies()
716     # check for a helpful planet
717     for i in range(game.inplan):
718         if game.state.planets[i].quadrant == game.state.kscmdr and \
719             game.state.planets[i].crystals == "present":
720             # destroy the planet
721             game.state.planets[i].pclass = "destroyed"
722             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
723             if communicating():
724                 announce()
725                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
726                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
727                 prout(_("   by the Super-commander.\""))
728             break
729     return True # looks good!
730
731 def supercommander():
732     "Move the Super Commander."
733     iq = Coord()
734     sc = Coord()
735     ibq = Coord()
736     idelta = Coord()
737     basetbl = []
738     if game.idebug:
739         prout("== SUPERCOMMANDER")
740     # Decide on being active or passive
741     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 \
742             (game.state.date-game.indate) < 3.0)
743     if not game.iscate and avoid:
744         # compute move away from Enterprise
745         idelta = game.state.kscmdr-game.quadrant
746         if idelta.distance() > 2.0:
747             # circulate in space
748             idelta.i = game.state.kscmdr.j-game.quadrant.j
749             idelta.j = game.quadrant.i-game.state.kscmdr.i
750     else:
751         # compute distances to starbases
752         if not game.state.baseq:
753             # nothing left to do
754             unschedule(FSCMOVE)
755             return
756         sc = game.state.kscmdr
757         for (i, base) in enumerate(game.state.baseq):
758             basetbl.append((i, (base - sc).distance()))
759         if game.state.baseq > 1:
760             basetbl.sort(key=lambda x: x[1])
761         # look for nearest base without a commander, no Enterprise, and
762         # without too many Klingons, and not already under attack.
763         ifindit = iwhichb = 0
764         for (i2, base) in enumerate(game.state.baseq):
765             i = basetbl[i2][0]        # bug in original had it not finding nearest
766             if base == game.quadrant or base == game.battle or not welcoming(base):
767                 continue
768             # if there is a commander, and no other base is appropriate,
769             # we will take the one with the commander
770             for cmdr in game.state.kcmdr:
771                 if base == cmdr and ifindit != 2:
772                     ifindit = 2
773                     iwhichb = i
774                     break
775             else:        # no commander -- use this one
776                 ifindit = 1
777                 iwhichb = i
778                 break
779         if ifindit == 0:
780             return # Nothing suitable -- wait until next time
781         ibq = game.state.baseq[iwhichb]
782         # decide how to move toward base
783         idelta = ibq - game.state.kscmdr
784     # Maximum movement is 1 quadrant in either or both axes
785     idelta = idelta.sgn()
786     # try moving in both x and y directions
787     # there was what looked like a bug in the Almy C code here,
788     # but it might be this translation is just wrong.
789     iq = game.state.kscmdr + idelta
790     if not movescom(iq, avoid):
791         # failed -- try some other maneuvers
792         if idelta.i == 0 or idelta.j == 0:
793             # attempt angle move
794             if idelta.i != 0:
795                 iq.j = game.state.kscmdr.j + 1
796                 if not movescom(iq, avoid):
797                     iq.j = game.state.kscmdr.j - 1
798                     movescom(iq, avoid)
799             elif idelta.j != 0:
800                 iq.i = game.state.kscmdr.i + 1
801                 if not movescom(iq, avoid):
802                     iq.i = game.state.kscmdr.i - 1
803                     movescom(iq, avoid)
804         else:
805             # try moving just in x or y
806             iq.j = game.state.kscmdr.j
807             if not movescom(iq, avoid):
808                 iq.j = game.state.kscmdr.j + idelta.j
809                 iq.i = game.state.kscmdr.i
810                 movescom(iq, avoid)
811     # check for a base
812     if len(game.state.baseq) == 0:
813         unschedule(FSCMOVE)
814     else:
815         for ibq in game.state.baseq:
816             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
817                 # attack the base
818                 if avoid:
819                     return # no, don't attack base!
820                 game.iseenit = False
821                 game.isatb = 1
822                 schedule(FSCDBAS, randreal(1.0, 3.0))
823                 if is_scheduled(FCDBAS):
824                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
825                 if not communicating():
826                     return # no warning
827                 game.iseenit = True
828                 announce()
829                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
830                       % game.state.kscmdr)
831                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
832                 proutn(_("   It can survive until stardate %d.\"") \
833                        % int(scheduled(FSCDBAS)))
834                 if not game.resting:
835                     return
836                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
837                 if not ja():
838                     return
839                 game.resting = False
840                 game.optime = 0.0 # actually finished
841                 return
842     # Check for intelligence report
843     if not game.idebug and \
844         (withprob(0.8) or \
845          (not communicating()) or \
846          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
847         return
848     announce()
849     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
850     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
851     return
852
853 def movetholian():
854     "Move the Tholian."
855     if not game.tholian or game.justin:
856         return
857     tid = Coord()
858     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
859         tid.i = 0
860         tid.j = QUADSIZE-1
861     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
862         tid.i = QUADSIZE-1
863         tid.j = QUADSIZE-1
864     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
865         tid.i = QUADSIZE-1
866         tid.j = 0
867     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
868         tid.i = 0
869         tid.j = 0
870     else:
871         # something is wrong!
872         game.tholian.move(None)
873         prout("***Internal error: Tholian in a bad spot.")
874         return
875     # do nothing if we are blocked
876     if game.quad[tid.i][tid.j] not in ('.', '#'):
877         return
878     here = copy.copy(game.tholian.location)
879     delta = (tid - game.tholian.location).sgn()
880     # move in x axis
881     while here.i != tid.i:
882         here.i += delta.i
883         if game.quad[here.i][here.j] == '.':
884             game.tholian.move(here)
885     # move in y axis
886     while here.j != tid.j:
887         here.j += delta.j
888         if game.quad[here.i][here.j] == '.':
889             game.tholian.move(here)
890     # check to see if all holes plugged
891     for i in range(QUADSIZE):
892         if game.quad[0][i] != '#' and game.quad[0][i] != 'T':
893             return
894         if game.quad[QUADSIZE-1][i] != '#' and game.quad[QUADSIZE-1][i] != 'T':
895             return
896         if game.quad[i][0] != '#' and game.quad[i][0] != 'T':
897             return
898         if game.quad[i][QUADSIZE-1] != '#' and game.quad[i][QUADSIZE-1] != 'T':
899             return
900     # All plugged up -- Tholian splits
901     game.quad[game.tholian.location.i][game.tholian.location.j] = '#'
902     dropin(' ')
903     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
904     game.tholian.move(None)
905     return
906
907 # Code from battle.c begins here
908
909 def doshield(shraise):
910     "Change shield status."
911     action = "NONE"
912     game.ididit = False
913     if shraise:
914         action = "SHUP"
915     else:
916         key = scanner.nexttok()
917         if key == "IHALPHA":
918             if scanner.sees("transfer"):
919                 action = "NRG"
920             else:
921                 if damaged(DSHIELD):
922                     prout(_("Shields damaged and down."))
923                     return
924                 if scanner.sees("up"):
925                     action = "SHUP"
926                 elif scanner.sees("down"):
927                     action = "SHDN"
928         if action == "NONE":
929             proutn(_("Do you wish to change shield energy? "))
930             if ja():
931                 action = "NRG"
932             elif damaged(DSHIELD):
933                 prout(_("Shields damaged and down."))
934                 return
935             elif game.shldup:
936                 proutn(_("Shields are up. Do you want them down? "))
937                 if ja():
938                     action = "SHDN"
939                 else:
940                     scanner.chew()
941                     return
942             else:
943                 proutn(_("Shields are down. Do you want them up? "))
944                 if ja():
945                     action = "SHUP"
946                 else:
947                     scanner.chew()
948                     return
949     if action == "SHUP": # raise shields
950         if game.shldup:
951             prout(_("Shields already up."))
952             return
953         game.shldup = True
954         game.shldchg = True
955         if game.condition != "docked":
956             game.energy -= 50.0
957         prout(_("Shields raised."))
958         if game.energy <= 0:
959             skip(1)
960             prout(_("Shields raising uses up last of energy."))
961             finish(FNRG)
962             return
963         game.ididit = True
964         return
965     elif action == "SHDN":
966         if not game.shldup:
967             prout(_("Shields already down."))
968             return
969         game.shldup = False
970         game.shldchg = True
971         prout(_("Shields lowered."))
972         game.ididit = True
973         return
974     elif action == "NRG":
975         while scanner.nexttok() != "IHREAL":
976             scanner.chew()
977             proutn(_("Energy to transfer to shields- "))
978         nrg = scanner.real
979         scanner.chew()
980         if nrg == 0:
981             return
982         if nrg > game.energy:
983             prout(_("Insufficient ship energy."))
984             return
985         game.ididit = True
986         if game.shield+nrg >= game.inshld:
987             prout(_("Shield energy maximized."))
988             if game.shield+nrg > game.inshld:
989                 prout(_("Excess energy requested returned to ship energy"))
990             game.energy -= game.inshld-game.shield
991             game.shield = game.inshld
992             return
993         if nrg < 0.0 and game.energy-nrg > game.inenrg:
994             # Prevent shield drain loophole
995             skip(1)
996             prout(_("Engineering to bridge--"))
997             prout(_("  Scott here. Power circuit problem, Captain."))
998             prout(_("  I can't drain the shields."))
999             game.ididit = False
1000             return
1001         if game.shield+nrg < 0:
1002             prout(_("All shield energy transferred to ship."))
1003             game.energy += game.shield
1004             game.shield = 0.0
1005             return
1006         proutn(_("Scotty- \""))
1007         if nrg > 0:
1008             prout(_("Transferring energy to shields.\""))
1009         else:
1010             prout(_("Draining energy from shields.\""))
1011         game.shield += nrg
1012         game.energy -= nrg
1013         return
1014
1015 def randdevice():
1016     "Choose a device to damage, at random."
1017     weights = (
1018         105,        # DSRSENS: short range scanners        10.5%
1019         105,        # DLRSENS: long range scanners                10.5%
1020         120,        # DPHASER: phasers                        12.0%
1021         120,        # DPHOTON: photon torpedoes                12.0%
1022         25,        # DLIFSUP: life support                         2.5%
1023         65,        # DWARPEN: warp drive                         6.5%
1024         70,        # DIMPULS: impulse engines                 6.5%
1025         145,        # DSHIELD: deflector shields                14.5%
1026         30,        # DRADIO:  subspace radio                 3.0%
1027         45,        # DSHUTTL: shuttle                         4.5%
1028         15,        # DCOMPTR: computer                         1.5%
1029         20,        # NAVCOMP: navigation system                 2.0%
1030         75,        # DTRANSP: transporter                         7.5%
1031         20,        # DSHCTRL: high-speed shield controller  2.0%
1032         10,        # DDRAY: death ray                         1.0%
1033         30,        # DDSP: deep-space probes                 3.0%
1034     )
1035     assert(sum(weights) == 1000)
1036     idx = randrange(1000)
1037     wsum = 0
1038     for (i, w) in enumerate(weights):
1039         wsum += w
1040         if idx < wsum:
1041             return i
1042     return None        # we should never get here
1043
1044 def collision(rammed, enemy):
1045     "Collision handling for rammong events."
1046     prouts(_("***RED ALERT!  RED ALERT!"))
1047     skip(1)
1048     prout(_("***COLLISION IMMINENT."))
1049     skip(2)
1050     proutn("***")
1051     proutn(crmshp())
1052     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1053     if rammed:
1054         proutn(_(" rammed by "))
1055     else:
1056         proutn(_(" rams "))
1057     proutn(crmena(False, enemy.type, "sector", enemy.location))
1058     if rammed:
1059         proutn(_(" (original position)"))
1060     skip(1)
1061     deadkl(enemy.location, enemy.type, game.sector)
1062     proutn("***" + crmshp() + " heavily damaged.")
1063     icas = randrange(10, 30)
1064     prout(_("***Sickbay reports %d casualties") % icas)
1065     game.casual += icas
1066     game.state.crew -= icas
1067     # In the pre-SST2K version, all devices got equiprobably damaged,
1068     # which was silly.  Instead, pick up to half the devices at
1069     # random according to our weighting table,
1070     ncrits = randrange(NDEVICES/2)
1071     while ncrits > 0:
1072         ncrits -= 1
1073         dev = randdevice()
1074         if game.damage[dev] < 0:
1075             continue
1076         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1077         # Damage for at least time of travel!
1078         game.damage[dev] += game.optime + extradm
1079     game.shldup = False
1080     prout(_("***Shields are down."))
1081     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1082         announce()
1083         damagereport()
1084     else:
1085         finish(FWON)
1086     return
1087
1088 def torpedo(origin, bearing, dispersion, number, nburst):
1089     "Let a photon torpedo fly"
1090     if not damaged(DSRSENS) or game.condition == "docked":
1091         setwnd(srscan_window)
1092     else:
1093         setwnd(message_window)
1094     ac = bearing + 0.25*dispersion        # dispersion is a random variable
1095     bullseye = (15.0 - bearing)*0.5235988
1096     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin))
1097     bumpto = Coord(0, 0)
1098     # Loop to move a single torpedo
1099     setwnd(message_window)
1100     for step in range(1, QUADSIZE*2):
1101         if not track.nexttok():
1102             break
1103         w = track.sector()
1104         if not w.valid_sector():
1105             break
1106         iquad = game.quad[w.i][w.j]
1107         tracktorpedo(w, step, number, nburst, iquad)
1108         if iquad == '.':
1109             continue
1110         # hit something
1111         setwnd(message_window)
1112         if not damaged(DSRSENS) or game.condition == "docked":
1113             skip(1)        # start new line after text track
1114         if iquad in ('E', 'F'): # Hit our ship
1115             skip(1)
1116             prout(_("Torpedo hits %s.") % crmshp())
1117             hit = 700.0 + randreal(100) - \
1118                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1119             newcnd() # we're blown out of dock
1120             if game.landed or game.condition == "docked":
1121                 return hit # Cheat if on a planet
1122             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1123             # is 143 degrees, which is almost exactly 4.8 clockface units
1124             displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1125             displacement.nexttok()
1126             bumpto = displacement.sector()
1127             if not bumpto.valid_sector():
1128                 return hit
1129             if game.quad[bumpto.i][bumpto.j] == ' ':
1130                 finish(FHOLE)
1131                 return hit
1132             if game.quad[bumpto.i][bumpto.j] != '.':
1133                 # can't move into object
1134                 return hit
1135             game.sector = bumpto
1136             proutn(crmshp())
1137             game.quad[w.i][w.j] = '.'
1138             game.quad[bumpto.i][bumpto.j] = iquad
1139             prout(_(" displaced by blast to Sector %s ") % bumpto)
1140             for enemy in game.enemies:
1141                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1142             sortenemies()
1143             return None
1144         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy
1145             # find the enemy
1146             if iquad in ('C', 'S') and withprob(0.05):
1147                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1148                 prout(_("   torpedo neutralized."))
1149                 return None
1150             for enemy in game.enemies:
1151                 if w == enemy.location:
1152                     kp = math.fabs(enemy.power)
1153                     h1 = 700.0 + randrange(100) - \
1154                         1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1155                     h1 = math.fabs(h1)
1156                     if kp < h1:
1157                         h1 = kp
1158                     if enemy.power < 0:
1159                         enemy.power -= -h1
1160                     else:
1161                         enemy.power -= h1
1162                     if enemy.power == 0:
1163                         deadkl(w, iquad, w)
1164                         return None
1165                     proutn(crmena(True, iquad, "sector", w))
1166                     displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1167                     displacement.nexttok()
1168                     bumpto = displacement.sector()
1169                     if not bumpto.valid_sector():
1170                         prout(_(" damaged but not destroyed."))
1171                         return
1172                     if game.quad[bumpto.i][bumpto.j] == ' ':
1173                         prout(_(" buffeted into black hole."))
1174                         deadkl(w, iquad, bumpto)
1175                     if game.quad[bumpto.i][bumpto.j] != '.':
1176                         prout(_(" damaged but not destroyed."))
1177                     else:
1178                         prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1179                         enemy.location = bumpto
1180                         game.quad[w.i][w.j] = '.'
1181                         game.quad[bumpto.i][bumpto.j] = iquad
1182                         for enemy in game.enemies:
1183                             enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1184                         sortenemies()
1185                     break
1186             else:
1187                 prout("Internal error, no enemy where expected!")
1188                 raise SystemExit(1)
1189             return None
1190         elif iquad == 'B': # Hit a base
1191             skip(1)
1192             prout(_("***STARBASE DESTROYED.."))
1193             game.state.baseq = [x for x in game.state.baseq if x != game.quadrant]
1194             game.quad[w.i][w.j] = '.'
1195             game.base.invalidate()
1196             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
1197             game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
1198             game.state.basekl += 1
1199             newcnd()
1200             return None
1201         elif iquad == 'P': # Hit a planet
1202             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1203             game.state.nplankl += 1
1204             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1205             game.iplnet.pclass = "destroyed"
1206             game.iplnet = None
1207             game.plnet.invalidate()
1208             game.quad[w.i][w.j] = '.'
1209             if game.landed:
1210                 # captain perishes on planet
1211                 finish(FDPLANET)
1212             return None
1213         elif iquad == '@': # Hit an inhabited world -- very bad!
1214             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1215             game.state.nworldkl += 1
1216             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1217             game.iplnet.pclass = "destroyed"
1218             game.iplnet = None
1219             game.plnet.invalidate()
1220             game.quad[w.i][w.j] = '.'
1221             if game.landed:
1222                 # captain perishes on planet
1223                 finish(FDPLANET)
1224             prout(_("The torpedo destroyed an inhabited planet."))
1225             return None
1226         elif iquad == '*': # Hit a star
1227             if withprob(0.9):
1228                 nova(w)
1229             else:
1230                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1231             return None
1232         elif iquad == '?': # Hit a thingy
1233             if not (game.options & OPTION_THINGY) or withprob(0.3):
1234                 skip(1)
1235                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1236                 skip(1)
1237                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1238                 skip(1)
1239                 proutn(_("Mr. Spock-"))
1240                 prouts(_("  \"Fascinating!\""))
1241                 skip(1)
1242                 deadkl(w, iquad, w)
1243             else:
1244                 # Stas Sergeev added the possibility that
1245                 # you can shove the Thingy and piss it off.
1246                 # It then becomes an enemy and may fire at you.
1247                 thing.angry()
1248             return None
1249         elif iquad == ' ': # Black hole
1250             skip(1)
1251             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1252             return None
1253         elif iquad == '#': # hit the web
1254             skip(1)
1255             prout(_("***Torpedo absorbed by Tholian web."))
1256             return None
1257         elif iquad == 'T':  # Hit a Tholian
1258             h1 = 700.0 + randrange(100) - \
1259                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1260             h1 = math.fabs(h1)
1261             if h1 >= 600:
1262                 game.quad[w.i][w.j] = '.'
1263                 deadkl(w, iquad, w)
1264                 game.tholian = None
1265                 return None
1266             skip(1)
1267             proutn(crmena(True, 'T', "sector", w))
1268             if withprob(0.05):
1269                 prout(_(" survives photon blast."))
1270                 return None
1271             prout(_(" disappears."))
1272             game.tholian.move(None)
1273             game.quad[w.i][w.j] = '#'
1274             dropin(' ')
1275             return None
1276         else: # Problem!
1277             skip(1)
1278             proutn("Don't know how to handle torpedo collision with ")
1279             proutn(crmena(True, iquad, "sector", w))
1280             skip(1)
1281             return None
1282         break
1283     skip(1)
1284     setwnd(message_window)
1285     prout(_("Torpedo missed."))
1286     return None
1287
1288 def fry(hit):
1289     "Critical-hit resolution."
1290     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1291         return
1292     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1293     proutn(_("***CRITICAL HIT--"))
1294     # Select devices and cause damage
1295     cdam = []
1296     while ncrit > 0:
1297         while True:
1298             j = randdevice()
1299             # Cheat to prevent shuttle damage unless on ship
1300             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship")):
1301                 break
1302         cdam.append(j)
1303         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1304         game.damage[j] += extradm
1305         ncrit -= 1
1306     skipcount = 0
1307     for (i, j) in enumerate(cdam):
1308         proutn(device[j])
1309         if skipcount % 3 == 2 and i < len(cdam)-1:
1310             skip(1)
1311         skipcount += 1
1312         if i < len(cdam)-1:
1313             proutn(_(" and "))
1314     prout(_(" damaged."))
1315     if damaged(DSHIELD) and game.shldup:
1316         prout(_("***Shields knocked down."))
1317         game.shldup = False
1318
1319 def attack(torps_ok):
1320     # bad guy attacks us
1321     # torps_ok == False forces use of phasers in an attack
1322     # game could be over at this point, check
1323     if game.alldone:
1324         return
1325     attempt = False
1326     ihurt = False
1327     hitmax = 0.0
1328     hittot = 0.0
1329     chgfac = 1.0
1330     where = "neither"
1331     if game.idebug:
1332         prout("=== ATTACK!")
1333     # Tholian gets to move before attacking
1334     if game.tholian:
1335         movetholian()
1336     # if you have just entered the RNZ, you'll get a warning
1337     if game.neutz: # The one chance not to be attacked
1338         game.neutz = False
1339         return
1340     # commanders get a chance to tac-move towards you
1341     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:
1342         for (bugout, enemy, old, goto) in  moveklings():
1343             if bugout:
1344                 # we know about this if either short or long range
1345                 # sensors are working
1346                 if damaged(DSRSENS) and damaged(DLRSENS) \
1347                        and game.condition != "docked":
1348                     prout(crmena(True, enemy.type, "sector", old) + \
1349                           (_(" escapes to Quadrant %s (and regains strength).") % goto))
1350             else: # Enemy still in-sector
1351                 if enemy.move(goto):
1352                     if not damaged(DSRSENS) or game.condition == "docked":
1353                         proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
1354                         if enemy.kdist < old:
1355                             proutn(_(" advances to "))
1356                         else:
1357                             proutn(_(" retreats to "))
1358                         prout("Sector %s." % goto)
1359         sortenemies()
1360     # if no enemies remain after movement, we're done
1361     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
1362         return
1363     # set up partial hits if attack happens during shield status change
1364     pfac = 1.0/game.inshld
1365     if game.shldchg:
1366         chgfac = 0.25 + randreal(0.5)
1367     skip(1)
1368     # message verbosity control
1369     if game.skill <= SKILL_FAIR:
1370         where = "sector"
1371     for enemy in game.enemies:
1372         if enemy.power < 0:
1373             continue        # too weak to attack
1374         # compute hit strength and diminish shield power
1375         r = randreal()
1376         # Increase chance of photon torpedos if docked or enemy energy is low
1377         if game.condition == "docked":
1378             r *= 0.25
1379         if enemy.power < 500:
1380             r *= 0.25
1381         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1382             continue
1383         # different enemies have different probabilities of throwing a torp
1384         usephasers = not torps_ok or \
1385             (enemy.type == 'K' and r > 0.0005) or \
1386             (enemy.type == 'C' and r > 0.015) or \
1387             (enemy.type == 'R' and r > 0.3) or \
1388             (enemy.type == 'S' and r > 0.07) or \
1389             (enemy.type == '?' and r > 0.05)
1390         if usephasers:            # Enemy uses phasers
1391             if game.condition == "docked":
1392                 continue # Don't waste the effort!
1393             attempt = True # Attempt to attack
1394             dustfac = randreal(0.8, 0.85)
1395             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1396             enemy.power *= 0.75
1397         else: # Enemy uses photon torpedo
1398             # We should be able to make the bearing() method work here
1399             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1400             hit = 0
1401             proutn(_("***TORPEDO INCOMING"))
1402             if not damaged(DSRSENS):
1403                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1404             attempt = True
1405             prout("  ")
1406             dispersion = (randreal()+randreal())*0.5 - 0.5
1407             dispersion += 0.002*enemy.power*dispersion
1408             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1409             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1410                 finish(FWON) # Klingons did themselves in!
1411             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1412                 return # Supernova or finished
1413             if hit is None:
1414                 continue
1415         # incoming phaser or torpedo, shields may dissipate it
1416         if game.shldup or game.shldchg or game.condition == "docked":
1417             # shields will take hits
1418             propor = pfac * game.shield
1419             if game.condition == "docked":
1420                 propor *= 2.1
1421             if propor < 0.1:
1422                 propor = 0.1
1423             hitsh = propor*chgfac*hit+1.0
1424             absorb = 0.8*hitsh
1425             if absorb > game.shield:
1426                 absorb = game.shield
1427             game.shield -= absorb
1428             hit -= hitsh
1429             # taking a hit blasts us out of a starbase dock
1430             if game.condition == "docked":
1431                 dock(False)
1432             # but the shields may take care of it
1433             if propor > 0.1 and hit < 0.005*game.energy:
1434                 continue
1435         # hit from this opponent got through shields, so take damage
1436         ihurt = True
1437         proutn(_("%d unit hit") % int(hit))
1438         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1439             proutn(_(" on the ") + crmshp())
1440         if not damaged(DSRSENS) and usephasers:
1441             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1442         skip(1)
1443         # Decide if hit is critical
1444         if hit > hitmax:
1445             hitmax = hit
1446         hittot += hit
1447         fry(hit)
1448         game.energy -= hit
1449     if game.energy <= 0:
1450         # Returning home upon your shield, not with it...
1451         finish(FBATTLE)
1452         return
1453     if not attempt and game.condition == "docked":
1454         prout(_("***Enemies decide against attacking your ship."))
1455     percent = 100.0*pfac*game.shield+0.5
1456     if not ihurt:
1457         # Shields fully protect ship
1458         proutn(_("Enemy attack reduces shield strength to "))
1459     else:
1460         # Emit message if starship suffered hit(s)
1461         skip(1)
1462         proutn(_("Energy left %2d    shields ") % int(game.energy))
1463         if game.shldup:
1464             proutn(_("up "))
1465         elif not damaged(DSHIELD):
1466             proutn(_("down "))
1467         else:
1468             proutn(_("damaged, "))
1469     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1470     # Check if anyone was hurt
1471     if hitmax >= 200 or hittot >= 500:
1472         icas = randrange(int(hittot * 0.015))
1473         if icas >= 2:
1474             skip(1)
1475             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1476             prout(_("   in that last attack.\""))
1477             game.casual += icas
1478             game.state.crew -= icas
1479     # After attack, reset average distance to enemies
1480     for enemy in game.enemies:
1481         enemy.kavgd = enemy.kdist
1482     sortenemies()
1483     return
1484
1485 def deadkl(w, etype, mv):
1486     "Kill a Klingon, Tholian, Romulan, or Thingy."
1487     # Added mv to allow enemy to "move" before dying
1488     proutn(crmena(True, etype, "sector", mv))
1489     # Decide what kind of enemy it is and update appropriately
1490     if etype == 'R':
1491         # Chalk up a Romulan
1492         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1493         game.irhere -= 1
1494         game.state.nromrem -= 1
1495     elif etype == 'T':
1496         # Killed a Tholian
1497         game.tholian = None
1498     elif etype == '?':
1499         # Killed a Thingy
1500         global thing
1501         thing = None
1502     else:
1503         # Killed some type of Klingon
1504         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1505         game.klhere -= 1
1506         if type == 'C':
1507             game.state.kcmdr.remove(game.quadrant)
1508             unschedule(FTBEAM)
1509             if game.state.kcmdr:
1510                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1511             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1512                 unschedule(FCDBAS)
1513         elif type ==  'K':
1514             game.state.remkl -= 1
1515         elif type ==  'S':
1516             game.state.nscrem -= 1
1517             game.state.kscmdr.invalidate()
1518             game.isatb = 0
1519             game.iscate = False
1520             unschedule(FSCMOVE)
1521             unschedule(FSCDBAS)
1522     # For each kind of enemy, finish message to player
1523     prout(_(" destroyed."))
1524     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1525         return
1526     game.recompute()
1527     # Remove enemy ship from arrays describing local conditions
1528     for e in game.enemies:
1529         if e.location == w:
1530             e.move(None)
1531             break
1532     return
1533
1534 def targetcheck(w):
1535     "Return None if target is invalid, otherwise return a course angle."
1536     if not w.valid_sector():
1537         huh()
1538         return None
1539     delta = Coord()
1540     # C code this was translated from is wacky -- why the sign reversal?
1541     delta.j = (w.j - game.sector.j)
1542     delta.i = (game.sector.i - w.i)
1543     if delta == Coord(0, 0):
1544         skip(1)
1545         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1546         prout(_("  I recommend an immediate review of"))
1547         prout(_("  the Captain's psychological profile.\""))
1548         scanner.chew()
1549         return None
1550     return delta.bearing()
1551
1552 def torps():
1553     "Launch photon torpedo salvo."
1554     tcourse = []
1555     game.ididit = False
1556     if damaged(DPHOTON):
1557         prout(_("Photon tubes damaged."))
1558         scanner.chew()
1559         return
1560     if game.torps == 0:
1561         prout(_("No torpedoes left."))
1562         scanner.chew()
1563         return
1564     # First, get torpedo count
1565     while True:
1566         scanner.nexttok()
1567         if scanner.token == "IHALPHA":
1568             huh()
1569             return
1570         elif scanner.token == "IHEOL" or not scanner.waiting():
1571             prout(_("%d torpedoes left.") % game.torps)
1572             scanner.chew()
1573             proutn(_("Number of torpedoes to fire- "))
1574             continue        # Go back around to get a number
1575         else: # key == "IHREAL"
1576             n = scanner.int()
1577             if n <= 0: # abort command
1578                 scanner.chew()
1579                 return
1580             if n > MAXBURST:
1581                 scanner.chew()
1582                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1583                 return
1584             if n > game.torps:
1585                 scanner.chew()        # User requested more torps than available
1586                 continue        # Go back around
1587             break        # All is good, go to next stage
1588     # Next, get targets
1589     target = []
1590     for i in range(n):
1591         key = scanner.nexttok()
1592         if i == 0 and key == "IHEOL":
1593             break        # no coordinate waiting, we will try prompting
1594         if i == 1 and key == "IHEOL":
1595             # direct all torpedoes at one target
1596             while i < n:
1597                 target.append(target[0])
1598                 tcourse.append(tcourse[0])
1599                 i += 1
1600             break
1601         scanner.push(scanner.token)
1602         target.append(scanner.getcoord())
1603         if target[-1] is None:
1604             return
1605         tcourse.append(targetcheck(target[-1]))
1606         if tcourse[-1] is None:
1607             return
1608     scanner.chew()
1609     if len(target) == 0:
1610         # prompt for each one
1611         for i in range(n):
1612             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1613             scanner.chew()
1614             target.append(scanner.getcoord())
1615             if target[-1] is None:
1616                 return
1617             tcourse.append(targetcheck(target[-1]))
1618             if tcourse[-1] is None:
1619                 return
1620     game.ididit = True
1621     # Loop for moving <n> torpedoes
1622     for i in range(n):
1623         if game.condition != "docked":
1624             game.torps -= 1
1625         dispersion = (randreal()+randreal())*0.5 -0.5
1626         if math.fabs(dispersion) >= 0.47:
1627             # misfire!
1628             dispersion *= randreal(1.2, 2.2)
1629             if n > 0:
1630                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1631             else:
1632                 prouts(_("***TORPEDO MISFIRES."))
1633             skip(1)
1634             if i < n:
1635                 prout(_("  Remainder of burst aborted."))
1636             if withprob(0.2):
1637                 prout(_("***Photon tubes damaged by misfire."))
1638                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1639             break
1640         if game.shldup or game.condition == "docked":
1641             dispersion *= 1.0 + 0.0001*game.shield
1642         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1643         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1644             return
1645     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
1646         finish(FWON)
1647
1648 def overheat(rpow):
1649     "Check for phasers overheating."
1650     if rpow > 1500:
1651         checkburn = (rpow-1500.0)*0.00038
1652         if withprob(checkburn):
1653             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1654             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1655
1656 def checkshctrl(rpow):
1657     "Check shield control."
1658     skip(1)
1659     if withprob(0.998):
1660         prout(_("Shields lowered."))
1661         return False
1662     # Something bad has happened
1663     prouts(_("***RED ALERT!  RED ALERT!"))
1664     skip(2)
1665     hit = rpow*game.shield/game.inshld
1666     game.energy -= rpow+hit*0.8
1667     game.shield -= hit*0.2
1668     if game.energy <= 0.0:
1669         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1670         skip(1)
1671         stars()
1672         finish(FPHASER)
1673         return True
1674     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1675     skip(2)
1676     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1677     icas = randrange(int(hit*0.012))
1678     skip(1)
1679     fry(0.8*hit)
1680     if icas:
1681         skip(1)
1682         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1683         prout(_("  %d casualties so far.\"") % icas)
1684         game.casual += icas
1685         game.state.crew -= icas
1686     skip(1)
1687     prout(_("Phaser energy dispersed by shields."))
1688     prout(_("Enemy unaffected."))
1689     overheat(rpow)
1690     return True
1691
1692 def hittem(hits):
1693     "Register a phaser hit on Klingons and Romulans."
1694     w = Coord()
1695     skip(1)
1696     kk = 0
1697     for wham in hits:
1698         if wham == 0:
1699             continue
1700         dustfac = randreal(0.9, 1.0)
1701         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1702         kpini = game.enemies[kk].power
1703         kp = math.fabs(kpini)
1704         if PHASEFAC*hit < kp:
1705             kp = PHASEFAC*hit
1706         if game.enemies[kk].power < 0:
1707             game.enemies[kk].power -= -kp
1708         else:
1709             game.enemies[kk].power -= kp
1710         kpow = game.enemies[kk].power
1711         w = game.enemies[kk].location
1712         if hit > 0.005:
1713             if not damaged(DSRSENS):
1714                 boom(w)
1715             proutn(_("%d unit hit on ") % int(hit))
1716         else:
1717             proutn(_("Very small hit on "))
1718         ienm = game.quad[w.i][w.j]
1719         if ienm == '?':
1720             thing.angry()
1721         proutn(crmena(False, ienm, "sector", w))
1722         skip(1)
1723         if kpow == 0:
1724             deadkl(w, ienm, w)
1725             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1726                 finish(FWON)
1727             if game.alldone:
1728                 return
1729             kk -= 1        # don't do the increment
1730             continue
1731         else: # decide whether or not to emasculate klingon
1732             if kpow > 0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1733                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1734                 prout(_("   has just lost its firepower.\""))
1735                 game.enemies[kk].power = -kpow
1736         kk += 1
1737     return
1738
1739 def phasers():
1740     "Fire phasers at bad guys."
1741     hits = []
1742     kz = 0
1743     k = 1
1744     irec = 0 # Cheating inhibitor
1745     ifast = False
1746     no = False
1747     itarg = True
1748     msgflag = True
1749     rpow = 0.0
1750     automode = "NOTSET"
1751     key = ""
1752     skip(1)
1753     # SR sensors and Computer are needed for automode
1754     if damaged(DSRSENS) or damaged(DCOMPTR):
1755         itarg = False
1756     if game.condition == "docked":
1757         prout(_("Phasers can't be fired through base shields."))
1758         scanner.chew()
1759         return
1760     if damaged(DPHASER):
1761         prout(_("Phaser control damaged."))
1762         scanner.chew()
1763         return
1764     if game.shldup:
1765         if damaged(DSHCTRL):
1766             prout(_("High speed shield control damaged."))
1767             scanner.chew()
1768             return
1769         if game.energy <= 200.0:
1770             prout(_("Insufficient energy to activate high-speed shield control."))
1771             scanner.chew()
1772             return
1773         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1774         ifast = True
1775     # Original code so convoluted, I re-did it all
1776     # (That was Tom Almy talking about the C code, I think -- ESR)
1777     while automode == "NOTSET":
1778         key = scanner.nexttok()
1779         if key == "IHALPHA":
1780             if scanner.sees("manual"):
1781                 if len(game.enemies)==0:
1782                     prout(_("There is no enemy present to select."))
1783                     scanner.chew()
1784                     key = "IHEOL"
1785                     automode = "AUTOMATIC"
1786                 else:
1787                     automode = "MANUAL"
1788                     key = scanner.nexttok()
1789             elif scanner.sees("automatic"):
1790                 if (not itarg) and len(game.enemies) != 0:
1791                     automode = "FORCEMAN"
1792                 else:
1793                     if len(game.enemies)==0:
1794                         prout(_("Energy will be expended into space."))
1795                     automode = "AUTOMATIC"
1796                     key = scanner.nexttok()
1797             elif scanner.sees("no"):
1798                 no = True
1799             else:
1800                 huh()
1801                 return
1802         elif key == "IHREAL":
1803             if len(game.enemies)==0:
1804                 prout(_("Energy will be expended into space."))
1805                 automode = "AUTOMATIC"
1806             elif not itarg:
1807                 automode = "FORCEMAN"
1808             else:
1809                 automode = "AUTOMATIC"
1810         else:
1811             # "IHEOL"
1812             if len(game.enemies)==0:
1813                 prout(_("Energy will be expended into space."))
1814                 automode = "AUTOMATIC"
1815             elif not itarg:
1816                 automode = "FORCEMAN"
1817             else:
1818                 proutn(_("Manual or automatic? "))
1819                 scanner.chew()
1820     avail = game.energy
1821     if ifast:
1822         avail -= 200.0
1823     if automode == "AUTOMATIC":
1824         if key == "IHALPHA" and scanner.sees("no"):
1825             no = True
1826             key = scanner.nexttok()
1827         if key != "IHREAL" and len(game.enemies) != 0:
1828             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1829         irec = 0
1830         while True:
1831             scanner.chew()
1832             if not kz:
1833                 for i in range(len(game.enemies)):
1834                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1835             kz = 1
1836             proutn(_("%d units required. ") % irec)
1837             scanner.chew()
1838             proutn(_("Units to fire= "))
1839             key = scanner.nexttok()
1840             if key != "IHREAL":
1841                 return
1842             rpow = scanner.real
1843             if rpow > avail:
1844                 proutn(_("Energy available= %.2f") % avail)
1845                 skip(1)
1846                 key = "IHEOL"
1847             if not rpow > avail:
1848                 break
1849         if rpow <= 0:
1850             # chicken out
1851             scanner.chew()
1852             return
1853         key = scanner.nexttok()
1854         if key == "IHALPHA" and scanner.sees("no"):
1855             no = True
1856         if ifast:
1857             game.energy -= 200 # Go and do it!
1858             if checkshctrl(rpow):
1859                 return
1860         scanner.chew()
1861         game.energy -= rpow
1862         extra = rpow
1863         if len(game.enemies):
1864             extra = 0.0
1865             powrem = rpow
1866             for i in range(len(game.enemies)):
1867                 hits.append(0.0)
1868                 if powrem <= 0:
1869                     continue
1870                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
1871                 over = randreal(1.01, 1.06) * hits[i]
1872                 temp = powrem
1873                 powrem -= hits[i] + over
1874                 if powrem <= 0 and temp < hits[i]:
1875                     hits[i] = temp
1876                 if powrem <= 0:
1877                     over = 0.0
1878                 extra += over
1879             if powrem > 0.0:
1880                 extra += powrem
1881             hittem(hits)
1882             game.ididit = True
1883         if extra > 0 and not game.alldone:
1884             if game.tholian:
1885                 proutn(_("*** Tholian web absorbs "))
1886                 if len(game.enemies)>0:
1887                     proutn(_("excess "))
1888                 prout(_("phaser energy."))
1889             else:
1890                 prout(_("%d expended on empty space.") % int(extra))
1891     elif automode == "FORCEMAN":
1892         scanner.chew()
1893         key = "IHEOL"
1894         if damaged(DCOMPTR):
1895             prout(_("Battle computer damaged, manual fire only."))
1896         else:
1897             skip(1)
1898             prouts(_("---WORKING---"))
1899             skip(1)
1900             prout(_("Short-range-sensors-damaged"))
1901             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1902             prout(_("Manual-fire-must-be-used"))
1903             skip(1)
1904     elif automode == "MANUAL":
1905         rpow = 0.0
1906         for k in range(len(game.enemies)):
1907             aim = game.enemies[k].location
1908             ienm = game.quad[aim.i][aim.j]
1909             if msgflag:
1910                 proutn(_("Energy available= %.2f") % (avail-0.006))
1911                 skip(1)
1912                 msgflag = False
1913                 rpow = 0.0
1914             if damaged(DSRSENS) and \
1915                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
1916                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1917                 scanner.chew()
1918                 key = "IHEOL"
1919                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
1920                 k += 1
1921                 continue
1922             if key == "IHEOL":
1923                 scanner.chew()
1924                 if itarg and k > kz:
1925                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        randreal(1.01, 1.06) + 1.0
1926                 kz = k
1927                 proutn("(")
1928                 if not damaged(DCOMPTR):
1929                     proutn("%d" % irec)
1930                 else:
1931                     proutn("??")
1932                 proutn(")  ")
1933                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
1934                 key = scanner.nexttok()
1935             if key == "IHALPHA" and scanner.sees("no"):
1936                 no = True
1937                 key = scanner.nexttok()
1938                 continue
1939             if key == "IHALPHA":
1940                 huh()
1941                 return
1942             if key == "IHEOL":
1943                 if k == 1: # Let me say I'm baffled by this
1944                     msgflag = True
1945                 continue
1946             if scanner.real < 0:
1947                 # abort out
1948                 scanner.chew()
1949                 return
1950             hits[k] = scanner.real
1951             rpow += scanner.real
1952             # If total requested is too much, inform and start over
1953             if rpow > avail:
1954                 prout(_("Available energy exceeded -- try again."))
1955                 scanner.chew()
1956                 return
1957             key = scanner.nexttok() # scan for next value
1958             k += 1
1959         if rpow == 0.0:
1960             # zero energy -- abort
1961             scanner.chew()
1962             return
1963         if key == "IHALPHA" and scanner.sees("no"):
1964             no = True
1965         game.energy -= rpow
1966         scanner.chew()
1967         if ifast:
1968             game.energy -= 200.0
1969             if checkshctrl(rpow):
1970                 return
1971         hittem(hits)
1972         game.ididit = True
1973      # Say shield raised or malfunction, if necessary
1974     if game.alldone:
1975         return
1976     if ifast:
1977         skip(1)
1978         if no == 0:
1979             if withprob(0.01):
1980                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1981                 prouts(_("         CLICK   CLICK   POP  . . ."))
1982                 prout(_(" No response, sir!"))
1983                 game.shldup = False
1984             else:
1985                 prout(_("Shields raised."))
1986         else:
1987             game.shldup = False
1988     overheat(rpow)
1989
1990
1991 def capture():
1992     game.ididit = False # Nothing if we fail
1993     Time = 0.0;
1994
1995     # Make sure there is room in the brig */
1996     if game.brigfree == 0:
1997         prout(_("Security reports the brig is already full."))
1998         return;
1999
2000     if damaged(DRADIO):
2001         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2002         return
2003
2004     if damaged(DTRANSP):
2005         prout(_("Scotty- \"Transporter damaged, sir.\""))
2006         return
2007
2008     # find out if there are any at all
2009     if game.klhere < 1:
2010         prout(_("Uhura- \"Getting no response, sir.\""))
2011         return
2012
2013     # if there is more than one Klingon, find out which one */
2014     #   Cruddy, just takes one at random.  Should ask the captain.
2015     #   Nah, just select the weakest one since it is most likely to
2016     #   surrender (Tom Almy mod)
2017     klingons = [e for e in game.enemies if e.type == 'K']
2018     weakest = sorted(klingons, key=lambda e: e.power)
2019     Time = 0.05         # This action will take some time
2020     game.ididit = True #  So any others can strike back
2021
2022     # check out that Klingon
2023     # The algorithm isn't that great and could use some more
2024     # intelligent design
2025     # x = 300 + 25*skill;
2026     x = game.energy / (weakest.power * len(klingons))
2027     x *= 2.5;  # would originally have been equivalent of 1.4,
2028                # but we want command to work more often, more humanely */
2029     #prout(_("Prob = %d (%.4f)\n", i, x))
2030     #   x = 100; // For testing, of course!
2031     if x > randreal(100):
2032         # guess what, he surrendered!!! */
2033         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2034         i = randreal(200)
2035         if i > 0:
2036             prout(_("%d Klingons commit suicide rather than be taken captive.") % 200 - i)
2037         if i > brigfree:
2038             prout(_("%d Klingons die because there is no room for them in the brig.") % i-brigfree)
2039             i = brigfree
2040         brigfree -= i
2041         prout(_("%d captives taken") % i)
2042         deadkl(weakest.location, weakest.type, game.sector)
2043         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
2044             finish(FWON)
2045         return
2046
2047         # big surprise, he refuses to surrender */
2048         prout(_("Fat chance, captain!"))
2049
2050 # Code from events.c begins here.
2051
2052 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2053 # event of each type active at any given time.  Mostly these means we can
2054 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2055 # BSD Trek, from which we swiped the idea, can have up to 5.
2056
2057 def unschedule(evtype):
2058     "Remove an event from the schedule."
2059     game.future[evtype].date = FOREVER
2060     return game.future[evtype]
2061
2062 def is_scheduled(evtype):
2063     "Is an event of specified type scheduled."
2064     return game.future[evtype].date != FOREVER
2065
2066 def scheduled(evtype):
2067     "When will this event happen?"
2068     return game.future[evtype].date
2069
2070 def schedule(evtype, offset):
2071     "Schedule an event of specified type."
2072     game.future[evtype].date = game.state.date + offset
2073     return game.future[evtype]
2074
2075 def postpone(evtype, offset):
2076     "Postpone a scheduled event."
2077     game.future[evtype].date += offset
2078
2079 def cancelrest():
2080     "Rest period is interrupted by event."
2081     if game.resting:
2082         skip(1)
2083         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2084         if ja():
2085             game.resting = False
2086             game.optime = 0.0
2087             return True
2088     return False
2089
2090 def events():
2091     "Run through the event queue looking for things to do."
2092     i = 0
2093     fintim = game.state.date + game.optime
2094     yank = 0
2095     ictbeam = False
2096     istract = False
2097     w = Coord()
2098     hold = Coord()
2099     ev = Event()
2100     ev2 = Event()
2101
2102     def tractorbeam(yank):
2103         "Tractor-beaming cases merge here."
2104         announce()
2105         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2106         skip(1)
2107         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2108         # If Kirk & Co. screwing around on planet, handle
2109         atover(True) # atover(true) is Grab
2110         if game.alldone:
2111             return
2112         if game.icraft: # Caught in Galileo?
2113             finish(FSTRACTOR)
2114             return
2115         # Check to see if shuttle is aboard
2116         if game.iscraft == "offship":
2117             skip(1)
2118             if withprob(0.5):
2119                 prout(_("Galileo, left on the planet surface, is captured"))
2120                 prout(_("by aliens and made into a flying McDonald's."))
2121                 game.damage[DSHUTTL] = -10
2122                 game.iscraft = "removed"
2123             else:
2124                 prout(_("Galileo, left on the planet surface, is well hidden."))
2125         if evcode == FSPY:
2126             game.quadrant = game.state.kscmdr
2127         else:
2128             game.quadrant = game.state.kcmdr[i]
2129         game.sector = randplace(QUADSIZE)
2130         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2131                % (game.quadrant, game.sector))
2132         if game.resting:
2133             prout(_("(Remainder of rest/repair period cancelled.)"))
2134             game.resting = False
2135         if not game.shldup:
2136             if not damaged(DSHIELD) and game.shield > 0:
2137                 doshield(shraise=True) # raise shields
2138                 game.shldchg = False
2139             else:
2140                 prout(_("(Shields not currently useable.)"))
2141         newqad()
2142         # Adjust finish time to time of tractor beaming?
2143         # fintim = game.state.date+game.optime
2144         attack(torps_ok=False)
2145         if not game.state.kcmdr:
2146             unschedule(FTBEAM)
2147         else:
2148             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2149
2150     def destroybase():
2151         "Code merges here for any commander destroying a starbase."
2152         # Not perfect, but will have to do
2153         # Handle case where base is in same quadrant as starship
2154         if game.battle == game.quadrant:
2155             game.state.chart[game.battle.i][game.battle.j].starbase = False
2156             game.quad[game.base.i][game.base.j] = '.'
2157             game.base.invalidate()
2158             newcnd()
2159             skip(1)
2160             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2161         elif game.state.baseq and communicating():
2162             # Get word via subspace radio
2163             announce()
2164             skip(1)
2165             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2166             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2167             if game.isatb == 2:
2168                 prout(_("the Klingon Super-Commander"))
2169             else:
2170                 prout(_("a Klingon Commander"))
2171             game.state.chart[game.battle.i][game.battle.j].starbase = False
2172         # Remove Starbase from galaxy
2173         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2174         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2175         if game.isatb == 2:
2176             # reinstate a commander's base attack
2177             game.battle = hold
2178             game.isatb = 0
2179         else:
2180             game.battle.invalidate()
2181     if game.idebug:
2182         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2183         for i in range(1, NEVENTS):
2184             if   i == FSNOVA:  proutn("=== Supernova       ")
2185             elif i == FTBEAM:  proutn("=== T Beam          ")
2186             elif i == FSNAP:   proutn("=== Snapshot        ")
2187             elif i == FBATTAK: proutn("=== Base Attack     ")
2188             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2189             elif i == FSCMOVE: proutn("=== SC Move         ")
2190             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2191             elif i == FDSPROB: proutn("=== Probe Move      ")
2192             elif i == FDISTR:  proutn("=== Distress Call   ")
2193             elif i == FENSLV:  proutn("=== Enslavement     ")
2194             elif i == FREPRO:  proutn("=== Klingon Build   ")
2195             if is_scheduled(i):
2196                 prout("%.2f" % (scheduled(i)))
2197             else:
2198                 prout("never")
2199     radio_was_broken = damaged(DRADIO)
2200     hold.i = hold.j = 0
2201     while True:
2202         # Select earliest extraneous event, evcode==0 if no events
2203         evcode = FSPY
2204         if game.alldone:
2205             return
2206         datemin = fintim
2207         for l in range(1, NEVENTS):
2208             if game.future[l].date < datemin:
2209                 evcode = l
2210                 if game.idebug:
2211                     prout("== Event %d fires" % evcode)
2212                 datemin = game.future[l].date
2213         xtime = datemin-game.state.date
2214         game.state.date = datemin
2215         # Decrement Federation resources and recompute remaining time
2216         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2217         game.recompute()
2218         if game.state.remtime <= 0:
2219             finish(FDEPLETE)
2220             return
2221         # Any crew left alive?
2222         if game.state.crew <= 0:
2223             finish(FCREW)
2224             return
2225         # Is life support adequate?
2226         if damaged(DLIFSUP) and game.condition != "docked":
2227             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2228                 finish(FLIFESUP)
2229                 return
2230             game.lsupres -= xtime
2231             if game.damage[DLIFSUP] <= xtime:
2232                 game.lsupres = game.inlsr
2233         # Fix devices
2234         repair = xtime
2235         if game.condition == "docked":
2236             repair /= DOCKFAC
2237         # Don't fix Deathray here
2238         for l in range(NDEVICES):
2239             if game.damage[l] > 0.0 and l != DDRAY:
2240                 if game.damage[l]-repair > 0.0:
2241                     game.damage[l] -= repair
2242                 else:
2243                     game.damage[l] = 0.0
2244         # If radio repaired, update star chart and attack reports
2245         if radio_was_broken and not damaged(DRADIO):
2246             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2247             prout(_("   surveillance reports are coming in."))
2248             skip(1)
2249             if not game.iseenit:
2250                 attackreport(False)
2251                 game.iseenit = True
2252             rechart()
2253             prout(_("   The star chart is now up to date.\""))
2254             skip(1)
2255         # Cause extraneous event EVCODE to occur
2256         game.optime -= xtime
2257         if evcode == FSNOVA: # Supernova
2258             announce()
2259             supernova(None)
2260             schedule(FSNOVA, expran(0.5*game.intime))
2261             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2262                 return
2263         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2264             if game.state.nscrem == 0 or \
2265                 ictbeam or istract or \
2266                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2267                 return
2268             if game.ientesc or \
2269                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2270                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2271                 (damaged(DSHIELD) and \
2272                  (game.energy < 2500 or damaged(DPHASER)) and \
2273                  (game.torps < 5 or damaged(DPHOTON))):
2274                 # Tractor-beam her!
2275                 istract = ictbeam = True
2276                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2277             else:
2278                 return
2279         elif evcode == FTBEAM: # Tractor beam
2280             if not game.state.kcmdr:
2281                 unschedule(FTBEAM)
2282                 continue
2283             i = randrange(len(game.state.kcmdr))
2284             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2285             if istract or game.condition == "docked" or yank == 0:
2286                 # Drats! Have to reschedule
2287                 schedule(FTBEAM,
2288                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2289                 continue
2290             ictbeam = True
2291             tractorbeam(yank)
2292         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2293             game.snapsht = copy.deepcopy(game.state)
2294             game.state.snap = True
2295             schedule(FSNAP, expran(0.5 * game.intime))
2296         elif evcode == FBATTAK: # Commander attacks starbase
2297             if not game.state.kcmdr or not game.state.baseq:
2298                 # no can do
2299                 unschedule(FBATTAK)
2300                 unschedule(FCDBAS)
2301                 continue
2302             ibq = None  # Force battle location to persist past loop
2303             try:
2304                 for ibq in game.state.baseq:
2305                     for cmdr in game.state.kcmdr:
2306                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2307                             raise JumpOut
2308                 # no match found -- try later
2309                 schedule(FBATTAK, expran(0.3*game.intime))
2310                 unschedule(FCDBAS)
2311                 continue
2312             except JumpOut:
2313                 pass
2314             # commander + starbase combination found -- launch attack
2315             game.battle = ibq
2316             schedule(FCDBAS, randreal(1.0, 4.0))
2317             if game.isatb: # extra time if SC already attacking
2318                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2319             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2320             game.iseenit = False
2321             if not communicating():
2322                 continue # No warning :-(
2323             game.iseenit = True
2324             announce()
2325             skip(1)
2326             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2327             prout(_("   reports that it is under attack and that it can"))
2328             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2329             if cancelrest():
2330                 return
2331         elif evcode == FSCDBAS: # Supercommander destroys base
2332             unschedule(FSCDBAS)
2333             game.isatb = 2
2334             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2335                 continue # WAS RETURN!
2336             hold = game.battle
2337             game.battle = game.state.kscmdr
2338             destroybase()
2339         elif evcode == FCDBAS: # Commander succeeds in destroying base
2340             if evcode == FCDBAS:
2341                 unschedule(FCDBAS)
2342                 if not game.state.baseq() \
2343                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2344                     game.battle.invalidate()
2345                     continue
2346                 # find the lucky pair
2347                 for cmdr in game.state.kcmdr:
2348                     if cmdr == game.battle:
2349                         break
2350                 else:
2351                     # No action to take after all
2352                     continue
2353             destroybase()
2354         elif evcode == FSCMOVE: # Supercommander moves
2355             schedule(FSCMOVE, 0.2777)
2356             if not game.ientesc and not istract and game.isatb != 1 and \
2357                    (not game.iscate or not game.justin):
2358                 supercommander()
2359         elif evcode == FDSPROB: # Move deep space probe
2360             schedule(FDSPROB, 0.01)
2361             if not game.probe.nexttok():
2362                 if not game.probe.quadrant().valid_quadrant() or \
2363                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2364                     # Left galaxy or ran into supernova
2365                     if communicating():
2366                         announce()
2367                         skip(1)
2368                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2369                         if not game.probe.quadrant().valid_quadrant():
2370                             prout(_("has left the galaxy.\""))
2371                         else:
2372                             prout(_("is no longer transmitting.\""))
2373                     unschedule(FDSPROB)
2374                     continue
2375                 if communicating():
2376                     #announce()
2377                     skip(1)
2378                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2379             pquad = game.probe.quadrant()
2380             pdest = game.state.galaxy[pquad.i][pquad.j]
2381             if communicating():
2382                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2383                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2384                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2385                 pdest.charted = True
2386             game.probe.moves -= 1 # One less to travel
2387             if game.probe.arrived() and game.isarmed and pdest.stars:
2388                 supernova(game.probe)                # fire in the hole!
2389                 unschedule(FDSPROB)
2390                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2391                     return
2392         elif evcode == FDISTR: # inhabited system issues distress call
2393             unschedule(FDISTR)
2394             # try a whole bunch of times to find something suitable
2395             for i in range(100):
2396                 # need a quadrant which is not the current one,
2397                 # which has some stars which are inhabited and
2398                 # not already under attack, which is not
2399                 # supernova'ed, and which has some Klingons in it
2400                 w = randplace(GALSIZE)
2401                 q = game.state.galaxy[w.i][w.j]
2402                 if not (game.quadrant == w or q.planet is None or \
2403                       not q.planet.inhabited or \
2404                       q.supernova or q.status!="secure" or q.klingons<=0):
2405                     break
2406             else:
2407                 # can't seem to find one; ignore this call
2408                 if game.idebug:
2409                     prout("=== Couldn't find location for distress event.")
2410                 continue
2411             # got one!!  Schedule its enslavement
2412             ev = schedule(FENSLV, expran(game.intime))
2413             ev.quadrant = w
2414             q.status = "distressed"
2415             # tell the captain about it if we can
2416             if communicating():
2417                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2418                         % (q.planet, repr(w)))
2419                 prout(_("by a Klingon invasion fleet."))
2420                 if cancelrest():
2421                     return
2422         elif evcode == FENSLV:                # starsystem is enslaved
2423             ev = unschedule(FENSLV)
2424             # see if current distress call still active
2425             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2426             if q.klingons <= 0:
2427                 q.status = "secure"
2428                 continue
2429             q.status = "enslaved"
2430
2431             # play stork and schedule the first baby
2432             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2433             ev2.quadrant = ev.quadrant
2434
2435             # report the disaster if we can
2436             if communicating():
2437                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2438                         q.planet)
2439                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2440         elif evcode == FREPRO:                # Klingon reproduces
2441             # If we ever switch to a real event queue, we'll need to
2442             # explicitly retrieve and restore the x and y.
2443             ev = schedule(FREPRO, expran(1.0 * game.intime))
2444             # see if current distress call still active
2445             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2446             if q.klingons <= 0:
2447                 q.status = "secure"
2448                 continue
2449             if game.state.remkl >= MAXKLGAME:
2450                 continue                # full right now
2451             # reproduce one Klingon
2452             w = ev.quadrant
2453             m = Coord()
2454             if game.klhere >= MAXKLQUAD:
2455                 try:
2456                     # this quadrant not ok, pick an adjacent one
2457                     for m.i in range(w.i - 1, w.i + 2):
2458                         for m.j in range(w.j - 1, w.j + 2):
2459                             if not m.valid_quadrant():
2460                                 continue
2461                             q = game.state.galaxy[m.i][m.j]
2462                             # check for this quad ok (not full & no snova)
2463                             if q.klingons >= MAXKLQUAD or q.supernova:
2464                                 continue
2465                             raise JumpOut
2466                     # search for eligible quadrant failed
2467                     continue
2468                 except JumpOut:
2469                     w = m
2470             # deliver the child
2471             game.state.remkl += 1
2472             q.klingons += 1
2473             if game.quadrant == w:
2474                 game.klhere += 1
2475                 game.enemies.append(newkling())
2476             # recompute time left
2477             game.recompute()
2478             if communicating():
2479                 if game.quadrant == w:
2480                     prout(_("Spock- sensors indicate the Klingons have"))
2481                     prout(_("launched a warship from %s.") % q.planet)
2482                 else:
2483                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2484                     if q.planet != None:
2485                         proutn(_("near %s ") % q.planet)
2486                     prout(_("in Quadrant %s.") % w)
2487
2488 def wait():
2489     "Wait on events."
2490     game.ididit = False
2491     while True:
2492         key = scanner.nexttok()
2493         if key  != "IHEOL":
2494             break
2495         proutn(_("How long? "))
2496     scanner.chew()
2497     if key != "IHREAL":
2498         huh()
2499         return
2500     origTime = delay = scanner.real
2501     if delay <= 0.0:
2502         return
2503     if delay >= game.state.remtime or len(game.enemies) != 0:
2504         proutn(_("Are you sure? "))
2505         if not ja():
2506             return
2507     # Alternate resting periods (events) with attacks
2508     game.resting = True
2509     while True:
2510         if delay <= 0:
2511             game.resting = False
2512         if not game.resting:
2513             prout(_("%d stardates left.") % int(game.state.remtime))
2514             return
2515         temp = game.optime = delay
2516         if len(game.enemies):
2517             rtime = randreal(1.0, 2.0)
2518             if rtime < temp:
2519                 temp = rtime
2520             game.optime = temp
2521         if game.optime < delay:
2522             attack(torps_ok=False)
2523         if game.alldone:
2524             return
2525         events()
2526         game.ididit = True
2527         if game.alldone:
2528             return
2529         delay -= temp
2530         # Repair Deathray if long rest at starbase
2531         if origTime-delay >= 9.99 and game.condition == "docked":
2532             game.damage[DDRAY] = 0.0
2533         # leave if quadrant supernovas
2534         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2535             break
2536     game.resting = False
2537     game.optime = 0.0
2538
2539 def nova(nov):
2540     "Star goes nova."
2541     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2542     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2543     if withprob(0.05):
2544         # Wow! We've supernova'ed
2545         supernova(game.quadrant)
2546         return
2547     # handle initial nova
2548     game.quad[nov.i][nov.j] = '.'
2549     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2550     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2551     game.state.starkl += 1
2552     # Set up queue to recursively trigger adjacent stars
2553     hits = [nov]
2554     kount = 0
2555     while hits:
2556         offset = Coord()
2557         start = hits.pop()
2558         for offset.i in range(-1, 1+1):
2559             for offset.j in range(-1, 1+1):
2560                 if offset.j == 0 and offset.i == 0:
2561                     continue
2562                 neighbor = start + offset
2563                 if not neighbor.valid_sector():
2564                     continue
2565                 iquad = game.quad[neighbor.i][neighbor.j]
2566                 # Empty space ends reaction
2567                 if iquad in ('.', '?', ' ', 'T', '#'):
2568                     pass
2569                 elif iquad == '*': # Affect another star
2570                     if withprob(0.05):
2571                         # This star supernovas
2572                         supernova(game.quadrant)
2573                         return
2574                     else:
2575                         hits.append(neighbor)
2576                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2577                         game.state.starkl += 1
2578                         proutn(crmena(True, '*', "sector", neighbor))
2579                         prout(_(" novas."))
2580                         game.quad[neighbor.i][neighbor.j] = '.'
2581                         kount += 1
2582                 elif iquad in ('P', '@'): # Destroy planet
2583                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2584                     if iquad == 'P':
2585                         game.state.nplankl += 1
2586                     else:
2587                         game.state.nworldkl += 1
2588                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2589                     game.iplnet.pclass = "destroyed"
2590                     game.iplnet = None
2591                     game.plnet.invalidate()
2592                     if game.landed:
2593                         finish(FPNOVA)
2594                         return
2595                     game.quad[neighbor.i][neighbor.j] = '.'
2596                 elif iquad == 'B': # Destroy base
2597                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2598                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2599                     game.base.invalidate()
2600                     game.state.basekl += 1
2601                     newcnd()
2602                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2603                     game.quad[neighbor.i][neighbor.j] = '.'
2604                 elif iquad in ('E', 'F'): # Buffet ship
2605                     prout(_("***Starship buffeted by nova."))
2606                     if game.shldup:
2607                         if game.shield >= 2000.0:
2608                             game.shield -= 2000.0
2609                         else:
2610                             diff = 2000.0 - game.shield
2611                             game.energy -= diff
2612                             game.shield = 0.0
2613                             game.shldup = False
2614                             prout(_("***Shields knocked out."))
2615                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2616                     else:
2617                         game.energy -= 2000.0
2618                     if game.energy <= 0:
2619                         finish(FNOVA)
2620                         return
2621                     # add in course nova contributes to kicking starship
2622                     bump += (game.sector-hits[-1]).sgn()
2623                 elif iquad == 'K': # kill klingon
2624                     deadkl(neighbor, iquad, neighbor)
2625                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2626                     target = None
2627                     for ll in range(len(game.enemies)):
2628                         if game.enemies[ll].location == neighbor:
2629                             target = game.enemies[ll]
2630                             break
2631                     if target is not None:
2632                         target.power -= 800.0 # If firepower is lost, die
2633                         if target.power <= 0.0:
2634                             deadkl(neighbor, iquad, neighbor)
2635                             continue    # neighbor loop
2636                         # Else enemy gets flung by the blast wave
2637                         newc = neighbor + neighbor - hits[-1]
2638                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2639                         if not newc.valid_sector():
2640                             # can't leave quadrant
2641                             skip(1)
2642                             continue
2643                         iquad1 = game.quad[newc.i][newc.j]
2644                         if iquad1 == ' ':
2645                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2646                             skip(1)
2647                             deadkl(neighbor, iquad, newc)
2648                             continue
2649                         if iquad1 != '.':
2650                             # can't move into something else
2651                             skip(1)
2652                             continue
2653                         proutn(_(", buffeted to Sector %s") % newc)
2654                         game.quad[neighbor.i][neighbor.j] = '.'
2655                         game.quad[newc.i][newc.j] = iquad
2656                         target.move(newc)
2657     # Starship affected by nova -- kick it away.
2658     dist = kount*0.1
2659     direc = ncourse[3*(bump.i+1)+bump.j+2]
2660     if direc == 0.0:
2661         dist = 0.0
2662     if dist == 0.0:
2663         return
2664     scourse = course(bearing=direc, distance=dist)
2665     game.optime = scourse.time(w=4)
2666     skip(1)
2667     prout(_("Force of nova displaces starship."))
2668     imove(scourse, noattack=True)
2669     game.optime = scourse.time(w=4)
2670     return
2671
2672 def supernova(w):
2673     "Star goes supernova."
2674     num = 0; npdead = 0
2675     if w != None:
2676         nq = copy.copy(w)
2677     else:
2678         # Scheduled supernova -- select star at random.
2679         nstars = 0
2680         nq = Coord()
2681         for nq.i in range(GALSIZE):
2682             for nq.j in range(GALSIZE):
2683                 nstars += game.state.galaxy[nq.i][nq.j].stars
2684         if stars == 0:
2685             return # nothing to supernova exists
2686         num = randrange(nstars) + 1
2687         for nq.i in range(GALSIZE):
2688             for nq.j in range(GALSIZE):
2689                 num -= game.state.galaxy[nq.i][nq.j].stars
2690                 if num <= 0:
2691                     break
2692             if num <=0:
2693                 break
2694         if game.idebug:
2695             proutn("=== Super nova here?")
2696             if ja():
2697                 nq = game.quadrant
2698     if not nq == game.quadrant or game.justin:
2699         # it isn't here, or we just entered (treat as enroute)
2700         if communicating():
2701             skip(1)
2702             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2703             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2704     else:
2705         ns = Coord()
2706         # we are in the quadrant!
2707         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2708         for ns.i in range(QUADSIZE):
2709             for ns.j in range(QUADSIZE):
2710                 if game.quad[ns.i][ns.j]=='*':
2711                     num -= 1
2712                     if num==0:
2713                         break
2714             if num==0:
2715                 break
2716         skip(1)
2717         prouts(_("***RED ALERT!  RED ALERT!"))
2718         skip(1)
2719         prout(_("***Incipient supernova detected at Sector %s") % ns)
2720         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2721             proutn(_("Emergency override attempts t"))
2722             prouts("***************")
2723             skip(1)
2724             stars()
2725             game.alldone = True
2726     # destroy any Klingons in supernovaed quadrant
2727     kldead = game.state.galaxy[nq.i][nq.j].klingons
2728     game.state.galaxy[nq.i][nq.j].klingons = 0
2729     if nq == game.state.kscmdr:
2730         # did in the Supercommander!
2731         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2732         game.iscate = False
2733         unschedule(FSCMOVE)
2734         unschedule(FSCDBAS)
2735     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2736     comkills = len(game.state.kcmdr) - len(survivors)
2737     game.state.kcmdr = survivors
2738     kldead -= comkills
2739     if not game.state.kcmdr:
2740         unschedule(FTBEAM)
2741     game.state.remkl -= kldead
2742     # destroy Romulans and planets in supernovaed quadrant
2743     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2744     game.state.galaxy[nq.i][nq.j].romulans = 0
2745     game.state.nromrem -= nrmdead
2746     # Destroy planets
2747     for loop in range(game.inplan):
2748         if game.state.planets[loop].quadrant == nq:
2749             game.state.planets[loop].pclass = "destroyed"
2750             npdead += 1
2751     # Destroy any base in supernovaed quadrant
2752     game.state.baseq = [x for x in game.state.baseq if x != nq]
2753     # If starship caused supernova, tally up destruction
2754     if w != None:
2755         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2756         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2757         game.state.nplankl += npdead
2758     # mark supernova in galaxy and in star chart
2759     if game.quadrant == nq or communicating():
2760         game.state.galaxy[nq.i][nq.j].supernova = True
2761     # If supernova destroys last Klingons give special message
2762     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2763         skip(2)
2764         if w is None:
2765             prout(_("Lucky you!"))
2766         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2767         finish(FWON)
2768         return
2769     # if some Klingons remain, continue or die in supernova
2770     if game.alldone:
2771         finish(FSNOVAED)
2772     return
2773
2774 # Code from finish.c ends here.
2775
2776 def selfdestruct():
2777     "Self-destruct maneuver. Finish with a BANG!"
2778     scanner.chew()
2779     if damaged(DCOMPTR):
2780         prout(_("Computer damaged; cannot execute destruct sequence."))
2781         return
2782     prouts(_("---WORKING---")); skip(1)
2783     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2784     prouts("   10"); skip(1)
2785     prouts("       9"); skip(1)
2786     prouts("          8"); skip(1)
2787     prouts("             7"); skip(1)
2788     prouts("                6"); skip(1)
2789     skip(1)
2790     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2791     skip(1)
2792     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2793     skip(1)
2794     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2795     skip(1)
2796     scanner.nexttok()
2797     if game.passwd != scanner.token:
2798         prouts(_("PASSWORD-REJECTED;"))
2799         skip(1)
2800         prouts(_("CONTINUITY-EFFECTED"))
2801         skip(2)
2802         return
2803     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2804     prouts("                   5"); skip(1)
2805     prouts("                      4"); skip(1)
2806     prouts("                         3"); skip(1)
2807     prouts("                            2"); skip(1)
2808     prouts("                              1"); skip(1)
2809     if withprob(0.15):
2810         prouts(_("GOODBYE-CRUEL-WORLD"))
2811         skip(1)
2812     kaboom()
2813
2814 def kaboom():
2815     stars()
2816     if game.ship=='E':
2817         prouts("***")
2818     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2819     skip(1)
2820     stars()
2821     skip(1)
2822     if len(game.enemies) != 0:
2823         whammo = 25.0 * game.energy
2824         for l in range(len(game.enemies)):
2825             if game.enemies[l].power*game.enemies[l].kdist <= whammo:
2826                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2827     finish(FDILITHIUM)
2828
2829 def killrate():
2830     "Compute our rate of kils over time."
2831     elapsed = game.state.date - game.indate
2832     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
2833         return 0
2834     else:
2835         starting = (game.inkling + game.incom + game.inscom)
2836         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2837         return (starting - remaining)/elapsed
2838
2839 def badpoints():
2840     "Compute demerits."
2841     badpt = 5.0*game.state.starkl + \
2842             game.casual + \
2843             10.0*game.state.nplankl + \
2844             300*game.state.nworldkl + \
2845             45.0*game.nhelp +\
2846             100.0*game.state.basekl +\
2847             3.0*game.abandoned
2848     if game.ship == 'F':
2849         badpt += 100.0
2850     elif game.ship is None:
2851         badpt += 200.0
2852     return badpt
2853
2854 def finish(ifin):
2855     # end the game, with appropriate notfications
2856     igotit = False
2857     game.alldone = True
2858     skip(3)
2859     prout(_("It is stardate %.1f.") % game.state.date)
2860     skip(1)
2861     if ifin == FWON: # Game has been won
2862         if game.state.nromrem != 0:
2863             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2864                   game.state.nromrem)
2865
2866         prout(_("You have smashed the Klingon invasion fleet and saved"))
2867         prout(_("the Federation."))
2868         if game.alive and game.brigcapacity-game.brigfree > 0:
2869             game.kcaptured += game.brigcapacity-game.brigfree
2870             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
2871         game.gamewon = True
2872         if game.alive:
2873             badpt = badpoints()
2874             if badpt < 100.0:
2875                 badpt = 0.0        # Close enough!
2876             # killsPerDate >= RateMax
2877             if game.state.date-game.indate < 5.0 or \
2878                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2879                 skip(1)
2880                 prout(_("In fact, you have done so well that Starfleet Command"))
2881                 if game.skill == SKILL_NOVICE:
2882                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2883                 elif game.skill == SKILL_FAIR:
2884                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2885                 elif game.skill == SKILL_GOOD:
2886                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2887                 elif game.skill == SKILL_EXPERT:
2888                     prout(_("promotes you to Commodore Emeritus."))
2889                     skip(1)
2890                     prout(_("Now that you think you're really good, try playing"))
2891                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2892                 elif game.skill == SKILL_EMERITUS:
2893                     skip(1)
2894                     proutn(_("Computer-  "))
2895                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2896                     skip(2)
2897                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2898                     skip(1)
2899                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2900                     skip(1)
2901                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2902                     skip(1)
2903                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2904                     skip(1)
2905                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2906                     skip(2)
2907                     prout(_("Now you can retire and write your own Star Trek game!"))
2908                     skip(1)
2909                 elif game.skill >= SKILL_EXPERT:
2910                     if game.thawed and not game.idebug:
2911                         prout(_("You cannot get a citation, so..."))
2912                     else:
2913                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2914                         scanner.chew()
2915                         if ja():
2916                             igotit = True
2917             # Only grant long life if alive (original didn't!)
2918             skip(1)
2919             prout(_("LIVE LONG AND PROSPER."))
2920         score()
2921         if igotit:
2922             plaque()
2923         return
2924     elif ifin == FDEPLETE: # Federation Resources Depleted
2925         prout(_("Your time has run out and the Federation has been"))
2926         prout(_("conquered.  Your starship is now Klingon property,"))
2927         prout(_("and you are put on trial as a war criminal.  On the"))
2928         proutn(_("basis of your record, you are "))
2929         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2930             prout(_("acquitted."))
2931             skip(1)
2932             prout(_("LIVE LONG AND PROSPER."))
2933         else:
2934             prout(_("found guilty and"))
2935             prout(_("sentenced to death by slow torture."))
2936             game.alive = False
2937         score()
2938         return
2939     elif ifin == FLIFESUP:
2940         prout(_("Your life support reserves have run out, and"))
2941         prout(_("you die of thirst, starvation, and asphyxiation."))
2942         prout(_("Your starship is a derelict in space."))
2943     elif ifin == FNRG:
2944         prout(_("Your energy supply is exhausted."))
2945         skip(1)
2946         prout(_("Your starship is a derelict in space."))
2947     elif ifin == FBATTLE:
2948         prout(_("The %s has been destroyed in battle.") % crmshp())
2949         skip(1)
2950         prout(_("Dulce et decorum est pro patria mori."))
2951     elif ifin == FNEG3:
2952         prout(_("You have made three attempts to cross the negative energy"))
2953         prout(_("barrier which surrounds the galaxy."))
2954         skip(1)
2955         prout(_("Your navigation is abominable."))
2956         score()
2957     elif ifin == FNOVA:
2958         prout(_("Your starship has been destroyed by a nova."))
2959         prout(_("That was a great shot."))
2960         skip(1)
2961     elif ifin == FSNOVAED:
2962         prout(_("The %s has been fried by a supernova.") % crmshp())
2963         prout(_("...Not even cinders remain..."))
2964     elif ifin == FABANDN:
2965         prout(_("You have been captured by the Klingons. If you still"))
2966         prout(_("had a starbase to be returned to, you would have been"))
2967         prout(_("repatriated and given another chance. Since you have"))
2968         prout(_("no starbases, you will be mercilessly tortured to death."))
2969     elif ifin == FDILITHIUM:
2970         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2971     elif ifin == FMATERIALIZE:
2972         prout(_("Starbase was unable to re-materialize your starship."))
2973         prout(_("Sic transit gloria mundi"))
2974     elif ifin == FPHASER:
2975         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2976     elif ifin == FLOST:
2977         prout(_("You and your landing party have been"))
2978         prout(_("converted to energy, disipating through space."))
2979     elif ifin == FMINING:
2980         prout(_("You are left with your landing party on"))
2981         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2982         skip(1)
2983         prout(_("They are very fond of \"Captain Kirk\" soup."))
2984         skip(1)
2985         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2986     elif ifin == FDPLANET:
2987         prout(_("You and your mining party perish."))
2988         skip(1)
2989         prout(_("That was a great shot."))
2990         skip(1)
2991     elif ifin == FSSC:
2992         prout(_("The Galileo is instantly annihilated by the supernova."))
2993         prout(_("You and your mining party are atomized."))
2994         skip(1)
2995         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2996         prout(_("joins the Romulans, wreaking terror on the Federation."))
2997     elif ifin == FPNOVA:
2998         prout(_("You and your mining party are atomized."))
2999         skip(1)
3000         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3001         prout(_("joins the Romulans, wreaking terror on the Federation."))
3002     elif ifin == FSTRACTOR:
3003         prout(_("The shuttle craft Galileo is also caught,"))
3004         prout(_("and breaks up under the strain."))
3005         skip(1)
3006         prout(_("Your debris is scattered for millions of miles."))
3007         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3008     elif ifin == FDRAY:
3009         prout(_("The mutants attack and kill Spock."))
3010         prout(_("Your ship is captured by Klingons, and"))
3011         prout(_("your crew is put on display in a Klingon zoo."))
3012     elif ifin == FTRIBBLE:
3013         prout(_("Tribbles consume all remaining water,"))
3014         prout(_("food, and oxygen on your ship."))
3015         skip(1)
3016         prout(_("You die of thirst, starvation, and asphyxiation."))
3017         prout(_("Your starship is a derelict in space."))
3018     elif ifin == FHOLE:
3019         prout(_("Your ship is drawn to the center of the black hole."))
3020         prout(_("You are crushed into extremely dense matter."))
3021     elif ifin == FCREW:
3022         prout(_("Your last crew member has died."))
3023     if game.ship == 'F':
3024         game.ship = None
3025     elif game.ship == 'E':
3026         game.ship = 'F'
3027     game.alive = False
3028     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
3029         goodies = game.state.remres/game.inresor
3030         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3031         if goodies/baddies >= randreal(1.0, 1.5):
3032             prout(_("As a result of your actions, a treaty with the Klingon"))
3033             prout(_("Empire has been signed. The terms of the treaty are"))
3034             if goodies/baddies >= randreal(3.0):
3035                 prout(_("favorable to the Federation."))
3036                 skip(1)
3037                 prout(_("Congratulations!"))
3038             else:
3039                 prout(_("highly unfavorable to the Federation."))
3040         else:
3041             prout(_("The Federation will be destroyed."))
3042     else:
3043         prout(_("Since you took the last Klingon with you, you are a"))
3044         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3045         prout(_("statue in your memory. Rest in peace, and try not"))
3046         prout(_("to think about pigeons."))
3047         game.gamewon = True
3048     score()
3049
3050 def score():
3051     "Compute player's score."
3052     timused = game.state.date - game.indate
3053     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
3054         timused = 5.0
3055     game.perdate = killrate()
3056     ithperd = 500*game.perdate + 0.5
3057     iwon = 0
3058     if game.gamewon:
3059         iwon = 100*game.skill
3060     if game.ship == 'E':
3061         klship = 0
3062     elif game.ship == 'F':
3063         klship = 1
3064     else:
3065         klship = 2
3066     game.score = 10*(game.inkling - game.state.remkl) \
3067              + 50*(game.incom - len(game.state.kcmdr)) \
3068              + ithperd + iwon \
3069              + 20*(game.inrom - game.state.nromrem) \
3070              + 200*(game.inscom - game.state.nscrem) \
3071                  - game.state.nromrem \
3072              + 3 * game.kcaptured \
3073              - badpoints()
3074     if not game.alive:
3075         game.score -= 200
3076     skip(2)
3077     prout(_("Your score --"))
3078     if game.inrom - game.state.nromrem:
3079         prout(_("%6d Romulans destroyed                 %5d") %
3080               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3081     if game.state.nromrem and game.gamewon:
3082         prout(_("%6d Romulans captured                  %5d") %
3083               (game.state.nromrem, game.state.nromrem))
3084     if game.inkling - game.state.remkl:
3085         prout(_("%6d ordinary Klingons destroyed        %5d") %
3086               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3087     if game.incom - len(game.state.kcmdr):
3088         prout(_("%6d Klingon commanders destroyed       %5d") %
3089               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3090     if game.kcaptured:
3091         prout(_("%d Klingons captured                   %5d") %
3092               (game.kcaptured, 3 * game.kcaptured))
3093     if game.inscom - game.state.nscrem:
3094         prout(_("%6d Super-Commander destroyed          %5d") %
3095               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3096     if ithperd:
3097         prout(_("%6.2f Klingons per stardate              %5d") %
3098               (game.perdate, ithperd))
3099     if game.state.starkl:
3100         prout(_("%6d stars destroyed by your action     %5d") %
3101               (game.state.starkl, -5*game.state.starkl))
3102     if game.state.nplankl:
3103         prout(_("%6d planets destroyed by your action   %5d") %
3104               (game.state.nplankl, -10*game.state.nplankl))
3105     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3106         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3107               (game.state.nworldkl, -300*game.state.nworldkl))
3108     if game.state.basekl:
3109         prout(_("%6d bases destroyed by your action     %5d") %
3110               (game.state.basekl, -100*game.state.basekl))
3111     if game.nhelp:
3112         prout(_("%6d calls for help from starbase       %5d") %
3113               (game.nhelp, -45*game.nhelp))
3114     if game.casual:
3115         prout(_("%6d casualties incurred                %5d") %
3116               (game.casual, -game.casual))
3117     if game.abandoned:
3118         prout(_("%6d crew abandoned in space            %5d") %
3119               (game.abandoned, -3*game.abandoned))
3120     if klship:
3121         prout(_("%6d ship(s) lost or destroyed          %5d") %
3122               (klship, -100*klship))
3123     if not game.alive:
3124         prout(_("Penalty for getting yourself killed        -200"))
3125     if game.gamewon:
3126         proutn(_("Bonus for winning "))
3127         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3128         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3129         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3130         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3131         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3132         prout("           %5d" % iwon)
3133     skip(1)
3134     prout(_("TOTAL SCORE                               %5d") % game.score)
3135
3136 def plaque():
3137     "Emit winner's commemmorative plaque."
3138     skip(2)
3139     while True:
3140         proutn(_("File or device name for your plaque: "))
3141         winner = cgetline()
3142         try:
3143             fp = open(winner, "w")
3144             break
3145         except IOError:
3146             prout(_("Invalid name."))
3147
3148     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3149     winner = cgetline()
3150     # The 38 below must be 64 for 132-column paper
3151     nskip = 38 - len(winner)/2
3152     fp.write("\n\n\n\n")
3153     # --------DRAW ENTERPRISE PICTURE.
3154     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3155     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3156     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3157     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3158     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3159     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3160     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3161     fp.write("                                      EEE           E          E            E  E\n")
3162     fp.write("                                                       E         E          E  E\n")
3163     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3164     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3165     fp.write("                                                    :E    :                 EEEE       E\n")
3166     fp.write("                                                   .-E   -:-----                       E\n")
3167     fp.write("                                                    :E    :                            E\n")
3168     fp.write("                                                      EE  :                    EEEEEEEE\n")
3169     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3170     fp.write("\n\n\n")
3171     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3172     fp.write("\n\n\n\n")
3173     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3174     fp.write("\n")
3175     fp.write(_("                                                Starfleet Command bestows to you\n"))
3176     fp.write("\n")
3177     fp.write("%*s%s\n\n" % (nskip, "", winner))
3178     fp.write(_("                                                           the rank of\n\n"))
3179     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3180     fp.write("                                                          ")
3181     if game.skill ==  SKILL_EXPERT:
3182         fp.write(_(" Expert level\n\n"))
3183     elif game.skill == SKILL_EMERITUS:
3184         fp.write(_("Emeritus level\n\n"))
3185     else:
3186         fp.write(_(" Cheat level\n\n"))
3187     timestring = time.ctime()
3188     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3189              (timestring+4, timestring+20, timestring+11))
3190     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3191     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3192     fp.close()
3193
3194 # Code from io.c begins here
3195
3196 rows = linecount = 0        # for paging
3197 stdscr = None
3198 replayfp = None
3199 fullscreen_window = None
3200 srscan_window     = None   # Short range scan
3201 report_window     = None   # Report legends for status window
3202 status_window     = None   # The status window itself
3203 lrscan_window     = None   # Long range scan
3204 message_window    = None   # Main window for scrolling text
3205 prompt_window     = None   # Prompt window at bottom of display
3206 curwnd = None
3207
3208 def iostart():
3209     global stdscr, rows
3210     # for some recent versions of python2, the following enables UTF8
3211     # for the older ones we probably need to set C locale, and python3
3212     # has no problems at all
3213     if sys.version_info[0] < 3:
3214         locale.setlocale(locale.LC_ALL, "")
3215     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3216     gettext.textdomain("sst")
3217     if not (game.options & OPTION_CURSES):
3218         ln_env = os.getenv("LINES")
3219         if ln_env:
3220             rows = ln_env
3221         else:
3222             rows = 25
3223     else:
3224         stdscr = curses.initscr()
3225         stdscr.keypad(True)
3226         curses.nonl()
3227         curses.cbreak()
3228         if game.options & OPTION_COLOR:
3229             curses.start_color()
3230             curses.use_default_colors()
3231             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3232             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3233             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3234             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3235             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3236             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3237             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3238             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3239         global fullscreen_window, srscan_window, report_window, status_window
3240         global lrscan_window, message_window, prompt_window
3241         (rows, _columns)   = stdscr.getmaxyx()
3242         fullscreen_window = stdscr
3243         srscan_window     = curses.newwin(12, 25, 0,       0)
3244         report_window     = curses.newwin(11, 0,  1,       25)
3245         status_window     = curses.newwin(10, 0,  1,       39)
3246         lrscan_window     = curses.newwin(5,  0,  0,       64)
3247         message_window    = curses.newwin(0,  0,  12,      0)
3248         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3249         message_window.scrollok(True)
3250         setwnd(fullscreen_window)
3251
3252 def ioend():
3253     "Wrap up I/O."
3254     if game.options & OPTION_CURSES:
3255         stdscr.keypad(False)
3256         curses.echo()
3257         curses.nocbreak()
3258         curses.endwin()
3259
3260 def waitfor():
3261     "Wait for user action -- OK to do nothing if on a TTY"
3262     if game.options & OPTION_CURSES:
3263         stdscr.getch()
3264
3265 def announce():
3266     skip(1)
3267     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3268     skip(1)
3269
3270 def pause_game():
3271     if game.skill > SKILL_FAIR:
3272         prompt = _("[CONTINUE?]")
3273     else:
3274         prompt = _("[PRESS ENTER TO CONTINUE]")
3275
3276     if game.options & OPTION_CURSES:
3277         drawmaps(0)
3278         setwnd(prompt_window)
3279         prompt_window.clear()
3280         prompt_window.addstr(prompt)
3281         prompt_window.getstr()
3282         prompt_window.clear()
3283         prompt_window.refresh()
3284         setwnd(message_window)
3285     else:
3286         global linecount
3287         sys.stdout.write('\n')
3288         proutn(prompt)
3289         if not replayfp:
3290             input()
3291         sys.stdout.write('\n' * rows)
3292         linecount = 0
3293
3294 def skip(i):
3295     "Skip i lines.  Pause game if this would cause a scrolling event."
3296     for _dummy in range(i):
3297         if game.options & OPTION_CURSES:
3298             (y, _x) = curwnd.getyx()
3299             try:
3300                 curwnd.move(y+1, 0)
3301             except curses.error:
3302                 pass
3303         else:
3304             global linecount
3305             linecount += 1
3306             if rows and linecount >= rows:
3307                 pause_game()
3308             else:
3309                 sys.stdout.write('\n')