#!/usr/bin/perl # Filename: dAIve # Author: David Ljung Madison # See License: http://MarginalHacks.com/License # Description: AI interface to dopewars # Version: 1.01 use strict; use IO::Socket; ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; # Dopewars stuff my $DW_VERSION = "1.4.8"; my $MESSAGE_H = "dopewars-$DW_VERSION/message.h"; # This is a guess - the server doesn't tell us that it's our last day :( my $NUM_TURNS = 30; # Dopewars server my $HOST = "localhost"; my $PORT = 7902; ################################################## # Allow override of any internal functions # They can specify any perl code on the command line ################################################## my @USING; sub load_ai { (@USING) = @_; foreach my $ai ( @USING ) { next if ($ai eq ""); if (-f $ai) { if (!do $ai) { print STDERR "[$0] Couldn't read ai [$ai]:\n $!\n" if ($!); print STDERR "[$0] Couldn't compile ai [$ai]:\n $@\n" if ($@); } print "Using AI module: $ai\n"; } else { print STDERR "[$0] Couldn't find AI module [$ai]\n" if ($ai); } } } load_ai(@ARGV); ################################################## # Connection ################################################## # This is the hokey way to write a client, using telnet.. sub expect { my ($exp) = @_; die("Expected expression [$exp]\nSaw: $_\n") unless (/$exp/); } sub client { my ($host,$port) = @_; $host = $host || "localhost"; $port = $port || 7902; my $handle = IO::Socket::INET->new(Proto => "tcp", PeerAddr => $host, PeerPort => $port) or die "cannot connect to port [$port] at localhost"; $handle->autoflush(1); $handle; } ################################################## # Game object (holds all game information) # Utility functions ################################################## sub list_drugs { my ($game) = @_; @{$game->{'Drugs'}}; } sub list_locations { my ($game) = @_; @{$game->{'Locations'}}; } sub location { my ($game) = @_; $game->{'Inv'}{'IsAt'}; } sub location_name { my ($game,$i) = @_; $game->{'Locations'}[$i]; } sub loan_shark { my ($game,$set) = @_; $game->{'Home'}{'LoanShark'} = $set if (defined $set); $game->{'Home'}{'LoanShark'}; } sub list_players { my ($game) = @_; @{$game->{'Players'}}; } sub drug_name { my ($g,$i) = @_; $g->{'Drugs'}[$i]{'Name'}; } sub drug_price { my ($g,$i) = @_; $g->{'Prices'}[$i]; } sub drug_ave { my ($g,$i) = @_; $g->{'Drugs'}[$i]{'Ave'}; } sub drug_min { my ($g,$i) = @_; $g->{'Drugs'}[$i]{'Min'}; } sub drug_max { my ($g,$i) = @_; $g->{'Drugs'}[$i]{'Max'}; } sub turn_num { my ($g) = @_; $g->{'Inv'}{'Turn'}; } sub inv_debt { my ($g) = @_; $g->{'Inv'}{'Debt'}; } sub inv_cash { my ($g) = @_; $g->{'Inv'}{'Cash'}; } sub inv_drugs { my ($g) = @_; @{$g->{'Inv'}{'Drugs'}}; } sub inv_drug { my ($g,$i) = @_; $g->{'Inv'}{'Drugs'}[$i]; } sub spaces_avail { my ($game) = @_; my $spaces = $game->{'Inv'}{'CoatSize'}; for (my $drug=0; $drug<$game->{'NumDrugs'}; $drug++) { $spaces -= inv_drug($game,$drug); } $spaces; } ################################################## ################################################## ################################################## # Make decisions # This is the 'AI' part of the code. If you # want to write your own drugwars 'bot, then cut # out this section and make it a new perl program. ################################################## ################################################## ################################################## # Choose our name # This needs to return a different name each time it's # called, in case someone is already using the name we try sub ChooseName { my ($game) = @_; return ++$game->{'Name'} if ($game->{'Name'}); $game->{'Name'} = $PROGNAME; } sub DealDrugs { my ($game) = @_; my $turns_left = $NUM_TURNS - turn_num($game); # Since there is no cost to buying back a drug we just sold, we # can simplify the logic for deciding which drugs to sell and then # buy by selling all drugs, and then trying to buy drugs sorted according # to which are the best buy. If we sell a drug we shouldn't have, then # arguably we should buy it back, otherwise our sort algorithm is broken. # See what's available, sell all drugs, choose some to buy my @wanted; for (my $drug=0; $drug<$game->{'NumDrugs'}; $drug++) { # Not available here next unless (drug_price($game,$drug)); # We want drugs that are cheap by more than some percent my $percent = 10; $percent = 20 if ($turns_left == 2); # Careful! $percent = 40 if ($turns_left == 1); # Careful! my $profit = drug_ave($game,$drug) - drug_price($game,$drug); push(@wanted,$drug) if ($profit > drug_price($game,$drug)*$percent/100); # Sell everything, wanted or not (you can't always get what you want :) SellDrug($game,$drug,inv_drug($game,$drug)) if (inv_drug($game,$drug)); } if (@wanted && $turns_left) { # Sort algorithm for which drugs to buy sub best_buy { my $a_profit = drug_ave($game,$a) - drug_price($game,$a); my $b_profit = drug_ave($game,$a) - drug_price($game,$a); $b_profit <=> $a_profit; } # Buy the more expensive drugs first to conserve space/$ foreach my $drug ( sort best_buy @wanted ) { BuyDrug($game,$drug,undef); # Buy all we can } } # Did we miss out on selling some drugs? if (!$turns_left) { for (my $drug=0; $drug<$game->{'NumDrugs'}; $drug++) { print "Damn: Didn't sell ",inv_drug($game,$drug), " of ",drug_name($game,$drug)," (losing approx \$", (inv_drug($game,$drug)*drug_ave($game,$drug)),")\n" if (!drug_price($game,$drug) && inv_drug($game,$drug)); } } } sub JetTo { my ($game) = @_; my $loc = location($game); # Time to see the loan shark? my $debt = inv_debt($game); my $shark = loan_shark($game); return $shark if (defined($shark) && $loc != $shark && $debt && inv_cash($game)*1.2 > $debt); ($loc+1) % $#{$game->{'Locations'}}; } # Amount to pay the loan shark sub Pay_LoanShark { my ($game) = @_; my $debt = inv_debt($game); # As long as we have enough left over, pay the loan shark! return $debt if (inv_cash($game)*1.1 > $debt); return 0; } ################################################## ################################################## ################################################## # End decision section ################################################## ################################################## ################################################## ################################################## # Communication to server ################################################## # SendClientMessage(NULL,C_NONE,C_NAME,NULL,AIPlay->Name); #SendClientMessage(PLAYER *From,char AICode,char Code,PLAYER *To,char *Data) #sprintf(text,"%s^%s^%game->{'C'}%s",From->Name, To->Name,AICode,Code,Data); #from^to^codecodedata sub SendClientMessage { my ($game,$From,$AICode,$Code,$To,$Data) = @_; my $code = $game->{'C'}{$Code} || $game->{'DT'}{$Code}; print "[$PROGNAME] Unknown Code: $Code\n" unless $code; my $aicode = $game->{'C'}{$AICode}; print "[$PROGNAME] Unknown AICode: $AICode\n" unless $aicode; #printf(STDERR "Send: %s^%s^%s%s%s\n",$From || "",$To || "",$aicode,$code,$Data); my $h = $game->{'handle'}; printf($h "%s^%s^%s%s%s\n",$From || "",$To || "",$aicode,$code,$Data); } sub SetName { my ($game) = @_; SendClientMessage($game,0,'NONE','NAME',0,$game->{'Name'}); } sub SellDrug { my ($game,$num,$amt) = @_; BuyDrug($game,$num,-$amt); } # Use undefined $amt to buy as much as possible sub BuyDrug { my ($game,$drug,$amt) = @_; if (!defined $amt || $amt>0) { # Buying my $can_afford = int($game->{'Inv'}{'Cash'} / drug_price($game,$drug)); my $spaces_avail = spaces_avail($game); $amt = $can_afford if (!defined $amt || $amt > $can_afford); $amt = $spaces_avail if (!defined $amt || $amt > $spaces_avail); return unless $amt; #print "Buy $amt x ",drug_name($game,$drug)," at ",drug_price($game,$drug),"\n"; } else { # Selling (amount is negative!) my $inventory = inv_drug($game,$drug); $amt = -$inventory if ($amt < -$inventory); return unless $amt; #print "Sell ",-$amt," x ",drug_name($game,$drug)," at ",drug_price($game,$drug),"\n"; } SendClientMessage($game,$game->{'Name'},'NONE','BUYOBJECT',0,"drug^$drug^$amt"); # Could just get the UPDATE message that we should be seeing.. # Adjust cash $game->{'Inv'}{'Cash'} -= $amt * drug_price($game,$drug); # Adjust drug inventory $game->{'Inv'}{'Drugs'}[$drug] += $amt; } sub Jet { my ($game) = @_; my $to = JetTo($game); #print "Jet to [$to]: $game->{'Locations'}[$to]\n"; SendClientMessage($game,$game->{'Name'},'NONE','REQUESTJET',0,$to); } ################################################## # Handle information from server ################################################## sub get_message { my ($game) = @_; # Read some input, break up the fields my $h = $game->{'handle'}; return undef unless ($_ = <$h>); chomp; return get_message($game) if (/^\s*$/); unless (/^([^\^]*)\^([^\^]*)\^(.)(.)(.*)$/) { print "[$PROGNAME] Bad network message. Oops:\n [$_]\n"; next; } return ($1,$2,$3,$4,$5); } # Regular expressions for the Data field my $WORD = '([^\^]+)'; my $NUM = '(\d+)'; my $Br = '\^'; my $WORDb = $WORD.$Br; my $NUMb = $NUM.$Br; sub game_loop { my ($game) = @_; SetName($game,ChooseName($game)); # Handle messages while (1) { my ($From,$To,$AICode,$Code,$Data) = get_message($game); last unless defined $From; #print "Got [$_]\n"; #print "From: $From\nTo: $To\nCodes: $AICode,$Code\nData: $Data\n"; # Decode the message ######################### # INIT ######################### if ($Code eq $game->{'C'}{'INIT'}) { # Init message: # "%s^%d^%d^%d^%s^%s^%s^%s^%s^%s^%s^%s^", # VERSION,NumLocation,NumGun,NumDrug, # Names.Bitch,Names.Bitches,Names.Gun,Names.Guns, # Names.Drug,Names.Drugs,Names.Month,Names.Year); die("[$PROGNAME] Couldn't understand INIT message:\n [$Data]\n") unless ($Data =~ /^$WORDb$NUMb$NUMb$NUMb$WORDb$WORDb$WORDb$WORDb$WORDb$WORDb$WORDb$WORDb$/); ($game->{'Version'},$game->{'NumLocs'},$game->{'NumGuns'},$game->{'NumDrugs'}, $game->{'Names'}{'Bitch'}, $game->{'Names'}{'Bitches'}, $game->{'Names'}{'Gun'}, $game->{'Names'}{'Guns'}, $game->{'Names'}{'Drug'}, $game->{'Names'}{'Drugs'}, $game->{'Names'}{'Month'}, $game->{'Names'}{'Year'}) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12); print STDERR "Server version [$game->{'Version'}] does not match [$DW_VERSION]\n" if ($game->{'Version'} ne $DW_VERSION); ######################### # Our name is taken ######################### } elsif ($Code eq $game->{'C'}{'NEWNAME'}) { # We need to pick a new name SetName($game,ChooseName($game)); ######################### # Someone joined/left the game, list players ######################### } elsif ($Code eq $game->{'C'}{'JOIN'}) { print "[$PROGNAME] \"$Data\" joined the game\n"; $game->{'Players'}{$Data} = 1; } elsif ($Code eq $game->{'C'}{'LEAVE'}) { print "[$PROGNAME] \"$Data\" left the game\n"; undef $game->{'Players'}{$Data}; } elsif ($Code eq $game->{'C'}{'LIST'}) { print "[$PROGNAME] Player in game: $Data\n"; $game->{'Players'}{$Data} = 1; ######################### # DATA ######################### } elsif ($Code eq $game->{'C'}{'DATA'}) { # Data is either prices, locations, guns or drugs # "num^Dstr^str^.. die("[$PROGNAME] Couldn't understand DATA message:\n [$Data]\n") unless ($Data =~ /^$NUMb(.)(.+)/); my ($num,$dt,$str) = ($1,$2,$3); ######################### # DATA, DT_PRICES if ($dt eq $game->{'DT'}{'PRICES'}) { # str is Prices.Spy, Prices.Tipoff print STDERR "[$PROGNAME] DT_PRICES error [$Data]\n" unless ($str =~ /^$NUMb$NUMb$/); $game->{'Cost'}{'Spy'} = $1; $game->{'Cost'}{'Tipoff'} = $2; ######################### # DATA, DT_LOCATION } elsif ($dt eq $game->{'DT'}{'LOCATION'}) { $str =~ s/\^$//; $game->{'Locations'}[$num] = $str; ######################### # DATA, DT_DRUG } elsif ($dt eq $game->{'DT'}{'DRUG'}) { print STDERR "[$PROGNAME] DT_DRUG error [$Data]\n" unless ($str =~ /^$WORDb$NUMb$NUMb$/); $game->{'Drugs'}[$num]{'Name'} = $1; $game->{'Drugs'}[$num]{'Min'} = $2; $game->{'Drugs'}[$num]{'Max'} = $3; $game->{'Drugs'}[$num]{'Ave'} = $2+($3-$2)/2; ######################### # DATA, DT_GUN } elsif ($dt eq $game->{'DT'}{'GUN'}) { print STDERR "[$PROGNAME] DT_GUN error [$Data]\n" unless ($str =~ /^$WORDb$NUMb$NUMb$NUMb$/); $game->{'Guns'}{$1}{'num'} = $num; $game->{'Guns'}{$1}{'price'} = $2; $game->{'Guns'}{$1}{'space'} = $3; $game->{'Guns'}{$1}{'damage'} = $4; } else { print STDERR "[$PROGNAME] Unknown DT Type error [$dt,$Data]\n"; } ######################### # ENDLIST ######################### } elsif ($Code eq $game->{'C'}{'ENDLIST'}) { print STDERR "[$PROGNAME] What do I do with C_ENDLIST?? [$Data]\n" if ($Data); # Otherwise ignore, we are stateless ######################### # UPDATE (Spy report or personal update) ######################### } elsif ($Code eq $game->{'C'}{'UPDATE'}) { my $re = $NUMb x 8 . '(.+)'; print STDERR "[$PROGNAME] UPDATE error [$Data]\n" unless ($Data =~ /^$re$/); if ($To eq $game->{'Name'}) { ($game->{'Inv'}{'Cash'}, $game->{'Inv'}{'Debt'}, $game->{'Inv'}{'Bank'}, $game->{'Inv'}{'Health'}, $game->{'Inv'}{'CoatSize'}, $game->{'Inv'}{'IsAt'}, $game->{'Inv'}{'Turn'}, $game->{'Inv'}{'Flags'}) = ($1,$2,$3,$4,$5,$6,$7,$8); my @inv = split($Br,$9); @{$game->{'Inv'}{'Bitches'}} = pop(@inv); @{$game->{'Inv'}{'Guns'}} = splice(@inv,0,$game->{'NumGuns'}); @{$game->{'Inv'}{'Drugs'}} = @inv; print "Inv [$game->{'Inv'}{'Turn'}] $game->{'Inv'}{'Cash'}/$game->{'Inv'}{'Debt'}, @{$game->{'Inv'}{'Drugs'}}\n"; } else { print STDERR "[$PROGNAME] UPDATE - Spying on $To??\n"; } ######################### # Print Message ######################### } elsif ($Code eq $game->{'C'}{'PRINTMESSAGE'}) { # Ignore for now. next; my @m = split($Br,$Data); print "Msg: ",join("\nMsg: ",@m),"\n"; ######################### # Y/N Question ######################### } elsif ($Code eq $game->{'C'}{'QUESTION'}) { print STDERR "[$PROGNAME] QUESTION error [$Data]\n" unless ($Data =~ /^$WORDb(.+)$/); my ($type,$question) = ($1,$2); my $answer; # The AICode tells us what the question is: if ($AICode eq $game->{'C'}{'NONE'}) { $answer = "N"; } # Bad weed! elsif ($AICode eq $game->{'C'}{'ASKLOAN'}) { loan_shark($game,location($game)); # Set the loan shark location $answer = "Y"; } elsif ($AICode eq $game->{'C'}{'COPS'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKBITCH'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKGUN'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKGUNSHOP'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKPUB'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKBANK'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'ASKRUN'}) { $answer = "Y"; } elsif ($AICode eq $game->{'C'}{'ASKRUNFIGHT'}) { $answer = "R"; } elsif ($AICode eq $game->{'C'}{'ASKSEW'}) { $answer = "N"; } elsif ($AICode eq $game->{'C'}{'MEETPLAYER'}) { $answer = "E"; } # Evade else { print STDERR "[$PROGNAME] Unknown QUESTION AICode: $AICode\n [$type] $question\n"; } print STDERR "[$PROGNAME] Bad response: $answer\n [$type] $question\n" if ($type !~ /$answer/); SendClientMessage($game,$game->{'Name'},'NONE','ANSWER',$From,$answer); ######################### # Subway flash? ######################### } elsif ($Code eq $game->{'C'}{'SUBWAYFLASH'}) { next; # Ignore for now. next unless $Data; my @m = split($Br,$Data); print "Subway: ",join("\nSubway: ",@m),"\n"; ######################### # Loan shark? ######################### } elsif ($Code eq $game->{'C'}{'LOANSHARK'}) { my $pay = Pay_LoanShark($game); SendClientMessage($game,$game->{'Name'},'NONE','PAYLOAN',0,$pay) if ($pay); SendClientMessage($game,$game->{'Name'},'NONE','DONE',0,0) ######################### # Drug prices ######################### } elsif ($Code eq $game->{'C'}{'DRUGHERE'}) { @{$game->{'Prices'}} = split(/$Br/,$Data); print STDERR "[$PROGNAME] Wrong number of drugs? [$game->{'NumDrugs'}] [$Data]\n" unless ($#{$game->{'Prices'}}+1 == $game->{'NumDrugs'}); ######################### # Now we can deal some drugs! ######################### DealDrugs($game); ######################### # Where to now? ######################### Jet($game); ######################### # Start Hi Score (game over) ######################### } elsif ($Code eq $game->{'C'}{'STARTHISCORE'}) { print STDERR "[$PROGNAME] STARTHISCORE Error? [$Data]\n" if ($Data); print STDERR "[$PROGNAME] Game Over\n"; # Ignore - we are stateless ######################### # Hi Score entry ######################### } elsif ($Code eq $game->{'C'}{'HISCORE'}) { print STDERR "[$PROGNAME] HISCORE Error? [$Data]\n" unless ($Data =~ /^$NUMb([BN])(.+)$/); my ($num,$bold,$str) = ($1,$2 eq "B" ? 1 : 0, $3); $str =~ s/^[\s>]+//; $str =~ s/[\s<]+$//; printf "%s %-3d $str\n",($bold ? "->" : " "),$num; ######################### # End Hi Score ######################### } elsif ($Code eq $game->{'C'}{'ENDHISCORE'}) { print STDERR "[$PROGNAME] ENDHISCORE Error? [$Data]\n" unless ($Data eq "end"); # Ignore - we are stateless ######################### # ?? ######################### } else { print STDERR "[$PROGNAME] Unknown code: [$Code,$Data]\n"; exit; } } } sub close_client { my ($game) = @_; close ($game->{'handle'}) || die "close: $!"; } ################################################## # Get info from message.h ################################################## sub default_message { my ($game) = @_; print STDERR "[$PROGNAME] Couldn't read message.h, using default values\n"; $game->{'C'}{'PRINTMESSAGE'} = 'A'; $game->{'C'}{'LIST'} = 'B'; $game->{'C'}{'ENDLIST'} = 'C'; $game->{'C'}{'NEWNAME'} = 'D'; $game->{'C'}{'MSG'} = 'E'; $game->{'C'}{'MSGTO'} = 'F'; $game->{'C'}{'JOIN'} = 'G'; $game->{'C'}{'LEAVE'} = 'H'; $game->{'C'}{'SUBWAYFLASH'} = 'I'; $game->{'C'}{'UPDATE'} = 'J'; $game->{'C'}{'DRUGHERE'} = 'K'; $game->{'C'}{'GUNSHOP'} = 'L'; $game->{'C'}{'LOANSHARK'} = 'M'; $game->{'C'}{'BANK'} = 'N'; $game->{'C'}{'QUESTION'} = 'O'; $game->{'C'}{'HISCORE'} = 'Q'; $game->{'C'}{'STARTHISCORE'} = 'R'; $game->{'C'}{'ENDHISCORE'} = 'S'; $game->{'C'}{'BUYOBJECT'} = 'T'; $game->{'C'}{'DONE'} = 'U'; $game->{'C'}{'REQUESTJET'} = 'V'; $game->{'C'}{'PAYLOAN'} = 'W'; $game->{'C'}{'ANSWER'} = 'X'; $game->{'C'}{'DEPOSIT'} = 'Y'; $game->{'C'}{'PUSH'} = 'Z'; $game->{'C'}{'QUIT'} = 'a'; $game->{'C'}{'RENAME'} = 'b'; $game->{'C'}{'NAME'} = 'c'; $game->{'C'}{'SACKBITCH'} = 'd'; $game->{'C'}{'TIPOFF'} = 'e'; $game->{'C'}{'SPYON'} = 'f'; $game->{'C'}{'WANTQUIT'} = 'g'; $game->{'C'}{'CONTACTSPY'} = 'h'; $game->{'C'}{'KILL'} = 'i'; $game->{'C'}{'REQUESTSCORE'} = 'j'; $game->{'C'}{'INIT'} = 'k'; $game->{'C'}{'DATA'} = 'l'; $game->{'C'}{'FIGHTPRINT'} = 'm'; $game->{'C'}{'FIGHTACT'} = 'n'; $game->{'C'}{'TRADE'} = 'o'; $game->{'C'}{'CHANGEDISP'} = 'p'; $game->{'C'}{'NETMESSAGE'} = 'q'; $game->{'C'}{'NONE'} = 'A'; $game->{'C'}{'ASKLOAN'} = 'B'; $game->{'C'}{'COPS'} = 'C'; $game->{'C'}{'ASKBITCH'} = 'D'; $game->{'C'}{'ASKGUN'} = 'E'; $game->{'C'}{'ASKGUNSHOP'} = 'F'; $game->{'C'}{'ASKPUB'} = 'G'; $game->{'C'}{'ASKBANK'} = 'H'; $game->{'C'}{'ASKRUN'} = 'I'; $game->{'C'}{'ASKRUNFIGHT'} = 'J'; $game->{'C'}{'ASKSEW'} = 'K'; $game->{'C'}{'MEETPLAYER'} = 'L'; $game->{'DT'}{'LOCATION'} = 'A'; $game->{'DT'}{'DRUG'} = 'B'; $game->{'DT'}{'GUN'} = 'C'; $game->{'DT'}{'PRICES'} = 'D'; } sub read_message_h { my ($game) = @_; if (open(MESS,$MESSAGE_H)) { while () { $game->{'C'}{$1} = $2 if (/#define\s+C_(\S+)\s+\'(.)\'/); $game->{'DT'}{$1} = $2 if (/#define\s+DT_(\S+)\s+\'(.)\'/); # Make the default_message() routine: #print " \$game->{'C'}{'$1'} = '$2';\n" if (/#define\s+C_(\S+)\s+\'(.)\'/); #print " \$game->{'DT'}{'$1'} = '$2';\n" if (/#define\s+DT_(\S+)\s+\'(.)\'/); } close(MESS); } else { default_message($game); } } ################################################## # Main ################################################## sub main { my %game; # Game data object read_message_h(\%game); $game{'handle'} = client(); game_loop(\%game); close_client(\%game); } main();