Diablo 2 Fun
Just some fraps footage I had on my HDD taking up space
I follow Umpa around as he kills Lilith, Uber Izual, Uber Duriel, Uber Mephisto, Uber Baal and finally Pandemonium Diablo.
Recorded 2nd July 2011
US West Ladder
Just some fraps footage I had on my HDD taking up space
I follow Umpa around as he kills Lilith, Uber Izual, Uber Duriel, Uber Mephisto, Uber Baal and finally Pandemonium Diablo.
Recorded 2nd July 2011
US West Ladder
From reddit.com/r/programingchallenges:
I googled this and I haven’t found a similar challenge, so I’d like to pose this question to you all!
Let’s say I give you a range from 1 to 2000. Within this range, find the number that yields the most characters. I asked a friend of mine and he worked out that 1888 has a lot of characters (MDCCCLXXXVIII).
import time
SYMBOLS = [
('M', 1000),
('CM', 900),
('D', 500),
('CD', 400),
('C', 100),
('XC', 90),
('L', 50),
('XL', 40),
('X', 10),
('IX', 9),
('V', 5),
('IV', 4),
('I', 1)]
def roman_numeral(number):
roman_number = [];
for (symbol, value) in SYMBOLS:
while value <= number:
roman_number.append(symbol)
number -= value
return ''.join(roman_number);
start = time.time();
pairs = [(i, roman_numeral(i)) for i in range(1, 2000)]
pairs.sort(lambda a,b: cmp(len(a[1]), len(b[1])))
print 'Longest roman numeral for numbers 1-2000 = %d -> %s' % \
(pairs[-1][0], pairs[-1][1])
print 'Took: %.2fsec' % (time.time() - start,)
Longest roman numeral for numbers 1-2000 = 1888 -> MDCCCLXXXVIII
Took: 0.14sec
Turns out historically there wasn’t a strict set of rules for Roman numerals, for example IV and IIII are both valid representations of the number 4. Only recent rules have added limits on the number of repeated characters and what values can be subtracted from other values. [Reference][2].
[2]: http://en.wikipedia.org/wiki/Roman_numerals#Reading Roman numerals
I’m not sure how useful this piece of code really is, but it gave me the chance to write some funky PHP code (have a look at the next() method).
<?php
// Single host:
foreach(new IpRange('10.10.10.10') as $ip)
{
echo $ip . "\n"
}
// >>> 10.10.10.10
// All hosts on a private network:
foreach(new IpRange('192.168.0.1-255') as $ip)
{
echo $ip . "\n";
}
// >>> 192.168.0.1
// >>> 192.168.0.2
// >>> ...
// >>> 192.168.0.254
// >>> 192.168.0.255
// All normal (not broadcast, or multicast) IP addresses:
foreach(new IpRange('1-232.0-255.0-255.0-255') as $k => $v)
{
echo "$k => $v\n";
}
// >>> 0 => 1.0.0.0
// >>> 1 => 1.0.0.1
// >>> 2 => 1.0.0.2
// >>> ...
// >>> 7315795 => 1.111.161.83
// >>> 7315796 => 1.111.161.84
// >>> etc
<?php
/*
Copyright (c) 2011, Matthew Davey <matthewd@project-2501.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
IpRange Class
Given an IP address where each octet can either be a number between 0 and
255, or a range i.e. 100-200. Return an iterable object that returns all
IP addresses in between.
Examples
--------
Single host:
foreach(new IpRange('10.10.10.10') as $ip)
{
echo $ip . "\n"
}
>>> 10.10.10.10
All hosts on a private network:
foreach(new IpRange('192.168.0.1-255') as $ip)
{
echo $ip . "\n";
}
>>> 192.168.0.1
>>> 192.168.0.2
>>> ...
>>> 192.168.0.254
>>> 192.168.0.255
All normal (not broadcast, or multicast) IP addresses:
foreach(new IpRange('1-232.0-255.0-255.0-255') as $key => $value)
{
echo "$key => $value\n";
}
>>> 0 => 1.0.0.0
>>> 1 => 1.0.0.1
>>> 2 => 1.0.0.2
>>> ...
>>> 7315795 => 1.111.161.83
>>> 7315796 => 1.111.161.84
>>> etc
*/
class IpRange implements Iterator
{
protected $count; // Current position, used as the key
protected $a; // ranges for each octet, l => low, h => high
protected $b; //
protected $c; // Most significant to least is aaa.bbb.ccc.ddd
protected $d; //
protected $ca; // current value of a
protected $cb; // current value of b
protected $cc; // current value of c
protected $cd; // current value of d
protected $isValid; // flag set when next() is call at end of ranges
public function __construct($string)
{
if(preg_match('#^(\d+|\d+-\d+)\.(\d+|\d+-\d+)\.(\d+|\d+-\d+)\.(\d+|\d+-\d+)$#', $string, $matches) !== 1)
{
throw new InvalidArgumentException('Invalid format. Each octet should either be a number between 0 and 255, or a range "40-120"');
}
// Parse each octet and find the low/high values (high === low if there is no range specified)
foreach(array(1 => 'a', 2 => 'b', 3 => 'c', 4 => 'd') as $i => $position)
{
$range = $matches[$i];
if(strpos($range, '-') !== false)
{
list($low, $high) = explode('-', $range);
}
else
{
list($low, $high) = array($range, $range);
}
// Check the IP address is at least mostly valid. We don't need to check for <0 as our regex will reject it first.
if($high > 255) throw new InvalidArgumentException("Invalid IP address. The octet '$high' cannot be greater than 255");
// If given a range like 255-0, flip the high/low value over
if($low > $high) list($high, $low) = array($low, $high);
$this->{$position} = array('l' => $low, 'h' => $high);
}
$this->rewind();
}
public function current()
{
return "{$this->ca}.{$this->cb}.{$this->cc}.{$this->cd}";
}
public function key()
{
return $this->count;
}
public function rewind()
{
$this->ca = $this->a['l'];
$this->cb = $this->b['l'];
$this->cc = $this->c['l'];
$this->cd = $this->d['l'];
$this->count = 0;
$this->isValid = true;
}
public function valid()
{
return $this->isValid;
}
public function next()
{
$this->count++;
// Least significant to most. Null is our guard.
foreach(array('d', 'c', 'b', 'a', null) as $position)
{
// Check if unable to generate the next IP address
if($position === null)
{
$this->isValid = false;
break;
}
// Check if the value is less than the maximum for this
// position, if so increment the value and stop. Otherwise set
// this position to its lowest value, and continue onto the next
// highest position.
if($this->{"c$position"} < $this->{$position}['h'])
{
$this->{"c$position"}++;
break;
}
else
{
$this->{"c$position"} = $this->{$position}['l'];
}
}
}
}
This week I’ve spent a lot of time mucking around with IPSec VPNs. I thought I should informally document some of my settings in the hope that in a years time, when I’ve forgotten everything, I have some sort of base to build on.
OS: OpenBSD >= 3.8 / Windows 7
Protocol: IPSec
Make sure the following are enabled (via /etc/sysctl.conf or the sysctl command)
net.inet.ip.forwarding=1
net.inet.esp.enable=1
net.inet.ah.enable=1
OpenBSD is awesome thanks to ipsecctl; a 4 line configuration file is all you need for a basic setup. But first we need to start isakmpd the IKEv1 key management daemon. As we are using ipsecctl to manage most of the setup, we use the -K option to ignore the isakmpd.policy file.
To see the log files for isakmpd use -DA=nn to set the debug level of all classes to nn (where nn is between 0 and 99; I’d suggest 50). Combine with with -d to keep the daemon running in the foreground.
isakmpd -K -DA=50 -d > /tmp/isakmpd.log 2>&1
ipsecctl is used in a similar way to everyone favorite tool pfctl. To load a configuration just run:
ipsecctl -f /etc/ipsec.conf
Don’t forget to check your firewall as well, you’ll need to open up port 500 (UDP) and if you want to see the unencrypted traffic set skip on enc0.
(TODO: I also have “pass in on $if_ext inet proto esp from any to $server_me_ext” is this actually needed?)
Open up /etc/ipsec.conf with vim, and then curse and moan that OpenBSD still doesn’t include vim in a default install.
Our site-site config looks like:
ike esp from 10.10.42.0/24 to 192.168.1.0/24 \
peer 103.103.103.103 \
main auth hmac-sha1 enc aes \
quick auth hmac-sha1 enc aes \
srcid 204.204.204.204 psk "put a real pre shared key here"
Where 10.10.42.0/24 is the local internal network, 192.168.1.0/24 is the remote network, 103.103.103.103 is the remote external IP and our eternal IP is 204.204.204.204.
(TODO: Fix this to use macros and define this nicely)
All that’s left is to run ipsecctl and then replicate these settings on your other OpenBSD box (all the settings will just be reversed) and you’re done.
As you can see, still super simple. We are using passive mode here so our server will not try to make a VPN connection, just listen for one.
ike passive from any to any \
main auth hmac-sha1 enc aes group modp1024 \
quick auth hmac-sha1 enc aes \
psk "good pre shared secrets are important"
(TODO: from any to any, will this give access to the entire network? Wouldn’t from 10.10.42.0/24 to any be better?)
(TODO: Why do we use DH Group 2 (modep1024) here and not above?)
On the Windows side I’m using Shrew Soft’s VPN client which is not only free, but works well.
I created a new Site Configuration and used the follow settings (click the image for a full view).
Of note:
As you can tell I’m still learning this myself, and hopefully I’ll come back to this is a year, call my old-self an idiot and write a far better post.
Note: I wrote this at 5am in the morning, so please excuse all the mistakes
“In computer programming, code smell is any symptom in the source code of a program that possibly indicates a deeper problem.” — Wikipedia
I found this piece of code this morning, I think it counts as something gone terribly, terribly wrong:
return displayItemDetails(this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode, 14059111);
(The code was in-line JavaScript, inserted into a onclick handler, generated in PHP)
I recently needed to set up a new reverse proxy as an alternative to pound, and for no particularly good reason chose Perlbal.
The documentation is fairly good, but I didn’t find many (good) examples of working configurations. So I thought I’d include my very simple conf.
LOAD vhosts
# Management service via telnet
CREATE SERVICE mgmt
SET role = management
SET listen = 127.0.0.1:16000
ENABLE mgmt
# Web server
CREATE POOL web
POOL web ADD 10.10.42.41:80
# Trac server
CREATE POOL trac
POOL trac ADD 10.10.42.42:80
CREATE SERVICE web_proxy
SET role = reverse_proxy
SET pool = web
ENABLE web_proxy
CREATE SERVICE trac_proxy
SET role = reverse_proxy
SET pool = trac
ENABLE trac_proxy
# Internally we use 'trac.internal.com' but externally it would
# be 'trac.external.com'. So rather than creating a second
# virtual host on our trac webserver, we re-write the header
HEADER trac_proxy REMOVE Host
HEADER trac_proxy INSERT Host:trac.internal.com
# Listen on our external IP
CREATE SERVICE selector
SET listen = 100.110.120.130:80
SET role = selector
SET plugins = vhosts
VHOST external.com.au = web_proxy
VHOST www.external.com.au = web_proxy
VHOST trac.external.com.au = trac_proxy
ENABLE selector
Firefox by default does not use your proxy when making DNS requests. This can lead to a bit of confusion if your internal DNS servers are different from your public servers, thankfully there’s a simple fix:
about:confignetwork.proxy.socks_remote_dns and set the value to trueMaybe this will help someone else with this laptop:
I’ve migrated most of my sites / services away from Slicehost this week. I guess the trigger was a combination of the Rackspace migration news, and the desire for lower latency to the server. I’ve been with Slicehost for around 3 years with an almost perfect track record (at least when I don’t let a process use up all the memory and the OOM killer).
I was originally looking at Crucial Paradigm due to lots of positive feedback and good prices. However their prices are really only good for new customers as the double RAM offer is not extended to upgrades/download of plans, which could cause some problems if you ever want to resize your VPS. They also specifically disallow game servers in thier AUP (which is a problem as I’m currently running a Minecraft server).
I tested the new iiNet VPS which is located in iiNet’s WA data centre. While the pricing is good, I wasn’t unhappy with the performance of the Virtuozzo based system (I was unable to run a Minecraft server with a single user without the load > 4.0), and the latency from Melbourne wasn’t exactly great.
I’ve now switched to MammothVPS 1 and so far it’s been a good experience. Their data centre is in Sydney and has a fairly low latency for Melbourne and Sydney users2 and their plan offerings are very flexible and are competitively priced for an Australian based server. They use Xen (like Slicehost) so there’s much less risk for over selling.
Note: The information present in this post is a couple of months out of date. I’ve not had the time to re-update WoL data and create new graphs, but thought it might be interesting share the data/code anyway
When choosing a Holy Paladin spec for 10 mans I was never completely sure about benefits of Tower of Radiance or Blessed Life. ToR seems like a safe choice, though the frequency that you DL/FoL your beacon seemed low. Blessed Life on the other hand was obviously a PvP talent, but it would generate free HP from some raid damage.
I set out to scrape the top 200 Paladins for each 10-N boss fights from World of Logs and record their source of HP generation. I created two graphs; one that looks at all 200 parses in general, and a second that only counts when the ability is present. e.g. Only 10 or so Paladins used CS on Cho’gall, and they generated very little HP overall, but if you just look at lose 10 parses, the average is 8. This is used as a crude way to see when certain talents are taken.
I’ve included the python code used to generate the data for these graphs below. My general disclaimer applies (i.e. be afraid)
import urllib2
import re
pages = {
'magmaw-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Magmaw/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Magmaw/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Magmaw/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Magmaw/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Magmaw/10N/Holy_Paladin/?page=5'],
'omnitron-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Omnitron_Defense_System/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Omnitron_Defense_System/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Omnitron_Defense_System/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Omnitron_Defense_System/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Omnitron_Defense_System/10N/Holy_Paladin/?page=5'],
'chimaeron-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Chimaeron/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Chimaeron/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Chimaeron/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Chimaeron/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Chimaeron/10N/Holy_Paladin/?page=5'],
'atramedes-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Atramedes/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Atramedes/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Atramedes/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Atramedes/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Atramedes/10N/Holy_Paladin/?page=5'],
'maloriak-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Maloriak/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Maloriak/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Maloriak/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Maloriak/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Maloriak/10N/Holy_Paladin/?page=5'],
'nefarian-10-n': ['http://worldoflogs.com/rankings/players/Blackwing_Descent/Nefarian/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Nefarian/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Nefarian/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Nefarian/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Blackwing_Descent/Nefarian/10N/Holy_Paladin/?page=5'],
'halfus-10-n': ['http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Halfus_Wyrmbreaker/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Halfus_Wyrmbreaker/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Halfus_Wyrmbreaker/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Halfus_Wyrmbreaker/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Halfus_Wyrmbreaker/10N/Holy_Paladin/?page=5'],
'valiona-10-n': ['http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Valiona_&_Theralion/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Valiona_&_Theralion/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Valiona_&_Theralion/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Valiona_&_Theralion/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Valiona_&_Theralion/10N/Holy_Paladin/?page=5'],
'twilight-10-n': ['http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Twilight_Ascendant_Council/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Twilight_Ascendant_Council/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Twilight_Ascendant_Council/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Twilight_Ascendant_Council/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Twilight_Ascendant_Council/10N/Holy_Paladin/?page=5'],
'chogall-10-n': ['http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Cho\'gall/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Cho\'gall/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Cho\'gall/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Cho\'gall/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Bastion_of_Twilight/Cho\'gall/10N/Holy_Paladin/?page=5'],
'conclave-10-n': ['http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Conclave_of_Wind/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Conclave_of_Wind/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Conclave_of_Wind/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Conclave_of_Wind/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Conclave_of_Wind/10N/Holy_Paladin/?page=5'],
'alakir-10-n': ['http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Al\'Akir/10N/Holy_Paladin/',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Al\'Akir/10N/Holy_Paladin/?page=2',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Al\'Akir/10N/Holy_Paladin/?page=3',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Al\'Akir/10N/Holy_Paladin/?page=4',
'http://worldoflogs.com/rankings/players/Throne_of_the_4_Winds/Al\'Akir/10N/Holy_Paladin/?page=5'],
}
def extract_player_pages(url):
regex = "<td><a href='(/reports/[^']+)'>([^<]+)</a></td>"
html = urllib2.urlopen(url).read()
matches = re.findall(regex, html)
return matches
def extract_player_details(report_url, player_name):
# First load the healing summary page
url = 'http://worldoflogs.com' + report_url
html = urllib2.urlopen(url).read()
match = re.search("<a href='(/reports/[^']+)'>" + player_name + "</a>", html)
# now load the players healing detail page
url = 'http://worldoflogs.com' + match.group(1)
html = urllib2.urlopen(url).read()
return html
for boss in pages:
print boss
for url in pages[boss]:
reports = extract_player_pages(url)
for report_details in reports:
report_url = report_details[0]
report_player_name = report_details[1]
print 'Extracting', report_player_name
html = extract_player_details(report_url, report_player_name)
f = open('data/' + boss + '/' + report_player_name, 'w+')
f.write(html)
f.close()
print 'Done'
import glob
import re
# Spell IDs
HOLY_SHOCK = 20473
ETERNAL_GLORY = 88676
TOWER_OF_RADIANCE = 88852
BLESSED_LIFE = 89023
PURSUIT_OF_JUSTICE = 89024
CRUSADER_STRIKE = 35395
bosses = [
'alakir-10-n',
'atramedes-10-n',
'chimaeron-10-n',
'chogall-10-n',
'conclave-10-n',
'halfus-10-n',
'magmaw-10-n',
'maloriak-10-n',
'nefarian-10-n',
'omnitron-10-n',
'twilight-10-n',
'valiona-10-n',
]
def extract_holy_power(html):
regex = r" <td class='name'><a href='/reports/[^']+' rel='spell=(\d+)' class='spell'><span [^>]+>([^<]+)</span></a>\S+\n <td>(\d+) holy power</td>"
matches = re.findall(regex, html)
results = {}
for match in matches:
(spell_id, spell_name, count) = match
results[int(spell_id)] = int(count)
return results
def generate_holy_power_summary(boss):
summary = {}
files = glob.glob('data/' + boss + '/*')
for file in files:
html = open(file, 'r').read()
results = extract_holy_power(html)
for spell_id in results:
count = results[spell_id]
if not summary.has_key(spell_id):
summary[spell_id] = []
summary[spell_id].append(count)
return summary
report_on = [
{'spell_name': 'HOLY_SHOCK', 'spell_id': HOLY_SHOCK},
{'spell_name': 'ETERNAL_GLORY', 'spell_id': ETERNAL_GLORY},
{'spell_name': 'TOWER_OF_RADIANCE', 'spell_id': TOWER_OF_RADIANCE},
{'spell_name': 'BLESSED_LIFE', 'spell_id': BLESSED_LIFE},
{'spell_name': 'PURSUIT_OF_JUSTICE', 'spell_id': PURSUIT_OF_JUSTICE},
{'spell_name': 'CRUSADER_STRIKE', 'spell_id': CRUSADER_STRIKE},
]
for boss in bosses:
print
print boss
summary = generate_holy_power_summary(boss)
if not summary.has_key(HOLY_SHOCK):
print 'Skipping, no data'
continue
total_count = len(summary[HOLY_SHOCK])
print 'Total reports', total_count
if total_count == 0:
continue
for report_details in report_on:
if not summary.has_key(report_details['spell_id']):
print report_details['spell_name'], 0
continue
spell_count = len(summary[report_details['spell_id']])
total_average = sum(summary[report_details['spell_id']]) / total_count
spell_average = sum(summary[report_details['spell_id']]) / spell_count
print report_details['spell_name'], total_average