#!/usr/bin/perl

# nhk - nethack monster high scores (version 1.0)
#
# Copyright (C) 1993 by John J. Chew, III <jjchew@math.utoronto.ca>
# Heavily edited by Boudewijn Wayers <kroisos@win.tue.nl>
#
# Eva Myers <erm1001@cam.ac.uk> updated it for 3.3.0 and made a few
# changes - 23 Feb 2000
#
# 11 Mar 2000 - First fully functional version for 3.3.0.  It was
# modified to take account of all the ways to die in my list - Eva.
#
# 17 Jul 2000 - Added Scorpius to the list of monsters which can kill
# with disease.
#
# 1 Dec 2000 - Updated it to work with Slash'Em (if $CV_DEFAULT_DIRECTORY
# is changed)
#
# 11 Dec 2000 - Fixed "killed by an X called Y, while helpless" bug.
#
# DESCRIPTION
#
# This program lists high scores for monsters, either by the number of
# players that they have managed to kill, or by the total score of 
# their victims.  
#
# BUGS
#
# - nhk has been tested only on our local files and may not correctly
#   format unusual killers.  Please report bugs to erm1001@cam.ac.uk
# - nhk stands, for historical reasons, for NetHack Killers.  It does
#   not stand for Nihon Housou Kyoku.
# - Some causes of death (e.g., riding a cockatrice) need more elegant
#   and precise descriptions
# - It is not always obvious which deaths are "really" the same.  YMMV.
#
# The output looks like:
#
# 13 47668338  3666795 ascended
#  1  2122863  2122863 Pestilence
#  1   408571   408571 electric eel
#  1   239816   239816 Croesus
#  1   211387   211387 Ashikaga Takauji
#  2   138039    69019 death ray
#  2   130732    65366 air elemental
#  1   101805   101805 troll
#  1    89870    89870 dingo
#  1    62131    62131 priest(ess)
#  1    35567    35567 cockatrice egg
#  1    27756    27756 quit
#  4    26614     6653 helplessness
#  1    21839    21839 fire vortex
#  1    13699    13699 blue jelly
#  1    13047    13047 leocrotta
#  1    11236    11236 gnome lord
#  1     3033     3033 rothe
#  1     1825     1825 giant ant

# configuration variables

$CV_DEFAULT_DIRECTORY = '/usr/lib/games/nethackdir';
$CV_LOGFILE_NAME = 'logfile';
$CV_RECORD_NAME = 'record';

%gods = ("Quetzalcoatl", 1, "Camaxtli", 1, "Huhetotl", 1,
	 "Mitra", 1, "Crom", 1, "Set", 1,
	 "Anu", 1, "Ishtar", 1, "Anshar", 1,
         "Earth", 1, "Fire", 1, "Ash", 1,
	 "Athena", 1, "Hermes", 1, "Poseidon", 1,
         "Air", 1, "Frost", 1, "Smoke", 1,
	 "Lugh", 1, "Brigit", 1, "Manannan Mac Lir", 1,
	 "Shan Lai Ching", 1, "Chih Sung-tzu", 1, "Huan Ti", 1,
         "Nharlotep", 1, "Zugguthobal", 1, "Gothuulbe", 1,
	 "Issek", 1, "Mog", 1, "Kos", 1,
	 "Mercury", 1, "Venus", 1, "Mars", 1,
	 "Amaterasu Omikami", 1, "Raijin", 1, "Susanowo", 1,
	 "Blind Io", 1, "The Lady", 1, "Offler", 1,
         "Seeker", 1, "Osiris", 1, "Seth", 1,
	 "Tyr", 1, "Odin", 1, "Loki", 1,
	 "Ptah", 1, "Thoth", 1, "Anhur", 1,
         "His Majesty", 1, "His Holiness", 1, "The Commons", 1);

@petrifiers = ("cockatrice", "chickatrice", "Medusa", "basilisk", "asphynx");

sub usage {
  warn "Usage: $0 [ -f file | -l | -r ] [ -n | -s ]\n\n";
  warn "  -f  use indicated file\n";
  warn "  -l  use logfile\n";
  warn "  -r  use record (high score file) [default]\n";
  warn "  -n  sort by number of kills\n";
  warn "  -s  sort by aggregate victim score [default]\n";
  exit 1;
  }

require 'getopts.pl';
&Getopts('f:lnrs') || &usage;
(defined $opt_f) && $opt_l && $opt_r && &usage;
$opt_n && $opt_s && &usage;
$opt_n || $opt_s || ($opt_s = 1);
$FILE = (defined $opt_f) ? $opt_f : 
  $CV_DEFAULT_DIRECTORY . '/' . ($opt_l ? $CV_LOGFILE_NAME : $CV_RECORD_NAME);

(defined $opt_f) || $opt_l || ($opt_r = 1);

open(FILE, "<$FILE") || die "$0: cannot open $FILE";
@file = <FILE>;
close(FILE);

