X-Git-Url: https://jxself.org/git/?p=super-star-trek.git;a=blobdiff_plain;f=src%2Fsst.py;h=4f7b6e4d315796e8be7887dbe0a216684caaf962;hp=4d98a47953ac9bae432e05f13c233a093acfb002;hb=d9f9e925bac113fd20f774d2e33b70e8836c9a7d;hpb=c2847bb7511b0cf8472ba3a0a170a4fedd3b2861 diff --git a/src/sst.py b/src/sst.py index 4d98a47..4f7b6e4 100644 --- a/src/sst.py +++ b/src/sst.py @@ -15,21 +15,8 @@ MAXKLGAME = 127 MAXKLQUAD = 9 FOREVER = 1e30 -# Feature vakues -IHR = 'R', -IHK = 'K', -IHC = 'C', -IHS = 'S', -IHSTAR = '*', -IHP = 'P', -IHW = '@', -IHB = 'B', -IHBLANK = ' ', -IHDOT = '.', +# These types have not been dealt with yet IHQUEST = '?', -IHE = 'E', -IHF = 'F', -IHT = 'T', IHWEB = '#', IHMATER0 = '-', IHMATER1 = 'o', @@ -45,15 +32,97 @@ class coord: return self.x != None and self.y != None def __eq__(self, other): return self.x == other.y and self.x == other.y + def __add__(self, other): + return coord(self.x+self.x, self.y+self.y) + def __sub__(self, other): + return coord(self.x-self.x, self.y-self.y) def distance(self, other): return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2) + def sgn(self): + return coord(self.x / abs(x), self.y / abs(y)); + def __hash__(self): + return hash((x, y)) + def __str__(self): + return "%d - %d" % (self.x, self.y) -class planet: +class feature: + "A feature in the current quadrant (ship, star, black hole, etc)." + def __init__(self): + self.type = None # name of feature type + self.location = None # location + def distance(self): + return self.location.distance(game.sector) + def __str__(self): + return self.name[0] + +empty = None # Value of empty space in game.quad + +class ship(feature): + "An enemy ship in the current quadrant." + def __init__(self): + feature.__init__(self) + self.type = None # klingon, romulan, commander, + # supercommander, tholian + self.power = None # power + if self.type in ("Klingon", "Commander", "Super-Commander"): + game.remkl += 1 + elif self.type == "Romulan": + game.romrem += 1 + def __del__(self): + if self.type in ("Klingon", "Commander", "Super-Commander"): + game.remkl -= 1 + elif self.type == "Romulan": + game.romrem -= 1 + def sectormove(self, dest): + "Move this ship within the current quadrant." + if self.location: + game.quad[self.location] = None + game.quad[dest] = self + self.location = dest + +class planet(feature): + "A planet. May be inhabited or not, may hold dilithium crystals or not." def __init(self): - self.w = coord() + feature.__init__(self) self.name = None self.crystals = None # "absent", "present", or "mined" + self.inhabited = False self.known = "unknown" # Other values: "known" and "shuttle down" + def __str__(self): + if self.inhabited: + return '@' + else: + return 'P' + +class star(feature): + "A star. Has no state, just knows how to identify iself." + def __init(self): + feature.__init__(self) + def __str__(self): + return '*' + +class web(feature): + "A bit of Tholian web. Has no state, just knows how to identify iself." + def __init(self): + feature.__init__(self) + def __str__(self): + return '*' + +class blackhole(feature): + "A black hole. Has no hair, just knows how to identify iself." + def __init(self): + feature.__init__(self) + def __str__(self): + return '*' + +class starbase(feature): + "Starbases also have no features." + def __init(self): + feature.__init__(self) + def __del__(self): + game.state.bases.remove(self.location) + def __str__(self): + return 'B' class quadrant: def __init__(self): @@ -65,6 +134,15 @@ class quadrant: self.supernova = None self.charted = None self.status = "secure" # Other valuues: "distressed", "enslaved" + def enemies(self): + "List enemies in this quadrant." + lst = [] + for feature in self.quad.values: + if not isinstance(feature, ship): + continue + if feature.name not in ("Enterprise", "Faerie Queene"): + lst.append(feature) + return lst class page: "A chart page. The starchart is a 2D array of these." @@ -116,7 +194,7 @@ class game: self.future = [] # future events self.passwd = None # Self Destruct password # Coordinate members start here - self.ks = {} # enemy sector locations + self.enemies = {} # enemy sector locations self.quadrant = None # where we are self.sector = None self.tholian = None # coordinates of Tholian @@ -170,7 +248,6 @@ class game: self.nkinks = None # count of energy-barrier crossings self.iplnet = None # planet # in quadrant self.inplan = None # initial planets - self.nenhere = None # number of enemies in quadrant self.irhere = None # Romulans in quadrant self.isatb = None # =1 if super commander is attacking base self.tourn = None # tournament number @@ -201,4 +278,463 @@ class game: self.probeiny = None # self.height = None # height of orbit around planet +def communicating(): + "Are we in communication with Starfleet Command?" + return (not damaged("DRADIO")) or game.condition == docked + +# Code corresponding to ai.c begins here + +def tryexit(look, ship, irun): + # a bad guy attempts to bug out of the quadrant + iq = coord() + iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1 + iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1 + if not valid_quadrant(iq) or \ + game.state.galaxy[iq].supernova or \ + game.state.galaxy[iq].klingons > 8: + return False; # no can do -- neg energy, supernovae, or >8 Klingons + if ship.type == "Romulan": + return False # Romulans cannot escape + if not irun: + # avoid intruding on another commander's territory + if ship.type == "Commander": + if iq in gamestate.kcmdr: + return False + # refuse to leave if currently attacking starbase: + if game.battle == game.quadrant: + return False; + # don't leave if over 1000 units of energy + if ship.power > 1000.0: + return false; + # Print escape message and move out of quadrant. + # We know this if either short or long range sensors are working + if not damaged("DSRSENS") or not damaged("DLRSENS") or game.condition=="docked": + crmena(True, "sector", ship) + prout(" escapes to quadrant %s (and regains strength)." % iq) + # handle local matters related to escape + game.quad[ship.location] = None; + if game.condition != "docked": + newcnd() + # Handle global matters related to escape + game.state.galaxy[game.quadrant].klingons -= 1 + game.state.galaxy[iq].klingons += 1 + if ship.type == "Super-Commander": + game.ishere = False + game.iscate = False + game.ientesc = False + game.isatb = 0 + schedule("FSCMOVE", 0.2777) + unschedule("FSCDBAS") + game.state.kscmdr = iq + else: + for (n, cmdr) in enumerate(game.state.kcmdr): + if cmdr == game.quadrant: + game.state.kcmdr[n] = iq + break + game.comhere = False + return True # successful exit + +def sgn(n): n / abs(n) + +''' +Algorithm for moving bad guys: + + * Enterprise has "force" based on condition of phaser and photon torpedoes. + If both are operating full strength, force is 1000. If both are damaged, + force is -1000. Having shields down subtracts an additional 1000. + + * Enemy has forces equal to the energy of the attacker plus + 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR + 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus. + + Attacker Initial energy levels (nominal): + Klingon Romulan Commander Super-Commander + Novice 400 700 1200 + Fair 425 750 1250 + Good 450 800 1300 1750 + Expert 475 850 1350 1875 + Emeritus 500 900 1400 2000 + VARIANCE 75 200 200 200 + + Enemy vessels only move prior to their attack. In Novice - Good games + only commanders move. In Expert games, all enemy vessels move if there + is a commander present. In Emeritus games all enemy vessels move. + + * If Enterprise is not docked, an agressive action is taken if enemy + forces are 1000 greater than Enterprise. + + Agressive action on average cuts the distance between the ship and + the enemy to 1/4 the original. + + * At lower energy advantage, movement units are proportional to the + advantage with a 650 advantage being to hold ground, 800 to move forward + 1, 950 for two, 150 for back 4, etc. Variance of 100. + + If docked, is reduced by roughly 1.75*game.skill, generally forcing a + retreat, especially at high skill levels. + + * Motion is limited to skill level, except for SC hi-tailing it out. +''' + +def movebaddy(ship): + # tactical movement for the bad guys + bugout = False + # This should probably be just game.comhere + game.ishere + if game.skill >= SKILL_EXPERT: + nbaddys = int((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0) + else: + nbaddys = game.comhere + game.ishere + dist1 = ship.distance() + mdist = round(dist1 + 0.5) # Nearest integer distance + # If SC, check with spy to see if should high-tail it + if ship.type == "Super-Commander" and \ + (ship.power <= 500.0 or (game.condition==docked and not damaged("DPHOTON"))): + bugout = True; + motion = -QUADSIZE; + else: + # decide whether to advance, retreat, or hold position + forces = ship.power + 100.0*len(game.quad.enemies()) + 400*(nbaddys-1) + if not game.shldup: + forces += 1000.0 # Good for enemy if shield is down! + if not damaged("DPHASER") or not damaged("DPHOTON"): + if damaged(DPHASER): + forces += 300.0 + else: + forces -= 0.2*(game.energy - 2500.0); + if damaged("DPHOTON"): + forces += 300.0 + else: + forces -= 50.0*game.torps + else: + # phasers and photon tubes both out! + forces += 1000.0 + motion = 0; + if forces <= 1000.0 and game.condition != "docked": # Typical case + motion = ((forces+200.0*Rand())/150.0) - 5.0 + else: + if forces > 1000.0: # Very strong -- move in for kill + motion = (1.0-square(Rand()))*dist1 + 1.0 + if game.condition == "docked" and "base" in game.options: + # protected by base -- back off ! + motion -= game.skill * (2.0-Rand()**2) + if idebug: + proutn("=== MOTION = %1.2f, FORCES = %1.2f, ", motion, forces) + # don't move if no motion + if motion == 0: + return + # Limit motion according to skill + if abs(motion) > game.skill: + if motion < 0: + motion = -game.kill + else: + motion = game.skill + # calculate preferred number of steps + nsteps = abs(motion) + if motion > 0 and nsteps > mdist: # don't overshoot + nsteps = mdist + if nsteps > QUADSIZE: # This shouldn't be necessary + nsteps = QUADSIZE + if nsteps < 1: # This shouldn't be necessary + nsteps = 1 + if idebug: + proutn("NSTEPS = %d:", nsteps) + # Compute preferred values of delta X and Y + me = game.sector - com; + if 2.0 * abs(me.x) < abs(me.y): + me.x = 0; + if 2.0 * abs(me.y) < abs(game.sector.x-com.x): + me.y = 0; + if me.x != 0: me.x = sgn(me.x*motion) + if me.y != 0: me.y = sgn(me.y*motion) + next = com; + # main move loop + for ll in range(nsteps): + if idebug: + proutn(" %d", ll+1) + # Check if preferred position available + look = next + me + krawl = me.sgn() + success = False + attempts = 0 # Settle meysterious hang problem + while attempts < 20 and not success: + attempts += 1 + if look.x < 1 or look.x > QUADSIZE: + if motion < 0 and tryexit(look, ship, bugout): + return + if krawl.x == me.x or me.y == 0: + break + look.x = next.x + krawl.x + krawl.x = -krawl.x + elif look.y < 1 or look.y > QUADSIZE: + if motion < 0 and tryexit(look, ship, bugout): + return + if krawl.y == me.y or me.x == 0: + break + look.y = next.y + krawl.y + krawl.y = -krawl.y + elif "ramming" in game.options and game.quad[look] != IHDOT: + # See if we should ram ship + if game.quad[look] == game.ship and ienm in (IHC, IHS): + ram(true, ienm, com) + return + if krawl.x != me.x and me.y != 0: + look.x = next.x + krawlx + krawl.x = -krawl.x + elif krawly != me.y and me.x != 0: + look.y = next.y + krawly + krawl.y = -krawl.y + else: + break # we have failed + else: + success = True + if success: + next = look + if idebug: + proutn(str(next)) + else: + break # done early + if idebug: + prout("") + # Put ship in place within same quadrant + if next != ship.location: + # it moved + if not damaged("DSRSENS") or game.condition == "docked": + proutn("*** %s from sector %s" % (ship, ship.location)) + if ship.distance() < dist1: + prout(" advances to sector %s" % ship.location) + else: + proutn(" retreats to sector %s" % ship.location) + ship.sectormove(next) + +def movcom(): + "Allow enemies to move." + for enemy in self.quad.enemies(): + if enemy.type == "Commander": + movebaddy(enemy) + break + for enemy in self.quad.enemies(): + if enemy.type == "Super-Commander": + movebaddy(enemy) + break + # Ff skill level is high, move other Klingons and Romulans too! + # Move these last so they can base their actions on what the + # commander(s) do. + if game.skill >= SKILL_EXPERT and "movebaddy" in game.options: + for enemy in self.quad.enemies(): + if enemy.type in ("Klingon", "Romulan"): + movebaddy(enemy) + break + +def movescom(ship, avoid): + # commander movement helper + global ipage + if game.state.kscmdr == game.quadrant or \ + game.state.galaxy[iq].supernova or \ + game.state.galaxy[iq].klingons > 8: + return True + if avoid: + # Avoid quadrants with bases if we want to avoid Enterprise + for base in game.state.starbases: + if base.location == ship.location: + return True + if game.justin and not game.iscate: + return True + # Super-Commander has scooted, Remove him from current quadrant. + if game.state.kscmdr == game.quadrant: + game.iscate = False + game.isatb = 0 + game.ientesc = False + unschedule("FSCDBAS") + if game.condition != "docked": + newcnd() + ship.sectormove(None) + # do the actual move + game.state.galaxy[game.state.kscmdr].klingons -= 1 + game.state.kscmdr = iq + game.state.galaxy[game.state.kscmdr].klingons += 1 + # check for a helpful planet in the destination quadrant + for planet in game.state.plnets: + if planet.location == game.state.kscmdr and planet.crystals=="present": + # destroy the planet + game.state.plnets.remove(planet) + if communicating(): + if not ipage: + pause_game(True) + ipage = true + prout("Lt. Uhura- \"Captain, Starfleet Intelligence reports") + proutn(_(" a planet in ")) + proutn(cramlc(quadrant, game.state.kscmdr)) + prout(" has been destroyed") + prout(" by the Super-commander.\"") + break + return False # looks good! + +def scom(): + # move the Super Commander + if (idebug): + prout("== SCOM") + + # Decide on being active or passive + passive = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) \ + or (game.state.date-game.indate) < 3.0) + if not game.iscate and passive: + # compute move away from Enterprise + idelta = game.state.kscmdr - game.quadrant + if distance(game.state.kscmdr) > 2.0: + # circulate in space + idelta,x = game.state.kscmdr.y-game.quadrant.y + idelta,y = game.quadrant.x-game.state.kscmdr.x + else: + if len(game.state.bases): + unschedule("FSCMOVE") + return + sc = game.state.kscmdr + # compute distances to starbases + game.starbases.sort(lambda x, y: cmp(distance(x, game.quadrant), distance(y, game.quadrant))) + # look for nearest base without a commander, no Enterprise, and + # without too many Klingons, and not already under attack. + nearest = filter(game.starbases, + lambda x: game.state.galaxy[x].supernova \ + and game.state.galaxy[x].klingons <= 8) + if game.quadrant in nearest: + nearest.remove(game.quadrant) + if game.battle in nearest: + nearest.remove(game.battle) + # if there is a commander, and no other base is appropriate, + # we will take the one with the commander + nocmd = filter(lambda x: x.location not in game.state.kcmdr, nearest) + if len(nocmd): + nearest = nocmd + ibq = nearest[0] + if len(nearest) == 0: + return # Nothing suitable -- wait until next time + # decide how to move toward base + idelta = ibq - game.state.kscmdr + # maximum movement is 1 quadrant in either or both axis + delta = delta.sgn() + # try moving in both x and y directions + iq = game.state.kscmdr + idelta + if movescom(iq, passive): + # failed -- try some other maneuvers + if ideltax==0 or ideltay==0: + # attempt angle move + if ideltax != 0: + iq.y = game.state.kscmdr.y + 1 + if movescom(iq, passive): + iq.y = game.state.kscmdr.y - 1 + movescom(iq, passive) + else: + iq.x = game.state.kscmdr.x + 1 + if movescom(iq, passive): + iq.x = game.state.kscmdr.x - 1 + movescom(iq, passive) + else: + # try moving just in x or y + iq.y = game.state.kscmdr.y + if movescom(iq, passive): + iq.y = game.state.kscmdr.y + ideltay + iq.x = game.state.kscmdr.x + movescom(iq, passive) + # check for a base + if game.state.rembase == 0: + unschedule("FSCMOVE") + else: + for ibq in game.bases: + if ibq == game.state.kscmdr and game.state.kscmdr == game.battle: + # attack the base + if passive: + return # no, don't attack base! + game.iseenit = false + game.isatb = 1 + schedule("FSCDBAS", 1.0 +2.0*Rand()) + if is_scheduled("FCDBAS"): + postpone("FSCDBAS", scheduled("FCDBAS")-game.state.date) + if not communicating(): + return # no warning + game.iseenit = True + if not ipage: + pause_game(true) + ipage = True + proutn(_("Lt. Uhura- \"Captain, the starbase in ")) + proutn(cramlc(quadrant, game.state.kscmdr)) + skip(1) + prout(" reports that it is under attack from the Klingon Super-commander.") + proutn(" It can survive until stardate %d.\"", + int(scheduled(FSCDBAS))) + if not game.resting: + return + prout("Mr. Spock- \"Captain, shall we cancel the rest period?\"") + if ja() == false: + return + game.resting = False + game.optime = 0.0 # actually finished + return + # Check for intelligence report + if (Rand() > 0.2 or not communicating() or + not game.state.galaxy[game.state.kscmdr].charted): + return + if ipage: + pause_game(true) + ipage = true + prout(_("Lt. Uhura- \"Captain, Starfleet Intelligence reports")) + proutn(_(" the Super-commander is in ")) + proutn(cramlc(quadrant, game.state.kscmdr)) + prout(".\"") + return + +def movetho(void): + "Move the Tholian (an instance of ship type pointed at by game.tholian)." + if not game.tholian or game.justin: + return + next = coord() + if game.tholian.location.x == 1 and game.tholian.location.y == 1: + next.x = 1 + next.y = QUADSIZE + elif game.tholian.location.x == 1 and game.tholian.location.y == QUADSIZE: + next.x = next.y = QUADSIZE + elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == QUADSIZE: + next.x = QUADSIZE + next.y = 1 + elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == 1: + next.x = next.y = 1 + else: + # something is wrong! + game.tholian = None + return + # Do nothing if we are blocked + if game.quad[next] != empty and not isinstance(game.quad[next]. web): + return + # Now place some web + im = (next - game.tholian.location).sgn() + if game.tholian.x != next.x: + # move in x axis + while game.tholian.location.x != next.x: + game.tholian.location.x += im.x + if game.quad[game.tholian.location] == empty: + game.quad[game.tholian.location] = web() + elif game.tholian.y != next.y: + # move in y axis + while game.tholian.y != next.y: + game.tholian.y += im.y + if game.quad[game.tholian.location] == empty: + game.quad[game.tholian.location] = web() + # web is done, move ship + game.tholian.movesector(next) + # check to see if all holes plugged + for i in range(1, QUADSIZE+1): + if (not isinstance(game.quad[(1,i)],web)) and game.quad[(1,i)]!=game.tholian: + return + if (not isinstance(game.quad[(QUADSIZE,i)],web)) and game.quad[(QUADSIZE,i)]!=game.tholian: + return + if (not isinstance(game.quad[(i,1)],web)) and game.quad[(i,1)]!=game.tholian: + return + if (not isinstance(game.quad[(i.QUADSIZE)],web)) and game.quad[(i,QUADSIZE)]!=game.tholian: + return + # All plugged up -- Tholian splits + game.quad[game.tholian] = web() + ship.movesector(None) + crmena(True, IHT, sector, game.tholian) + prout(" completes web.") + game.tholian = None + return