#!/usr/bin/ruby require 'libhttpclient' require 'libhtml' require 'mountycreds' class Event attr_reader :date, :type, :str, :owner def initialize(owner) @owner = owner @str = '' end def date=(str) raise 'invalid date' unless str =~ %r{(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+)} @date = Time.mktime($3, $2, $1, $4, $5, $6) end def type=(str) @type = str end def actor $1.to_i if str =~ /\(\s*(\d+)\s*\)/ end def to_s @date.strftime('%d/%m/%y %H:%M:%S ') + @type.ljust(14) + @str end end class EventList @@local_cache_duration = 3600 @@local_cache_base = '.mh_cache/tmp/events_' def initialize @httpserver = HttpClient.new('games.mountyhall.com') end def get_events_monstre(id, cat=nil) @curid = id.to_i id = id.to_s if cat == :any if d = Dir[@@local_cache_base + "monstre_#{id}*"].sort_by { |f| File.stat(f).mtime }.last cat = d[/monstre_#{id}(.*)$/, 1] else cat = nil end end cachefile = @@local_cache_base + "monstre_#{id}#{cat}" if not ARGV.include?('refresh') and File.exists?(cachefile) and Time.now.to_i - File.stat(cachefile).mtime.to_i < @@local_cache_duration hr = HttpResp.new hr.parse = parsehtml(File.read(cachefile)) return hr end req = '/mountyhall/View/MonsterView.php?ai_IDPJ='+id req << "&as_EventType=#{cat}" if cat hr = @httpserver.get req File.open(cachefile, 'w') { |fd| fd.write hr.content } hr end def get_events_troll(id, cat=nil) @curid = id.to_i id = id.to_s cachefile = @@local_cache_base + "troll_#{id}#{cat}" if not ARGV.include?('refresh') and File.exists?(cachefile) and Time.now.to_i - File.stat(cachefile).mtime.to_i < @@local_cache_duration hr = HttpResp.new hr.parse = parsehtml(File.read(cachefile)) return hr end req = '/mountyhall/View/PJView_Events.php?ai_IDPJ='+id req << "&as_EventType=#{cat}" if cat hr = @httpserver.get(req) File.open(cachefile, 'w') { |fd| fd.write hr.content } hr end def parse(page) ret = [] cur = nil evttable = 28 page.each_table { |pos, el| pos = pos[0] next unless pos and el.type == 'String' # dynammically find the table index corresponding to the events (3 for monters, 4 for trolls) evttable = pos[0] if pos[1] == 1 and pos[2] == 1 and el['content'] == 'Date' next unless pos[0] == evttable and pos[1] > 1 s = el['content'] case pos[2] when 1 ret << cur if cur cur = Event.new(@curid) cur.date = s when 2 cur.type = s when 3 cur.str << s.gsub("\xb4", "'") end } ret << cur end def event_list(id, type=nil) type ||= (id.to_i >= 100000 ? 'monstre' : 'troll') hr = send('get_events_'+type, id) parse hr end def event_list_dla(id, type=nil) type ||= (id.to_i >= 100000 ? 'monstre' : 'troll') hr1 = send('get_events_'+type, id, 'COMBAT') hr2 = send('get_events_'+type, id, 'MORT') parse(hr1).compact + parse(hr2).compact end end class Symbol ; def to_proc() proc { |obj, *args| obj.send(self, *args) } end end class Float ; def to_t() '%2dh%02d' % [self/3600, self/60 % 60].map(&:to_i) end end def show_user(events) puts events.find_all { |ev| ev.actor == ev.owner and ev.type =~ /COMBAT|MORT/ } end def calc_dla_corr(diff) return 100 if diff.empty? mins = diff.find_all { |d| d < diff.min * 1.6 } med = mins.inject { |a, b| a + b } / mins.length corr = diff.map { |d| ndla = (d / med).round val = d / ndla (val - med) / med } corr.map { |c| c ** 2 }.inject { |a, b| a+b } end def calc_dla(events) # dates des attaques du sujet en order decroissant dates = events.map { |ev| ev.date if ev.actor == ev.owner and ev.type =~ /COMBAT|MORT/ }.compact # liste des differences entre les dates d'attaques en secondes, # apres merge des attaques proches (10s) lastdate = dates.first fdates = dates.inject([lastdate]) { |d, cur| d << cur if d.last - cur > 10 ; d } fdiff = fdates.inject([]) { |diff, cur| diff << (lastdate - (lastdate=cur)) } fdiff.shift diff = fdiff.reverse if diff.empty? puts "pas suffisamment d'evenements !" return end med = 0 fcorr = calc_dla_corr diff scorr = calc_dla_corr diff[1..-1] diff.shift if fcorr > scorr and diff.length > 2 # now assume that the minimal difference is an approximation of the dla # try to find all consecutive dlas accordingly mins = diff.find_all { |d| d < diff.min * 1.6 } # mean of those med = mins.inject { |a, b| a + b } / mins.length dlas = diff.map { |d| ndla = (d / med).round val = d / ndla } med = dlas.inject { |a, b| a + b } / dlas.length corell = diff.map { |d| ndla = (d / med).round val = d / ndla (val - med).abs / med } bla = fdiff.find_all { |d| d < 15*3600 } bla = bla.inject { |a, b| a+b } / bla.length puts "Moyenne des diff courts: #{bla.to_t}" blo = med * (bla / med).round med = bla if (1-bla/blo).abs < 0.15 puts "found: #{med.to_t} [#{corell.map { |c| '%1.2f' % c } * ', '}]" fdates.zip([0]+fdiff).reverse_each { |da, di| puts da.strftime("%d/%m %Hh%M " << ((di > 5) ? " + #{di.to_t} (#{di>med ? '+' : '-'}#{(di>med ? di-med : med-di).to_t})" : '')) } na = dates[1] # next-to-most recent na = dates[0] if (dates[1] + med).hour > 7 na += med while na < Time.now - 1800 puts " next attacks:", [na, na + med].map { |d| d.strftime('%d/%m %Hh%M') } end if $0 == __FILE__ reader = EventList.new myid = mountycreds.first type = nil show = nil ARGV << myid if ARGV.empty? ARGV.each { |id| case id when 'show' show = true when 'monstre', 'troll' type = id when /^[0-9]+$/ if ARGV.include? 'dla' el = reader.event_list_dla(id, type) calc_dla(el) else if show el = reader.event_list_dla(id, type) show_user el.reverse else el = reader.event_list(id, type) puts el.reverse end end puts end } end