X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?p=ypp-sc-tools.db-live.git;a=blobdiff_plain;f=yoweb-scrape;h=d2d194c213e9039b941a1465c464239654ca14ae;hp=ed9533ef51c513b4628673712880bc9a0b0b1e3c;hb=9cec9bede40ddd31329097b6952d7a72ec005bbc;hpb=e9615860253eb8bfdb50d80fbea58002bc350c5b diff --git a/yoweb-scrape b/yoweb-scrape index ed9533e..d2d194c 100755 --- a/yoweb-scrape +++ b/yoweb-scrape @@ -14,6 +14,7 @@ import sys import re as regexp import random import curses +import termios from optparse import OptionParser from BeautifulSoup import BeautifulSoup @@ -27,8 +28,20 @@ puzzles = ('Swordfighting/Bilging/Sailing/Rigging/Navigating'+ '/Drinking/Spades/Hearts/Treasure Drop/Poker/Distilling'+ '/Alchemistry/Shipwrightery/Blacksmithing/Foraging').split('/') +core_duty_puzzles = [ + 'Gunning', + ['Sailing','Rigging'], + 'Bilging', + 'Carpentry', + ] + +duty_puzzles = ([ 'Navigating', 'Battle Navigation' ] + + core_duty_puzzles + + [ 'Treasure Haul' ]) + standingvals = ('Able/Distinguished/Respected/Master'+ '/Renowned/Grand-Master/Legendary/Ultimate').split('/') +standing_limit = len(standingvals) pirate_ref_re = regexp.compile('^/yoweb/pirate\\.wm') @@ -41,9 +54,12 @@ def debug(m): if opts.debug > 0: print >>opts.debug_file, m -def sleep(seconds): +def debug_flush(): if opts.debug > 0: - opts.debug_file.flush() + opts.debug_file.flush() + +def sleep(seconds): + debug_flush() time.sleep(seconds) def format_time_interval(ti): @@ -89,8 +105,9 @@ class Fetcher: ages.append(age) return ages - def need_wait(self, now): + def need_wait(self, now, imaginary=[]): ages = self._cache_scan(now) + ages += imaginary ages.sort() debug('Fetcher ages ' + `ages`) min_age = 1 @@ -247,7 +264,7 @@ u'\\s*\\S*/([-A-Za-z]+)\\s*$|\\s*\\S*/\\S*\\s*\\(ocean\\-wide(?:\\s|\\xa0)+([-A- skl.msg('puzzle "%s" no standing found' % puzzle) continue standing = sl[0] - for i in range(0, len(standingvals)-1): + for i in range(0, standing_limit): if standing == standingvals[i]: self.standings[puzzle] = i if not puzzle in self.standings: @@ -334,14 +351,7 @@ class StandingsTable: def __init__(self, use_puzzles=None, col_width=6): if use_puzzles is None: if opts.ship_duty: - use_puzzles=[ - 'Navigating','Battle Navigation', - 'Gunning', - ['Sailing','Rigging'], - 'Bilging', - 'Carpentry', - 'Treasure Haul' - ] + use_puzzles=duty_puzzles else: use_puzzles=puzzles self._puzzles = use_puzzles @@ -417,8 +427,23 @@ class PirateAboard: pa.pi = None def pirate_info(pa): - if not pa.pi and not fetcher.need_wait(time.time()): - pa.pi = PirateInfo(pa.name, 3600) + now = time.time() + if pa.pi: + age = now - pa.pi_fetched + guide = random.randint(120,240) + if age <= guide: + return pa.pi + debug('PirateAboard refresh %d > %d %s' % ( + age, guide, pa.name)) + imaginary = [2,6] + else: + imaginary = [1] + wait = fetcher.need_wait(now, imaginary) + if wait: + debug('PirateAboard fetcher not ready %d' % wait) + return pa.pi + pa.pi = PirateInfo(pa.name, 600) + pa.pi_fetched = now return pa.pi class ChatLogTracker: @@ -521,7 +546,7 @@ class ChatLogTracker: if pattern is None: pattern_check = lambda vn: True else: - re = '(?:.* )?%s$' % pattern.lower().replace('*','.*') + re = '(?:.* )?%s$' % pattern.lower().replace('*','.+') pattern_check = regexp.compile(re, regexp.I).match tries = [] @@ -819,6 +844,8 @@ class ChatLogTracker: # sorted by pirate name # you can pass this None and you'll get [] # or True for the current vessel (which is the default) + # the returned value is a fresh list of persistent + # PirateAboard objects if vesselname is True: v = self._v else: v = self._vl.get(vesselname.title()) if v is None: return [] @@ -884,9 +911,9 @@ def prep_chat_log(args, bu, if not match: bu('chat log filename is not in expected format') (pirate, ocean) = match.groups() fetcher.default_ocean(ocean) - - myself = PirateInfo(pirate,max_myself_age) + progress.show_init(pirate, fetcher.ocean) + myself = PirateInfo(pirate,max_myself_age) track = ChatLogTracker(myself, logfn) opts.debug -= 2 @@ -973,40 +1000,91 @@ def do_ship_aid(args, bu): if opts.ship_duty is None: opts.ship_duty = True displayer = globals()['Display_'+opts.display]() - rotate_nya = '/-\\' (myself, track) = prep_chat_log(args, bu, progress=displayer) + displayer.realstart() + + if os.isatty(0): kr_create = KeystrokeReader + else: kr_create = DummyKeystrokeReader + + try: + kreader = kr_create(0, 10) + ship_aid_core(myself, track, displayer, kreader) + finally: + kreader.stop() + print '\n' + +class KeyBasedSorter: + def compar_key_pa(self, pa): + return self.compar_key(pa.pirate_info()) + def lsort_pa(self, l): + l.sort(key = self.compar_key_pa) + +class NameSorter(KeyBasedSorter): + def compar_key(self, pi): return pi.name + +class SkillSorter(NameSorter): + def __init__(self, relevant): + self._want = frozenset(relevant.split('/')) + self._avoid = set() + for p in core_duty_puzzles: + if isinstance(p,basestring): self._avoid.add(p) + else: self._avoid |= set(p) + self._avoid -= self._want + + def compar_key(self, pi): + best_want = max([ + pi.standings.get(puz,-1) + for puz in self._want + ]) + best_avoid = [ + -pi.standings.get(puz,standing_limit) + for puz in self._avoid + ] + best_avoid.sort() + def negate(x): return -x + debug('compar_key %s bw=%s ba=%s' % (pi.name, `best_want`, + `best_avoid`)) + return (-best_want, map(negate, best_avoid), pi.name) + +def ship_aid_core(myself, track, displayer, kreader): + + def find_vessel(): + vn = track.vesselname() + if vn: return (vn, " on board the %s" % vn) + vn = track.lastvesselname() + if vn: return (vn, " ashore from the %s" % vn) + return (None, " not on a vessel") + def timeevent(t,e): if t is None: return ' ' * 22 return " %-4s %-16s" % (format_time_interval(now - t),e) - displayer.realstart() + displayer.show(track.myname() + find_vessel()[1] + '...') + + rotate_nya = '/-\\' + + sort = NameSorter() while True: track.catchup() now = time.time() - s = "%s" % track.myname() - - vn = track.vesselname() - if vn is not None: - s += " on board the %s" % vn - - if vn is None: - vn = track.lastvesselname() - if vn is not None: - s += " ashore from the %s" % vn - - if vn is None: - s += " not on a vessel" - - s += " at %s\n" % time.strftime("%Y-%m-%d %H:%M:%S") + (vn, s) = find_vessel() + s = track.myname() + s + s += " at %s" % time.strftime("%Y-%m-%d %H:%M:%S") + s += kreader.info() + s += '\n' tbl = StandingsTable() tbl.headings() - for pa in track.aboard(): + aboard = track.aboard(vn) + + sort.lsort_pa(aboard) + + for pa in aboard: pi = pa.pirate_info() xs = '' @@ -1021,10 +1099,51 @@ def do_ship_aid(args, bu): tbl.pirate(pi, xs) s += tbl.results() - displayer.show(s) - sleep(1) - rotate_nya = rotate_nya[1:3] + rotate_nya[0] + + k = kreader.getch() + if k is None: + rotate_nya = rotate_nya[1:3] + rotate_nya[0] + continue + + if k == 'q': break + elif k == 'g': sort = SkillSorter('Gunning') + elif k == 'c': sort = SkillSorter('Carpentry') + elif k == 's': sort = SkillSorter('Sailing/Rigging') + elif k == 'b': sort = SkillSorter('Bilging') + elif k == 'n': sort = SkillSorter('Navigating') + elif k == 'd': sort = SkillSorter('Battle Navigation') + elif k == 't': sort = SkillSorter('Treasure Haul') + elif k == 'a': sort = NameSorter() + else: pass # unknown key command + +#---------- individual keystroke input ---------- + +class DummyKeystrokeReader: + def __init__(self,fd,timeout_dummy): pass + def stop(self): pass + def getch(self): sleep(1); return None + def info(self): return ' [noninteractive]' + +class KeystrokeReader(DummyKeystrokeReader): + def __init__(self, fd, timeout_decisec=0): + self._fd = fd + self._saved = termios.tcgetattr(fd) + a = termios.tcgetattr(fd) + a[3] &= ~(termios.ECHO | termios.ECHONL | + termios.ICANON | termios.IEXTEN) + a[6][termios.VMIN] = 0 + a[6][termios.VTIME] = timeout_decisec + termios.tcsetattr(fd, termios.TCSANOW, a) + def stop(self): + termios.tcsetattr(self._fd, termios.TCSANOW, self._saved) + def getch(self): + debug_flush() + byte = os.read(self._fd, 1) + if not len(byte): return None + return byte + def info(self): + return '' #---------- main program ----------