$killlen = 1;
$scorelen = 1;
for $_ (@file) {
  chop;
  # version points deathwhere deathlev maxlvl hp maxhp deathdnum enddate
  # startdate usernum class race gender alignment name,death
  @info = split(/ /, $_, 16); $_ = $info[15];
  s/ \(with the Amulet\)//;
  s/ \(with a fake Amulet\)//;
  s/[^\,]*,//;

# This must be checked here because of "killed by a little dog called
# Idefix, while helpless".
# Some forms of helplessness, like being paralysed by a floating eye, are
# typically the root cause of death, while others, like praying 
# unsuccessfully, are not, but this is the best we can do until the logfile
# distinguishes between the different kinds of helplessness.
  s/.*, while helpless$/helplessness/;

# "called XYZ" always comes at the end, except for this
  if (/trying to help/ && /out of a pit/) {
    s / called .*/ out of a pit/;
  }
  else {
    s/ called .*//;
  }

# "choked on a foo" - choking
# "poisoned by a rotted foo corpse" - food poisoning
# "poisoned by a killer bee/quasit/..." - deadly poison
# "poisoned by Demogorgon/Pestilence/Juiblex/Scorpius" - 
# Demogorgon/Pestilence/Juiblex/Scorpius
# "drowned ... by a foo" - foo
# "drowned ..." - drowned [removed ring of levitation above water?]
  s/choked on .*/choking/;
  if (/poisoned by/) {
    s/poisoned by( an?)? //;
    if (/corpse/) { $_ = "food poisoning"; }
    elsif ($_ ne "Demogorgon" && $_ ne "Pestilence" && $_ ne "Juiblex"
           && $_ ne "Scorpius")
      { $_ = "deadly poison"; }
  }
  s/drowned in .* by an? //;
  s/drowned .*/drowned/;

  if (/petrified by/) {
    s/petrified by( an?)? //;
# Arbitrary assumption: all ways of being turned to stone by a
# cockatrice corpse are essentially the same YASD, but the ways of being
# petrified by a live cockatrice are distinct.
    if (/corpse| tin |meat/) {
      foreach $mon (@petrifiers) {
	if (/$mon/) { $_ = "dead $mon"; }
      }
    }

# Riding cockatrices (can this really happen?)
# Assumes that the monster name is the last word in the line,
# and that Medusa is the only unique petrifying monster
    if (/ride|riding|saddle|falling off/) {
      @words = split;
      $monart = $words[$#words-1] . " ";
      $monster = $words[$#words];
      if ($monster eq "Medusa") { $monart = ""; }
      $_ = "riding" . $monart . $monster;
    }
  }

  s/(killed by|crushed to death by|dissolved in)( an?)? //; # see topten.c
  s/^the //;
  s/hallucinogen-distorted |invisible //;
  s/ his / his\/her /;
  s/ her / his\/her /;
  s/ himself / him\/herself /;
  s/ herself / him\/herself /;

  s/priest(ess)?.*/priest(ess)/;
  s/.*, the shopkeeper/shopkeeper/;
  s/ghost of .*/ghost/;
  s/slipped while mounting .*/slipped while mounting a steed/;
  s/fall onto //;
  s/bolt of .*/magic bolt/;
  s/blast of .*/magic blast/;
  s/wrath of .*/divine wrath/;

# Miscellaneous deaths which are (IMHO) really the same thing
  if (/iron ball/) { $_ = "iron ball"; }
  if (/drawbridge/) { $_ = "drawbridge"; }
  if (/^caught/) { $_ = "caught him/herself in his/her own magical blast"; }
  if (/genocid/) { $_ = "self-genocide"; }
  if (/kicking/) { $_ = "kicking something"; }
  if (/reverting/) { $_ = "reverting to unhealthy normal form"; }
  if (/touching/ && $_ !~ /edge of the universe/) 
    { $_ = "touching an artifact"; }

# Divine minions.  "X of Y" must become "X" if and only if "Y" is a
# deity, EXCEPT that "Minion of Huhetotl" needs to remain unchanged.
  if (/of ([A-Z].*)/) {
    if ($gods{$1} == 1 && $_ ne "Minion of Huhetotl") {
      s/of [A-Z].*//;
    }
  }

  $count{$_}++;
  $killlen = length($count{$_}) if length($count{$_}) > $killlen;
  $score{$_} += $info[1];
  $scorelen = length($score{$_}) if length($score{$_}) > $scorelen;
  }

sub mysort {
  ($A = $a) =~ tr/A-Z/a-z/; ($B = $b) =~ tr/A-Z/a-z/;
  if ($opt_n) {
    $count{$b} <=> $count{$a} || $score{$b} <=> $score{$a} || $A cmp $B;
  }
  else {
    $score{$b} <=> $score{$a} || $count{$b} <=> $count{$a} || $A cmp $B;
  }
}

for $key (sort mysort keys %count)
  {
  printf "%${killlen}d %${scorelen}d %${scorelen}d %s\n", $count{$key}, $score{$key}, $score{$key}/$count{$key}, $key;
  }

exit 0;
__END__