File Coverage

blib/lib/Games/AIBots.pm
Criterion Covered Total %
statement 24 763 3.1
branch 0 500 0.0
condition 0 285 0.0
subroutine 8 67 11.9
pod 0 50 0.0
total 32 1665 1.9


line stmt bran cond sub pod time code
1             # $File: //member/autrijus/AIBots/lib/Games/AIBots.pm $ $Author: autrijus $
2             # $Revision: #3 $ $Change: 692 $ $DateTime: 2002/08/17 09:29:13 $
3              
4             require 5.005;
5             package Games::AIBots;
6             $Games::AIBots::VERSION = '0.03';
7              
8 1     1   6462 use strict;
  1         2  
  1         36  
9 1     1   1729 use integer;
  1         12  
  1         6  
10 1     1   615 use Games::AIBot;
  1         2  
  1         32  
11 1     1   5 use File::Glob;
  1         2  
  1         146  
12              
13             =head1 NAME
14              
15             Games::AIBots - An improved clone of A.I.Wars in Perl
16              
17             =head1 VERSION
18              
19             This document describes version 0.03 of Locale::Maketext::Fuzzy.
20              
21             =head1 SYNOPSIS
22              
23             In the command line:
24              
25             % aibots # with Tk, displays the GUI
26             % aibots map bot1 bot2... # run the game
27             % aibots -h # show help
28            
29             Programatically:
30              
31             use Games::AIBots;
32             Games::AIBots::init_sound($flag);
33             Games::AIBots::init_console();
34             # Games::AIBots::init_gui(); # requires Tk
35             Games::AIBots::init_arg($map, @bots);
36             Games::AIBots::init_map();
37             Games::AIBots::do_loop($rounds);
38              
39             =head1 DESCRIPTION
40              
41             This module exists exclusively for the purpose of the F
42             script bundled in the distribution. Please see L for
43             an explanation of the game's mechanics, rules and tips.
44              
45             =cut
46              
47             # =====================
48             # Constants Declaration
49             # =====================
50              
51             # Global variables
52             my ($Console, @Flash, $Top, $Canvas, $DFrame1, $DFrame2, $UFrame,
53             $Btn_play, $Btn_stop, $Btn_tempo, $Btn_watch, $Btn_sound, $Btn_about,
54             $Lbl_info, $Dlg_about, @Snodes);
55             my (@Btn_arg, @Arg, @Bots, $Board, $Running, $Tick, $FirstBot, $Watch,
56             %Buffer, %Wav, $Sound, $Music, %Mines, %Teamvar, $GUI, $MPlayer, %Flash,
57             %Color, %UserCmd, $Continue, $Msglog);
58             my @Mnu_arg = map {''} (0..9);
59 1     1   5 use vars qw/$Mask/;
  1         1  
  1         2573  
60              
61             # Game Settings
62             my ($Max_ammo, $Max_life, $Max_fuel) = (30, 10, 2500);
63             my ($Vault_ammo, $Flag_ammo, $Flag_fuel) = (20, 30, 350);
64             my ($Cloak_fuel, $Cloak_ammo, $Score_adj) = (10, 2, 20);
65             my ($Tick_delay, $Scan_range, $Scan_list) = (
66             160, 5, 'space wall fence flag mine vault friend enemy'
67             );
68             my ($Cols, $Rows, $Tile_width, $Tile_height) = (40, 25, 18, 18);
69             my $Path = $ENV{'Path_AIBots'} || (__FILE__ =~ /^(.+)\.pm$/ ? $1 : '.');
70              
71             # Object list
72             my %Obj = (
73             '1' => 'spawn', '2' => 'spawn', '3' => 'spawn', '4' => 'spawn',
74             '5' => 'spawn', '6' => 'spawn', '7' => 'spawn', '8' => 'spawn',
75             '9' => 'spawn', '@' => 'spawn', '.' => 'space', '#' => 'wall',
76             '+' => 'fence', 'P' => 'flag', 'O' => 'mine', 'A' => 'vault',
77             '^' => 'bot8', 'v' => 'bot2', '<' => 'bot4', '>' => 'bot6',
78             'n' => 'bot8c', 'u' => 'bot2c', '[' => 'bot4c', ']' => 'bot6c',
79             '*' => 'snode', '=' => 'wall', # '*' => 'marsh',
80             );
81              
82             # Damage source table
83             my ($Verb, $DmgS, $DmgN, $BurnS, $BurnN, $ScrS, $ScrN, $CostF, $CostA) = (0..8);
84             my %Weapon = (
85             'bazookah' => ['scorched', 60, 90, 6, 9, 300, 500, 300, 10],
86             'bazookas' => ['splashed', 40, 70, 4, 7, 200, 400 ],
87             'grenadeh' => ['shredded', 40, 70, 9, 12, 300, 500, 200, 5],
88             'grenades' => ['splashed', 20, 50, 1, 4, 100, 200 ],
89             'vaults' => ['impacted', 30, 30, 3, 3, 200, 200 ],
90             'energyh' => ['zapped', 10, 10, 0, 0, 0, 0, 0, 0],
91             'energys' => ['zapped', 20, 20, 2, 2, 100, 100 ],
92             'destructh' => ['shattered', -1, -1, 0, 0, 0, 0 ],
93             'destructs' => ['zapped', -1, -1, 1, 1, 50, 50 ],
94             'mineh' => ['trapped', 50, 50, 5, 5, 400, 400, 0, 2],
95             'flagh' => ['overloaded',50, 50, 0, 0, 0, 0 ],
96             'laserh' => ['burnt', 20, 50, 2, 5, 80, 200, 0, 1],
97             );
98              
99             # Message template
100             my %Msg = (
101             'damage' => "%s is %s by %s %s (%d dmg).",
102             'death' => "%s is killed %s!",
103             'hit' => "%s discovers a %s.",
104             'end' => "*** Game Over ***",
105             'winner' => "*** Winner: %s | Team %s ***",
106             );
107              
108             my @BG = ( map {substr($_, length($Path) + 6, -4) } <$Path/pics/background*.gif> );
109             my $BGidx = 0;
110              
111             $SIG{__DIE__} = $SIG{INT} = sub {$MPlayer->Kill(0) if $MPlayer};
112              
113             # =======================
114             # Initialization Routines
115             # =======================
116              
117             # $success = init_console();
118             sub init_console {
119 0 0   0 0   if ($^O eq 'MSWin32') {
120 0           require Win32::Console;
121 0 0         $Console = Win32::Console->new() or return;
122             }
123             else {
124 0           require Term::ANSIScreen;
125 0 0         $Console = Term::ANSIScreen->new() or return;
126             }
127              
128 0           %Color = (
129             '.' => $main::FG_BROWN, '#' => $main::FG_LIGHTRED,
130             '+' => $main::FG_RED, 'P' => $main::FG_LIGHTMAGENTA,
131             'O' => $main::FG_LIGHTBLUE, 'A' => $main::FG_LIGHTGREEN,
132             '*' => $main::FG_BLACK, '=' => $main::FG_LIGHTRED,
133             );
134 0           $Console->Attr($main::FG_WHITE);
135             }
136              
137             # $success = init_gui();
138             sub init_gui {
139 0     0 0   my @lparam = (-background => '#8484ff', -foreground => 'black');
140              
141             # toplevel window
142 0           $Top = MainWindow->new(
143             -title => "AI Bots v$Games::AIBots::VERSION",
144             -width => ($Cols + 1) * $Tile_width,
145             -height => ($Rows + 1) * $Tile_height,
146             );
147              
148             $Top->bind('', sub {
149 0 0   0     $Running = 0; $MPlayer->Kill(0) if $MPlayer
  0            
150 0           });
151              
152 0           $Top->bind('', \&Games::AIBots::btn_watch);
153 0           $Top->bind('', \&Games::AIBots::btn_stop);
154 0           $Top->bind('', \&Games::AIBots::btn_sound);
155 0           $Top->bind('', \&Games::AIBots::btn_tempo);
156 0     0     $Top->bind('', sub {&Games::AIBots::btn_tempo for (1..2)});
  0            
157 0           $Top->bind('', \&Games::AIBots::btn_about);
158 0           $Top->bind('', \&Games::AIBots::btn_play);
159 0     0     $Top->bind('', sub { eval{ $Top->Close() }; exit });
  0            
  0            
  0            
160              
161             # cache pictures
162 0           foreach (<$Path/pics/*.gif>) {
163 0           my $pic = substr($_, length($Path) + 6, -4);
164 0           $Obj{$pic} = $pic;
165 0           $Top->Photo($pic, '-format' => 'gif', '-file' => $_);
166             }
167              
168 0           $Dlg_about = $Top->DialogBox(
169             @lparam,
170             -title => 'Anarchistic Independent Robots',
171             -default_button => 'Anarchy!',
172             -buttons => ['Anarchy!'],
173             );
174              
175 0           $Dlg_about->Label(
176             @lparam,
177             -text => "AI Bots v$Games::AIBots::VERSION",
178             -font => ['helvetica', 32, 'bold']
179             )->pack(-side => 'top', -fill => 'x', -expand => 'x');
180 0           $Dlg_about->resizable(0, 0);
181              
182 0           my $frame = $Dlg_about->Frame(@lparam)->pack(-side => 'top', -fill => 'x');
183              
184 0           $frame->Label(
185             @lparam,
186             -font => ['helvetica', 12, 'bold'],
187             -text => "Developed by Autrijus Tang (autrijus\@autrijus.org).\n".
188             "Idea from A.I.Wars (http://www.tacticalneurotics.com/).\n".
189             "This game is free software under the Perl License.\n"
190             )->pack(-side => 'right', -expand => 'y');
191              
192             # window layout
193 0           $Top->resizable(0, 0);
194 0           $Top->Icon('-image' => 'aibots');
195              
196 0           $DFrame2 = $Top->Frame(@lparam, -bd => 3, -relief => 'ridge')->pack(-side => 'bottom', -fill => 'x');
197 0           $DFrame1 = $Top->Frame(@lparam, -bd => 3, -relief => 'ridge')->pack(-side => 'bottom', -fill => 'x');
198 0           $UFrame = $Top->Frame(@lparam, -bd => 3, -relief => 'ridge')->pack(-side => 'top', -fill => 'x');
199              
200 0           my @nparam = (-background => 'black', -foreground => '#8484ff', -activebackground => '#202050', -activeforeground => '#8484ff');
201              
202 0 0         $Btn_sound = $UFrame->Button(@nparam, -relief => 'ridge', -image => ($Sound ? 'sound' : 'mute'), -command => \&Games::AIBots::btn_sound, -state => ($Sound ? 'normal' : 'disabled'))->pack(-side => 'right', -padx => 2);
    0          
203 0           $Btn_tempo = $UFrame->Button(@nparam, -relief => 'ridge', -image => 'normal', -command => \&Games::AIBots::btn_tempo)->pack(-side => 'right', -padx => 2);
204 0           $Btn_stop = $UFrame->Button(@nparam, -relief => 'groove', -image => 'stop', -command => \&Games::AIBots::btn_stop, -state => 'disabled')->pack(-side => 'right', -padx => 2);
205 0           $Btn_play = $UFrame->Button(@nparam, -relief => 'groove', -image => 'play', -command => \&Games::AIBots::btn_play)->pack(-side => 'right', -padx => 2);
206              
207 0           $Btn_watch = $UFrame->Button(
208             @nparam,
209             -relief => 'ridge',
210             -font => ['helvetica', 10, 'bold'],
211             -disabledforeground => '#8484ff',
212             -highlightthickness => 0,
213             -borderwidth => 0,
214             -bd => 3, -width => 10,
215             -command => \&Games::AIBots::btn_watch
216             )->pack(-side => 'left', -padx => 2, -ipadx => 0, -ipady => 0);
217              
218 0           $Lbl_info = $UFrame->Label(-foreground => 'black', -background => '#8484ff', -font => ['Courier', 9, 'bold'], -anchor => 'w')->pack(-side => 'left', -fill => 'x', -expand => 'x', -padx => 2);
219              
220 0           my @bparam = (-background => '#a04444', -font => ['helvetica', 8]);
221 0           $Top->Balloon(@bparam)->attach($Btn_sound, -balloonmsg => "[S]ound");
222 0           $Top->Balloon(@bparam)->attach($Btn_watch, -balloonmsg => "[W]atch");
223 0           $Top->Balloon(@bparam)->attach($Btn_tempo, -balloonmsg => "[T]empo");
224 0           $Top->Balloon(@bparam)->attach($Btn_stop, -balloonmsg => "[H]alt");
225 0           $Top->Balloon(@bparam)->attach($Btn_play, -balloonmsg => "[P]lay/Pause");
226              
227             $Btn_arg[0] = $DFrame1->Button(
228             @nparam,
229             -image => 'wall',
230             -relief => 'ridge',
231             -state => 'normal',
232             -command => sub {
233 0     0     $BGidx = ($BGidx + 1) % scalar @BG;
234 0           $Canvas->itemconfigure('background', -image => $BG[$BGidx]);
235             # return unless $Board;
236             # foreach my $y (1..$Rows) {
237             # print "\n", substr($Board, ($y - 1) * $Cols, $Cols);
238             # }
239             # print "\r";
240             }
241 0           )->pack(-side => 'left', -padx => 2);
242 0           $Top->Balloon(@bparam)->attach($Btn_arg[0], -balloonmsg => "Change background");
243              
244 0           $Mnu_arg[0] = $DFrame1->Optionmenu(
245             -font => ['helvetica', 9, 'bold'],
246             -background => '#a04444',
247             -foreground => '#8484ff',
248             -variable => \$Arg[0],
249             -options => ['', map {substr($_, length($Path) + 6, -4)} <$Path/maps/*.map>],
250             -command => sub {
251 0     0     ding('select');
252 0 0 0       init_map() if $GUI and not defined $Running;
253 0           Games::AIBots::init_arg();
254             },
255 0           )->pack(-side => 'left');
256              
257             # $Mnu_arg[0]->bind('', sub {
258             # $Mnu_arg[0]->configure(-options => ['', map {substr($_, length($Path) + 6, -4)} <$Path/maps/*.map>]);
259             # });
260              
261 0           $Top->Balloon(@bparam)->attach($Mnu_arg[0], -balloonmsg => "Select Map");
262              
263 0           my @mparam = (-font => ['helvetica', 9],
264             -background => '#a04444',
265             -foreground => '#8484ff',
266 0     0     -options => ['', map {substr($_, length($Path) + 6, -4)} <$Path/bots/*.bot>],
267             -command => sub {Games::AIBots::init_arg()}
268 0           );
269              
270 0           foreach my $arg (1..9) {
271             $Btn_arg[$arg] = ($arg < 5 ? $DFrame1 : $DFrame2)->Button(
272             @nparam,
273             -image => 'bot8',
274             -relief => 'ridge',
275             -state => 'disabled',
276             -command => sub {
277 0     0     ding('select');
278 0           $Watch->{'id'} = $arg;
279 0           Games::AIBots::bot_watch($Bots[$arg-1]);
280 0           ding('toggle');
281             }
282 0 0         )->pack(-side => 'left', -padx => 2);
283             $Top->bind("", sub {
284 0     0     $Watch->{'id'} = $arg;
285 0           Games::AIBots::bot_watch($Bots[$arg-1]);
286 0           ding('toggle');
287 0           });
288              
289 0           $Top->Balloon(@bparam)->attach($Btn_arg[$arg], -balloonmsg => "Watch #$arg");
290              
291 0 0         $Mnu_arg[$arg] = ($arg < 5 ? $DFrame1 : $DFrame2)->Optionmenu(@mparam, -variable => \$Arg[$arg])->pack(-side => 'left');
292             $Mnu_arg[$arg]->bind('', sub {
293 0     0     $Mnu_arg[$arg]->configure(-options => ['', map {substr($_, length($Path) + 6, -4)} <$Path/bots/*.bot>]);
  0            
294 0           });
295 0           $Top->Balloon(@bparam)->attach($Mnu_arg[$arg], -balloonmsg => "Select #$arg");
296             }
297              
298 0           $Btn_about = $DFrame2->Button(
299             @nparam,
300             -bd => 3, -font => ['helvetica', 10],
301             -relief => 'ridge',
302             -image => 'anarchy',
303             -command => \&Games::AIBots::btn_about,
304             )->pack(-side => 'right', -padx => 2);
305 0           $Top->Balloon(@bparam)->attach($Btn_about, -balloonmsg => "About AIBots");
306              
307             # board canvas
308 0           $Canvas = $Top->Canvas(
309             -width => ($Cols + 1) * $Tile_width - 6,
310             -height => ($Rows + 1) * $Tile_height - 6,
311             -relief => 'ridge',
312             )->pack(-side => 'top');
313              
314 0           return $GUI = 1;
315             }
316              
317             # $success = init_sound($music)
318             sub init_sound {
319 1     1   8 no strict 'subs';
  1         2  
  1         5599  
320 0     0 0   $Sound = 1;
321 0 0         return unless ($^O eq 'MSWin32');
322              
323 0           require Win32::Sound;
324 0           require Win32::Process;
325              
326             # my $vol = Win32::Sound::Volume();
327             # Win32::Sound::Volume("0%");
328              
329 0           foreach (<$Path/wavs/*.wav>) {
330 0           $Wav{substr($_, length($Path) + 6, -4)} = $_;
331             # Win32::Sound::Play($_, 1);
332             }
333              
334             # Win32::Sound::Volume($vol);
335 0 0         return unless $_[0];
336              
337 0           foreach my $path (split(/;+/, $ENV{'Path'})) {
338 0 0         $path .= "\\" if substr($path, -1) ne "\\";
339 0 0         if (-e $path.'mplay32.exe') {
340 0           Win32::Process::Create($MPlayer,
341             $path.'mplay32.exe', "mplay32 /play /rewind $Path\\wavs\\aibots.mid",
342             0, (NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW), "."
343             );
344 0           $Music = 1;
345             }
346             }
347             }
348              
349             sub init_arg {
350 0     0 0   my $sum;
351              
352 0 0         if (@_) {
353 0   0       $Arg[$_] = shift(@_) || '' for (0..(scalar @Mnu_arg));
354 0           return;
355             }
356              
357 0           for ((2..(scalar @Mnu_arg))) {
358 0 0 0       if ($Arg[$_] and !$Arg[$_-1]) {
359 0           $Arg[$_-1] = $Arg[$_];
360 0           $Arg[$_] = '';
361             }
362             }
363              
364 0           for (1..(scalar @Mnu_arg)) {
365 0 0         $sum++ if $Arg[$_];
366             }
367              
368 0 0 0       $Btn_play->configure(-state => (($Arg[0] and ($sum >= 2)) ? 'normal' : 'disabled')) if $GUI;
    0          
369             }
370              
371             # $botcount = init_game(@bots)
372             sub init_game {
373 0     0 0   @Bots = %Buffer = %Mines = ();
374 0           $Running = $Tick = 0;
375              
376 0           foreach my $file (@Arg[1..$#Arg]) {
377 0 0         next unless $file;
378 0           my $bot = Games::AIBot->new("$Path/bots/$file.bot");
379 0           push @Bots, $bot;
380              
381 0           $bot->{'id'} = scalar @Bots;
382 0           $bot->{'name'} = ucfirst($file);
383 0           $bot->{'pic'} = lc($file);
384 0           $bot->{'burn'} = 1;
385 0           $bot->{'score'} = 0;
386 0           $bot->{'h'} = int(rand(4) + 1) * 2;
387 0           $bot->{'max_fuel'} = $bot->{'fuel'} = $Max_fuel;
388 0           $bot->{'max_ammo'} = $bot->{'ammo'} = $Max_ammo;
389 0           $bot->{'max_life'} = $bot->{'life'} = $Max_life;
390 0           $bot->{'lastcmd'} = '';
391              
392 0           @{$bot}{'shield', 'cloak', 'laymine'} = (0,0,0);
  0            
393              
394 0           do { @{$bot}{'x', 'y'} = (int(rand($Cols) + 1), int(rand($Rows) + 1)) }
  0            
  0            
395 0 0         while (obj_at(@{$bot}{'x', 'y'}) ne ((index($Board, $bot->{'id'}) > -1) ? $bot->{'id'} : (index($Board, '@') > -1) ? '@' : '.'));
    0          
396              
397 0           bot_draw($bot);
398             }
399              
400 0 0         if ($GUI) {
401 0           $_->configure(-state => 'disabled', -background => '#8484ff') foreach @Mnu_arg;
402 0           $Btn_watch->configure(-state => 'normal');
403 0 0         $Btn_arg[$_]->configure(-state => ($Arg[$_] ? 'normal' : 'disabled')) for (1..$#Btn_arg);
404             }
405              
406 0           $Board =~ s/[\@\*\d]/./g;
407 0           $FirstBot = int(rand(scalar @Bots));
408              
409 0 0         @{$Watch}{'x', 'y', 'id'} = @{$Bots[$Mask ? $#Bots : $FirstBot]}{'x', 'y', 'id'};
  0            
  0            
410 0           obj_draw(@{$Watch}{'x', 'y'}, 'watch', '_watch', 1);
  0            
411 0           bot_watch($Bots[$Watch->{'id'}]);
412              
413 0 0         if ($Console) {
414 0           $Console->Cursor(43, 22);
415 0           $Console->Write('[h]alt [q]uit [s]ound [p]lay/pause');
416 0           $Console->Attr($main::BG_BLUE);
417 0           $Console->Attr($main::FG_YELLOW);
418 0           $Console->Cursor(40, 23);
419 0           $Console->Write(' Autrijus Tang ');
420              
421 0           $Console->Attr($main::FG_WHITE);
422             }
423 0           return scalar @Bots;
424             }
425              
426             # $map = init_map($mapname)
427             sub init_map {
428 0     0 0   %Flash = @Snodes = ();
429 0           $Board = '.' x ($Cols * $Rows);
430              
431 0 0         if ($GUI) {
    0          
432 0           $Canvas->delete('all');
433 0           $Canvas->createImage(
434             ($Cols + 1) * $Tile_width / 2,
435             ($Rows + 1) * $Tile_height / 2,
436             -image => $BG[$BGidx],
437             -tag => 'background',
438             );
439 0           $Btn_watch->configure(-state => 'disabled');
440 0           $_->configure(-state => 'disabled') foreach @Btn_arg;
441 0           $Btn_arg[0]->configure(-state => 'normal');
442             }
443             elsif ($Console) {
444 0           $Console->Cls;
445 0           $Console->Cursor(40, 0);
446 0           $Console->Attr($main::BG_BLUE);
447 0           $Console->Attr($main::FG_YELLOW);
448 0           $Console->Write(" -=[AI Bots v$Games::AIBots::VERSION]=- ");
449 0           $Console->Attr($main::FG_WHITE);
450             }
451              
452 0 0         return unless $Arg[0];
453 0 0         init_rndmap($Arg[0]) if $Arg[0] eq 'random';
454              
455 0 0         open _, "$Path/maps/$Arg[0].map" or die "$!: $Path/maps/$Arg[0].map";
456 0           my $y = 0;
457 0           while (my $line = <_>) {
458 0           chomp $line;
459 0 0         next if substr($line, 0, 2) eq '# ';
460              
461 0 0         if ($line =~ /^=background[\s\t]+(.+)/) {
    0          
    0          
    0          
    0          
462 0 0         $Canvas->itemconfigure('background', -image => $1) if $GUI;
463             }
464             elsif ($line =~ /^=bot(\d)[\s\t]+(.+)/) {
465 0 0         $Arg[$1] = $2 if -e "$Path/bots/$2.bot";
466             }
467             elsif ($line =~ /^=snode[\s\t]+(\d+)[\s\t]*,[\s\t]*(\d+)/) {
468 0           push @Snodes, {'x' => $1, 'y' => $2};
469 0           obj_draw($1, $2, '*', '_snode');
470             }
471             elsif ($line =~ /^=sound[\s\t]+(.+)/) {
472 0           ding($1);
473             }
474             elsif (length($line) eq $Cols) {
475 0           $y++;
476 0           foreach my $x (1 .. $Cols) {
477 0           my $char = substr($line, $x-1, 1);
478 0 0         obj_draw($x, $y, $char, ($Obj{$char} =~ /^(?:spawn|snode)/) ? "_$Obj{$char}" : '') if (exists($Obj{$char}));
    0          
479 0 0         push @Snodes, {'x' => $x, 'y' => $y} if $Obj{$char} eq 'snode';
480             }
481             }
482             }
483 0           close _;
484              
485 0           return $Arg[0];
486             }
487              
488             # init_rndmap($mapname)
489             sub init_rndmap {
490 0     0 0   my $rnd = 'PAO####+++' . ('.' x 120);
491              
492 0           open _, ">$Path/maps/$_[0].map";
493 0           foreach my $y (1 .. $Rows) {
494 0           foreach my $x (1 .. $Cols) {
495 0           print _ substr($rnd, int(rand(length($rnd))), 1);
496             }
497 0           print _ "\n";
498             }
499 0           close _;
500             }
501              
502             # ===========================================================================
503             # Button callbacks
504             # ===========================================================================
505              
506             sub btn_tempo {
507 0     0 0   $Tick_delay = (((sqrt(sqrt($Tick_delay / 10)) + 1) % 3) + 1) ** 4 * 10;
508 0 0         $Btn_tempo->configure(
509             -image => ('slow', 'fast', 'normal')[
510             sqrt(sqrt($Tick_delay / 10)) % 3
511             ]
512             ) if $GUI;
513              
514 0           ding('toggle');
515             }
516              
517             sub btn_play {
518 0 0   0 0   (ding('game_begin'), init_map(), init_game()) unless defined($Running);
519 0           $Running = not $Running;
520              
521 0 0         if ($GUI) {
522 0           $Btn_stop->configure(-state => 'normal');
523 0 0         $Btn_play->configure(-image => $Running ? 'pause' : 'play');
524 0 0         $Top->after($Tick_delay / (scalar @Bots), \&Games::AIBots::tick_bot) if $Running;
525             }
526             }
527              
528             sub btn_stop {
529 0     0 0   $Running = 0;
530 0 0         $Btn_stop->configure(-state => 'disabled') if $GUI;
531              
532 0           $_->{'fuel'} = 0 foreach (@Bots);
533 0           btn_play();
534             }
535              
536             sub btn_watch {
537 0     0 0   $Watch->{'id'} = ($Watch->{'id'} % scalar @Bots) + 1;
538 0           bot_watch($Bots[$Watch->{'id'}]);
539              
540 0           ding('toggle');
541             }
542              
543             sub btn_sound {
544             # return unless defined $Sound;
545              
546 0     0 0   $Sound = not $Sound;
547 0 0         $Btn_sound->configure(-image => ($Sound ? 'sound' : 'mute')) if $GUI;
    0          
548              
549 0 0         if ($Sound) {
550 0           init_sound($Music);
551             }
552             else {
553 0 0         $MPlayer->Kill(0) if $MPlayer;
554             }
555              
556 0           ding('toggle');
557             }
558              
559             sub btn_about {
560 0     0 0   my $tmp = $Running;
561              
562 0           $Running = 0;
563 0           ding('anarchy');
564 0           $Dlg_about->Show();
565 0 0         if ($Running = $tmp) {
566 0           $Running = 0;
567 0           btn_play();
568             }
569             }
570              
571             # ===========================================================================
572             # Drawing routines
573             # ===========================================================================
574              
575             sub obj_draw {
576 0     0 0   my ($x, $y, $type, $tag, $flash, $step) = @_;
577              
578 0 0         if ($GUI) {
579 0 0 0       $Canvas->createImage(
      0        
      0        
      0        
580             ($x * $Tile_width), ($y * $Tile_height),
581             '-image' => $Obj{$type},
582             '-tags' => [$type.($step || ''), $tag || "$x:$y"],
583             ) if (defined($Obj{$type}) and $Obj{$type} ne 'space' and $type ne '=');
584             }
585              
586 0           obj_set($x, $y, $type, $flash);
587             }
588              
589             sub obj_erase {
590 0     0 0   my ($x, $y) = @_;
591              
592 0 0         $Canvas->delete("$x:$y") if $GUI;
593 0           obj_set($x, $y, '.');
594             }
595              
596             sub obj_at {
597 0 0   0 0   my ($x, $y) = @_ or return;
598 0 0 0       return '=' if $x < 1 or $y < 1 or $x > $Cols or $y > $Rows;
      0        
      0        
599 0           return substr($Board, ($y - 1) * $Cols + $x - 1, 1);
600             }
601              
602             sub obj_flash {
603 0     0 0   my ($obj, $x, $y, $step) = @_;
604 0 0         $obj .= $step if $step;
605              
606 0           ding('flash', $obj);
607              
608 0 0 0       if ($GUI and $Top->state eq 'normal') {
609 0 0         if (exists($Flash{$obj})) {
610 0           $Canvas->itemconfigure($obj, -state => 'normal');
611 0           obj_move(@{$Flash{$obj}}, $x, $y, $_[0], $obj, 1);
  0            
612             }
613             else {
614 0           obj_draw($x, $y, $_[0], '_flash', 1, $_[3]);
615             }
616 0           $Flash{$obj} = [$x, $y];
617             }
618             }
619              
620             sub obj_move {
621 0     0 0   my ($ox, $oy, $nx, $ny, $obj, $tag, $flash, $onwatch) = @_;
622              
623 0 0         if (!$flash) {
624 0 0 0       if ($Mask and $onwatch) {
625 0           local $Mask;
626 0 0         obj_set($ox, $oy, '.') unless obj_at($ox, $oy) eq 'O';
627             }
628             else {
629 0 0         obj_set($ox, $oy, '.') unless obj_at($ox, $oy) eq 'O';
630             }
631              
632 0 0 0       if ($Console and $onwatch) {
633 0           $Console->Attr($main::FG_LIGHTCYAN);
634 0           obj_set($nx, $ny, $obj);
635 0           $Console->Attr($main::FG_WHITE);
636             }
637             else {
638 0 0 0       bot_fill($Bots[-1]) unless !$Mask or $Bots[-1]{dead};
639 0           obj_set($nx, $ny, $obj);
640             }
641             }
642              
643 0 0 0       $Canvas->move($tag, ($nx-$ox) * $Tile_width, ($ny-$oy) * $Tile_height)
      0        
644             unless (!$GUI or $nx == $ox and $ny == $oy);
645              
646 0 0 0       if (!$Buffer{$tag} or $Buffer{$tag} ne $Obj{$obj}) {
647 0 0         $Canvas->itemconfigure($tag, '-image' => $Obj{$obj}) if $GUI;
648 0           $Buffer{$tag} = $Obj{$obj};
649             }
650             }
651              
652             sub obj_set {
653 0     0 0   my ($x, $y, $obj, $flash) = @_;
654              
655 0 0         if ($flash) {
656 0           push @Flash, ($x, $y, obj_at($x, $y));
657             }
658             else {
659 0 0         substr($Board, ($y - 1) * $Cols + $x - 1, 1) = $obj unless $flash;
660             }
661              
662 0 0         if ($Console) {
663 0 0         if ($Mask) {
664 0           my $bot = $Bots[$Watch->{id}-1];
665 0 0 0       return if $Obj{$obj} eq 'flag' or $Obj{$obj} eq 'mine';
666             return unless
667 0 0 0       ($x == $bot->{x} and $y == $bot->{y}) or
      0        
      0        
      0        
      0        
      0        
      0        
668             ($x == $bot->{enemy_x} and $y == $bot->{enemy_y}) or
669             ($x == $bot->{friend_x} and $y == $bot->{friend_y}) or
670             ($x == $bot->{bumped_x} and $y == $bot->{bumped_y});
671             }
672              
673 0           $Console->Cursor($x - 1, $y - 1, 0, 0);
674 0 0         $Console->Attr($Color{$obj}) if exists($Color{$obj});
675 0 0         $Console->Write($flash ? ' ' : $Obj{$obj} eq 'spawn' ? '.' : $obj);
    0          
676 0 0         $Console->Attr($main::FG_WHITE) if exists($Color{$obj});
677             }
678             }
679              
680             # ============
681             # Bot Handling
682             # ============
683              
684             sub bot_draw {
685 0     0 0   my $bot = shift;
686              
687 0           obj_draw(@{$bot}{'x', 'y'}, bot_char($bot), "_bot$bot->{'id'}");
  0            
688 0 0         obj_draw(@{$bot}{'x', 'y'}, ($bot->{'shield'} ? 'shield' : 'noshield'), "_bots$bot->{'id'}", 1);
  0            
689             }
690              
691             sub bot_char {
692 0     0 0   my $bot = shift;
693 0 0         my $obj = $bot->{'h'} . ($bot->{'cloak'} ? 'c' : '');
694 0 0         my $char = ($bot->{'cloak'} ? qw/n ] u [/ : qw/^ > v {'h'})];
695 0   0       $Obj{$char} = $Obj{$bot->{'pic'}.$obj} || "bot$obj";
696              
697 0           return $char;
698             }
699              
700             sub bot_at {
701 0     0 0   my ($x, $y) = @_;
702              
703 0           foreach my $bot (@Bots) {
704 0 0         next if $bot->{'dead'};
705 0 0 0       return $bot if ($bot->{'x'} == $x and $bot->{'y'} == $y);
706             }
707             }
708              
709             sub bot_id {
710 0 0   0 0   my $bot = shift or return;
711 0           return join('-', @{$bot}{'name', 'id'});
  0            
712             }
713              
714             sub bot_watch {
715 0     0 0   my $bot = shift;
716 0 0 0       return unless $bot and ($bot->{'id'} == $Watch->{'id'} or $Console);
      0        
717              
718 0           obj_move(@{$Watch}{'x', 'y'}, @{$bot}{'x', 'y'}, 'watch', '_watch', 1);
  0            
  0            
719              
720 0           my $msg = sprintf("Score:%d Ammo:%d Life:%d Fuel:%d [%s]", @{$bot}{qw/score ammo life fuel lastcmd/});
  0            
721              
722 0 0 0       if ($GUI and !@_) {
723 0           $Btn_watch->configure(-text => bot_id($bot));
724 0           $Lbl_info->configure(-text => $msg);
725             }
726             else {
727 0           $msg = '{'.bot_id($bot).'} '.$msg;
728 0 0         if ($Console) {
729 0 0         return if $Mask;
730              
731 0 0         if ($bot->{'id'} == $Watch->{'id'}) {
732 0           $Console->Attr($main::FG_LIGHTCYAN);
733             }
734              
735 0           $Console->Cursor(40, $bot->{'id'} * (4 - int((scalar @Bots) / 3)));
736 0           $Console->Write(substr($msg, 0, index($msg, ' Fuel')).(' ' x
737             (40 - index($msg, ' Fuel'))));
738 0           $Console->Cursor(42, 1 + $bot->{'id'} * (4 - int((scalar @Bots) / 3)));
739 0           $Console->Write(substr($msg, index($msg, 'Fuel')).(' ' x (70 - length($msg))));
740 0           $Console->Attr($main::FG_WHITE);
741             }
742             else {
743 0           print $msg, (' ' x (79 - length($msg))), "\r";
744             }
745             }
746              
747 0           @{$Watch}{'x', 'y'} = @{$bot}{'x', 'y'};
  0            
  0            
748             }
749              
750             sub bot_fill {
751 0     0 0   my $bot = shift;
752              
753 0           @{$bot}{qw/enemy_x enemy_y enemy_h enemy_l
  0            
754             snode_x snode_y
755             friend_x friend_y friend_h friend_l botcount/} = ();
756              
757 0           foreach my $other (@Bots) {
758 0 0         next if $other->{'dead'};
759 0           $bot->{'botcount'}++;
760 0 0 0       next if $other->{'id'} == $bot->{'id'} or $other->{'cloak'};
761              
762 0           my $rel = bot_scan($bot, @{$other}{'x', 'y'});
  0            
763              
764 0 0         if ($bot->_nearst($rel) > $bot->_distance(@{$other}{'x', 'y'})) {
  0            
765 0           @{$bot}{"${rel}_x", "${rel}_y", "${rel}_h", "${rel}_l"}
  0            
766 0           = @{$other}{qw/x y h life/};
767             }
768             }
769              
770 0           foreach my $snode (@Snodes) {
771 0 0         if ($bot->_nearst('snode') > $bot->_distance(@{$snode}{'x', 'y'})) {
  0            
772 0           @{$bot}{'snode_x', 'snode_y'} = @{$snode}{'x', 'y'};
  0            
  0            
773             }
774             }
775              
776 0 0         if ($bot->_onnode()) {
777 0 0 0       $bot->{'fuel'}++ if $bot->{'fuel'} < $Max_fuel and !(($Tick / scalar @Bots) % 5);
778 0 0 0       $bot->{'ammo'}++ if $bot->{'ammo'} < $Max_ammo and !(($Tick / scalar @Bots) % 10);
779 0 0 0       $bot->{'life'}++ if $bot->{'life'} < $Max_life and !(($Tick / scalar @Bots) % 15);
780             }
781              
782 0           return $bot;
783             }
784              
785             sub bot_damage {
786 0     0 0   my ($bot, $type, $owner, $adj) = @_;
787 0 0         return if $bot->{'dead'}; # no good whipping a dead horse
788              
789 0 0         $type .= 'h' unless exists($Weapon{$type});
790 0 0 0       my $dmg = $Weapon{$type}[$bot->{'shield'} ? $DmgS : $DmgN] * $Max_life / 100 + ($adj || 0);
791              
792 0 0         return unless $dmg > 0;
793              
794 0 0         display('damage', bot_id($bot), $Weapon{$type}[$Verb], ($owner ? bot_id($owner)."'s" : 'a'), substr($type, 0, -1), $dmg);
795              
796 0           $bot->{'life'} -= $dmg;
797 0 0         $bot->{'burn'} += $Weapon{$type}[$bot->{'shield'} ? $BurnS : $BurnN];
798 0 0 0       $owner->{'score'} += $Weapon{$type}[$bot->{'shield'} ? $ScrS : $ScrN] -
    0 0        
    0 0        
    0 0        
799             (($adj || 0) * $Weapon{$type}[$bot->{'shield'} ? $ScrS : $ScrN]
800             / $Weapon{$type}[$bot->{'shield'} ? $DmgS : $DmgN])
801             if ($owner and $owner->{'id'} != $bot->{'id'} and !$owner->{'team'} or $owner->{'team'} ne $bot->{'team'});
802              
803 0 0         if ($bot->{'life'} <= 0) {
804 0           $bot->{'dead'} = 1;
805 0           $bot->{'lastcmd'} = '** Dead **';
806 0 0         display('death', bot_id($bot), ($owner ? ('by '. ($owner == $bot ? 'itself' : bot_id($owner))) : ''));
    0          
807              
808 0 0         $Canvas->delete("_bot$bot->{'id'}") if $GUI;
809 0 0         $Canvas->delete("_bots$bot->{'id'}") if $GUI;
810 0 0         obj_draw(@{$bot}{'x', 'y'}, ($type eq 'destructh') ? '.' : 'P');
  0            
811             }
812              
813 0           return $dmg;
814             }
815              
816             sub bot_hit {
817 0     0 0   my ($bot, $obj) = @_;
818              
819 0           obj_erase(@{$bot}{'x', 'y'});
  0            
820 0           display('hit', bot_id($bot), $obj);
821              
822 0 0         if ($obj eq 'mine') {
    0          
    0          
823 0           obj_flash('explode', @{$bot}{'x', 'y'});
  0            
824 0           bot_damage($bot, $obj, $Mines{join(':', @{$bot}{'x', 'y'})});
  0            
825             }
826             elsif ($obj eq 'flag') {
827 0 0         if ($bot->{'life'} == $Max_life) {
828 0           obj_flash('explode', @{$bot}{'x', 'y'});
  0            
829 0           bot_damage($bot, $obj);
830             }
831             else {
832 0           $bot->{'life'} = $Max_life;
833 0           $bot->{'ammo'} += $Flag_ammo;
834 0           $bot->{'fuel'} += $Flag_fuel;
835 0           ding('hit', $obj);
836             }
837             }
838             elsif ($obj eq 'vault') {
839 0           $bot->{'ammo'} += $Vault_ammo;
840 0           ding('hit', $obj);
841             }
842             }
843              
844             sub bot_ready {
845 0     0 0   my ($bot, $type) = @_;
846 0 0         $type .= 'h' unless exists($Weapon{$type});
847              
848 0   0       return ($bot->{'fuel'} >= $Weapon{$type}[$CostF]
849             and $bot->{'ammo'} >= $Weapon{$type}[$CostA]);
850             }
851              
852             sub bot_pay {
853 0     0 0   my ($bot, $type, $amount) = @_;
854 0 0         $amount *= $bot->{'burn'} if $type eq 'fuel';
855              
856 0 0         return if $amount > $bot->{$type};
857 0           return $bot->{$type} -= $amount;
858             }
859              
860             sub bot_scan {
861 0     0 0   my ($bot, $x, $y) = @_;
862              
863 0 0         if (my $other = bot_at($x, $y)) {
864 0 0 0       return (($bot->{'team'} and $other->{'team'} eq $bot->{'team'}) ? 'friend' : 'enemy');
865             }
866             else {
867 0           return $Obj{obj_at($x, $y)};
868             }
869             }
870              
871              
872             # ===========================================================================
873             # Movement Handling
874             # ===========================================================================
875              
876             sub tick_bot {
877 1     1   10 no strict;
  1         2  
  1         119  
878 0     0 0   my $bot = $Bots[($FirstBot + $Tick++) % scalar @Bots];
879              
880 0 0         if ($GUI) {
    0          
881 0 0         $Top or exit;
882 0           $Canvas->itemconfigure('_flash', -state => 'hidden');
883 0 0         $Top->configure('-title' => "AI Bots v$Games::AIBots::VERSION (Tick: $Tick)") if $Running;
884             }
885             elsif ($Console) {
886 1     1   6 no integer;
  1         2  
  1         8  
887 0           $Console->Cursor(43, 21);
888 0           $Console->Write(sprintf("[1-%1d][w]atch [t]empo: %4s [%5d]",
889             scalar @Bots,
890             ('slow', 'fast', 'norm')[
891             sqrt(sqrt($Tick_delay / 10)) % 3
892             ]
893             , $Tick));
894              
895 0 0         select(undef, undef, undef,
896             ((sqrt($Tick_delay / 10)) / (scalar @Bots) / 12)
897             ) unless $Mask;
898             }
899              
900 0 0         tick_check() or return;
901              
902 0           tick_missile($_) foreach (@{$bot->{'missiles'}});
  0            
903              
904 0 0         if ($bot->{'dead'}) {
905 0 0         bot_watch($bot) unless $Continue;
906 0           goto &tick_bot;
907             }
908              
909 0 0 0       $bot->{'fuel'}-- if $bot->{'fuel'} > 0 and !(($Tick / scalar @Bots) % 10);
910              
911 0           my $old = { %{bot_fill($bot)} };
  0            
912              
913 0 0         unless (@{$bot->{'queue'}}) {
  0            
914 0           my @cmds;
915            
916 0 0 0       if ($Console and $Mask and $bot->{'id'} == $Watch->{'id'}) {
      0        
917 0           @cmds = user_tick($bot);
918             }
919             else {
920 0           @cmds = $bot->tick();
921             }
922              
923 0           my @passable = qw/pic stack state var queue author team name line/;
924              
925 0           @{$old}{@passable} = @{$bot}{@passable};
  0            
  0            
926 0           %{$bot} = %{$old};
  0            
  0            
927             # $bot->{'var'}{'teamvar'} = $Teamvar{$bot->{'team'}};
928 0           $bot->{'bumped'} = '';
929 0           $bot->{'bumped_x'} = 0;
930 0           $bot->{'bumped_y'} = 0;
931 0           $bot->{'found'} = '';
932              
933 0 0         push @{$bot->{'queue'}}, @cmds if @cmds;
  0            
934             }
935              
936 0           $_ = lc(shift @{$bot->{'queue'}});
  0            
937             # print $bot->{'id'},": $_\n";
938              
939 0 0 0       if (/^(scan)[\s\t]+(longrange|front|right|left|perimeter|cross|corner)$/
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
940             || /^(turn)[\s\t]+(left|right)$/
941             || /^(move)[\s\t]+(forward|backward)$/
942             || /^(fire)[\s\t]+(laser|bazooka|energy)$/
943             || /^(fire)[\s\t]+(grenade)([\s\t]+[\d\'\"]+)?$/
944             || /^(scan)[\s\t]+(gps)[\s\t]+(\d+)[\s\t]*,[\s\t]*(\d+)$/
945             || /^(scan)[\s\t]+(position|relative) ([12346789])$/
946             || /^(disable)[\s\t]+(shield|laymine|cloak)$/
947             || /^(enable)[\s\t]+(shield|laymine|cloak)$/
948             || /^(attempt)[\s\t]+(repair|destruct)$/
949             || /^(beam)[\s\t]+(command|fuel|ammo)\s+(.+)$/
950             || /^(toggle)[\s\t]+(shield|laymine|cloak)$/
951             ) {
952 0           $bot->{'lastcmd'} = join(' ', ucfirst($1), substr($_, $-[2]));
953 0 0         &{"cmd_$1"}($bot, $2, $3, $4) or @{$bot->{'queue'}} = grep {$_ eq $bot->{'lastcmd'}} @{$bot->{'queue'}};;
  0            
  0            
  0            
  0            
954 0 0         bot_watch($bot) unless $Continue;;
955             }
956             elsif ($_) {
957 0           warn "Malformed command $_ from ".$bot->{'id'}.", state ".$bot->{'state'};
958             }
959              
960 0 0         $bot->{'dead'} = 1 if $bot->{'life'} <= 0;
961 0 0         unless ($bot->{'dead'}) {
962 0           tick_cloak($bot);
963 0           obj_move(@{$old}{'x', 'y'}, @{$bot}{'x', 'y'}, bot_char($bot), "_bot$bot->{'id'}", 0, ($bot->{'id'} eq $Watch->{'id'}));
  0            
  0            
964 0 0         obj_move(@{$old}{'x', 'y'}, @{$bot}{'x', 'y'}, $bot->{'shield'} ? 'shield' : 'noshield', "_bots$bot->{'id'}", 1);
  0            
  0            
965             }
966              
967 0 0 0       $Top->after($Tick_delay / (scalar @Bots), \&Games::AIBots::tick_bot) if $GUI and $Running;
968             }
969              
970             sub tick_missile {
971 0     0 0   my $missile = shift;
972 0 0         return if $missile->{'dead'};
973              
974 0           my $bot = $missile->{'bot'};
975 0           my ($dx, $dy) = delta($missile->{'h'}, 'front');
976 0           my $tag = "_missile$bot->{'id'}:$missile->{'id'}";
977              
978 0 0 0       if (bot_at(@{$missile}{'x', 'y'}) and $missile->{'age'}
  0 0 0        
      0        
979             or obj_at($missile->{'x'} += $dx, $missile->{'y'} += $dy) ne '.'
980             or ++$missile->{'age'} >= $missile->{'range'})
981             {
982 0           detonate($bot, $missile->{'type'}, @{$missile}{'x', 'y'});
  0            
983 0           $missile->{'dead'}++;
984 0 0         $Canvas->delete($tag) if $GUI;
985             }
986             elsif ($missile->{'age'} > 1) {
987 0           obj_move($missile->{'x'} - $dx, $missile->{'y'} - $dy,
988 0           @{$missile}{'x', 'y'}, $missile->{'type'}.$missile->{'h'}, $tag, 1);
989             }
990             else {
991 0           obj_draw(@{$missile}{'x', 'y'}, $missile->{'type'}.$missile->{'h'}, $tag, 1);
  0            
992             }
993             }
994              
995             sub tick_cloak {
996 0     0 0   my $bot = shift;
997 0 0         return unless $bot->{'cloak'};
998              
999 0 0 0       if ($bot->{'fuel'} >= $Cloak_fuel and bot_pay($bot, ammo => $Cloak_ammo)) {
1000 0           $bot->{'fuel'} -= $Cloak_fuel;
1001             }
1002             else {
1003 0           $bot->{'cloak'} = 0;
1004             }
1005             }
1006              
1007             sub tick_check {
1008 0     0 0   my (%alive, $hasfuel, $missiles);
1009 0 0         return if !$Running;
1010              
1011 0           foreach my $bot (@Bots) {
1012 0 0 0       $alive{$bot->{'team'} || bot_id($bot)}++ unless $bot->{'dead'};
1013 0 0 0       $hasfuel++ unless $bot->{'dead'} or $bot->{'fuel'} <= 0;
1014 0           foreach my $missile (@{$bot->{'missiles'}}) {
  0            
1015 0 0         $missiles++ unless $missile->{'dead'};
1016             }
1017             }
1018              
1019 0 0 0       if (!$missiles and (scalar keys(%alive) <= 1) or !$hasfuel) {
      0        
1020             # Game Over
1021 0           display('end');
1022              
1023 0           my %TScore;
1024 0           foreach my $bot (@Bots) {
1025 0           $bot->{'score'} += 50 * $bot->{'life'};
1026 0           $bot->{'score'} -= $bot->{'burn'};
1027 0 0 0       $bot->{'score'} += 500 / $alive{$bot->{'team'} || bot_id($bot)}
      0        
1028             unless $bot->{'dead'} or (scalar keys(%alive) > 1);
1029 0   0       $TScore{$bot->{'team'} || bot_id($bot)} += $bot->{'score'} || 0;
      0        
1030 0           $bot->{'lastcmd'} = '**End**';
1031             }
1032              
1033 0           my @BScore = sort {$b->{'score'} <=> $a->{'score'}} @Bots;
  0            
1034 0           my @TScore = sort {$TScore{$b} <=> $TScore{$a}} keys(%TScore);
  0            
1035              
1036 0 0         if ($GUI) {
1037 0           $Btn_stop->configure(-state => 'disabled');
1038 0           $Btn_play->configure(-image => 'play');
1039 0           $_->configure(-state => 'normal', -background => '#a04444') foreach @Mnu_arg;
1040 0           $Top->configure('-title' => "AI Bots v$Games::AIBots::VERSION [Winner:".bot_id($BScore[0])." | Team ".$TScore[0]."] (Tick: $Tick)");
1041 0           $Watch->{'id'} = $BScore[0]->{'id'};
1042 0           bot_watch($BScore[0]);
1043             }
1044              
1045 0           foreach my $bot (@BScore) {
1046 0           $Watch->{'id'} = $bot->{'id'};
1047 0           bot_watch($bot, 1);
1048 0 0 0       print "\n" unless $Console and $^O ne 'MSWin32';
1049             }
1050              
1051 0           $Running = undef;
1052 0           ding('game_over');
1053 0           display('winner', bot_id($BScore[0]), $TScore[0]);
1054             }
1055              
1056 0           return $Running;
1057             }
1058              
1059             # ===========================================================================
1060             # Bot Commands
1061             # ===========================================================================
1062              
1063             sub cmd_attempt {
1064 0     0 0   my ($bot, $action) = @_;
1065              
1066 0 0         if ($action eq 'repair') {
    0          
1067 0 0 0       return if $bot->{'shield'} or $bot->{'life'} >= $Max_life;
1068              
1069 0 0         if (!int(rand(10))) {
    0          
1070 0           $bot->{'life'}++;
1071 0           obj_flash('repair', @{$bot}{'x', 'y'});
  0            
1072             }
1073             elsif (!int(rand(20))) {
1074 0           $bot->{'burn'}++;
1075 0           obj_flash('explode', @{$bot}{'x', 'y'});
  0            
1076             }
1077             }
1078             elsif ($action eq 'destruct') {
1079 0           my $dmg = $bot->{'life'} * 100 / $Max_life;
1080 0           @{$Weapon{'destructh'}}[$DmgS, $DmgN, $BurnS, $BurnN]
  0            
1081             = ($dmg, $dmg, $dmg / 10, $dmg / 10);
1082 0           @{$Weapon{'destructs'}}[$DmgS, $DmgN, $BurnS, $BurnN, $ScrS, $ScrN]
  0            
1083             = ($dmg, $dmg, $dmg / 10, $dmg / 10, $dmg * 5, $dmg * 5);
1084 0           detonate($bot, 'destruct', @{$bot}{'x', 'y'});
  0            
1085             }
1086              
1087 0           return 1;
1088             }
1089              
1090             sub cmd_beam {
1091 0     0 0   my ($bot, $type, $param) = @_;
1092 0           my ($x, $y) = delta($bot->{'h'}, 'back');
1093              
1094 0           $x += $bot->{'x'}; $y += $bot->{'y'};
  0            
1095              
1096 0 0         if (my $other = bot_at($x, $y)) {
1097 0 0         return unless $other->{'h'} + $bot->{'h'} == 10;
1098              
1099 0           ding('beam');
1100              
1101 0 0 0       if ($type eq 'command') {
    0          
1102 0           unshift(@{$other->{'queue'}}, $param);
  0            
1103             }
1104             elsif ($type eq 'fuel' or $type eq 'ammo') {
1105 0 0         $param = -($other->{$type}) if $param < -($other->{$type});
1106 0 0         $param = $bot->{$type} if $param > $bot->{$type};
1107              
1108 0           $bot->{$type} += $param;
1109 0           $other->{$type} -= $param;
1110             }
1111             }
1112              
1113 0           return 1;
1114             }
1115              
1116             sub cmd_disable {
1117 0     0 0   my ($bot, $switch) = @_;
1118 0           $bot->{$switch} = 0;
1119 0           return 1;
1120             }
1121              
1122             sub cmd_enable {
1123 0     0 0   my ($bot, $switch) = @_;
1124 0           $bot->{$switch} = 1;
1125 0           return 1;
1126             }
1127              
1128             sub cmd_toggle {
1129 0     0 0   my ($bot, $switch) = @_;
1130 0           $bot->{$switch} = 1 - $bot->{$switch};
1131 0           return 1;
1132             }
1133            
1134             sub cmd_fire {
1135 0     0 0   my ($bot, $type, $range) = @_;
1136              
1137             # check ammo & fuel requisites
1138 0 0         return if (not bot_ready($bot, $type));
1139              
1140 0           bot_pay($bot, ammo => $Weapon{$type.'h'}[$CostA]);
1141 0           $bot->{'fuel'} -= $Weapon{$type.'h'}[$CostF];
1142              
1143 0           ding('fire', $type);
1144              
1145 0 0 0       if ($type eq 'energy') {
    0          
    0          
    0          
1146 0           detonate($bot, $type, @{$bot}{'x', 'y'});
  0            
1147             }
1148             elsif ($type eq 'bazooka' or $type eq 'grenade') {
1149 0 0         if ($bot->{'shield'}) {
1150             # self explode!
1151 0           $bot->{'shield'} = 0;
1152 0           detonate($bot, $type, @{$bot}{'x', 'y'});
  0            
1153 0           $bot->{'shield'} = 1;
1154             }
1155             else {
1156             # new missile
1157 0   0       my $missile = {
1158             'x' => $bot->{'x'},
1159             'y' => $bot->{'y'},
1160             'h' => $bot->{'h'},
1161             'range' => $range || (($Cols > $Rows) ? $Cols : $Rows),
1162             'type' => $type,
1163             'bot' => $bot,
1164             };
1165 0           push @{$bot->{'missiles'}}, $missile;
  0            
1166              
1167 0           $missile->{'id'} = $#{$bot->{'missiles'}};
  0            
1168 0           tick_missile($missile);
1169             }
1170             }
1171             elsif ($type eq 'laser') {
1172 0           my ($x, $y) = @{$bot}{'x', 'y'};
  0            
1173 0           my ($dx, $dy) = delta($bot->{'h'}, 'front');
1174 0 0 0       my $dir = ($bot->{'h'} == 8 or $bot->{'h'} == 2) ? 'v' : 'h';
1175              
1176 0           foreach my $step (1 .. $Weapon{$type.'h'}[$DmgN] / 10) {
1177 0           my $obj = $Obj{obj_at($x += $dx, $y += $dy)};
1178              
1179 0 0         (obj_flash($type.$dir, $x, $y), next) if ($obj eq 'space');
1180              
1181 0           obj_flash($type, $x, $y);
1182 0 0         obj_erase($x, $y) if ($obj eq 'fence');
1183 0 0         (detonate($bot, $obj, $x, $y), obj_erase($x, $y)) if ($obj eq 'vault');
1184              
1185 0 0         my $other = bot_at($x, $y) or last;
1186 0 0         ding('dmg', $type) if bot_damage($other, $type, $bot, 1 - $step);
1187              
1188 0           last;
1189             }
1190             }
1191             elsif ($type eq 'mine') {
1192 0           $Mines{join(':', @{$bot}{'x', 'y'})} = $bot;
  0            
1193 0           obj_draw(@{$bot}{'x', 'y'}, 'O');
  0            
1194             }
1195              
1196 0           return 1;
1197             }
1198              
1199             sub cmd_move {
1200 0     0 0   my ($bot, $dir) = @_;
1201 0 0         my ($x, $y) = delta($bot->{'h'}, ($dir eq 'forward') ? 'front' : 'back');
1202              
1203 0           $x += $bot->{'x'}; $y += $bot->{'y'};
  0            
1204              
1205 0 0         if (obj_at($x, $y) =~ /^[.OPA]$/) {
1206 0 0         bot_pay($bot, fuel => 1) or return;
1207              
1208 0           substr($Board, ($bot->{'y'} - 1) * $Cols + $bot->{'x'} - 1, 1) = '.';
1209 0 0         cmd_fire($bot, 'mine') if ($bot->{'laymine'});
1210              
1211 0           @{$bot}{'x', 'y'} = ($x, $y);
  0            
1212 0 0         bot_hit($bot, $Obj{obj_at($x, $y)}) if (obj_at($x, $y) ne '.');
1213             }
1214             else {
1215 0           $bot->{'bumped'} = bot_scan($bot, $x, $y);
1216 0           @{$bot}{'bumped_x', 'bumped_y'} = ($x, $y);
  0            
1217 0           return;
1218             }
1219              
1220 0           return 1;
1221             }
1222              
1223             sub cmd_scan {
1224 0     0 0   my ($bot, $type) = splice(@_, 0, 2);
1225 0           my ($x, $y) = @{$bot}{'x', 'y'};
  0            
1226              
1227 0 0 0       if ($type eq 'gps') {
    0          
    0          
    0          
    0          
    0          
1228 0           obj_flash('scang', @_);
1229              
1230 0           $bot->{'found'} = bot_scan($bot, @_);
1231             }
1232             elsif ($type eq 'perimeter') {
1233 0           obj_flash('scanp', $x, $y);
1234 0           $bot->{'found'} = (sort
1235 0           {index($Scan_list, $b) <=> index($Scan_list, $a)}
1236             bot_scan($bot, $x-1, $y-1), bot_scan($bot, $x-1, $y), bot_scan($bot, $x-1, $y+1),
1237             bot_scan($bot, $x, $y-1), bot_scan($bot, $x , $y+1),
1238             bot_scan($bot, $x+1, $y-1), bot_scan($bot, $x+1, $y), bot_scan($bot, $x+1, $y+1),
1239             )[0];
1240             }
1241             elsif ($type eq 'cross') {
1242 0           obj_flash('scancr', $x, $y);
1243 0           $bot->{'found'} = (sort
1244 0           {index($Scan_list, $b) <=> index($Scan_list, $a)}
1245             bot_scan($bot, $x-1, $y), bot_scan($bot, $x, $y-1),
1246             bot_scan($bot, $x+1, $y), bot_scan($bot, $x, $y+1),
1247             )[0];
1248             }
1249             elsif ($type eq 'corner') {
1250 0           obj_flash('scanco', $x, $y);
1251 0           $bot->{'found'} = (sort
1252 0           {index($Scan_list, $b) <=> index($Scan_list, $a)}
1253             bot_scan($bot, $x-1, $y-1), bot_scan($bot, $x-1, $y+1),
1254             bot_scan($bot, $x+1, $y-1), bot_scan($bot, $x+1, $y+1),
1255             )[0];
1256             }
1257             elsif ($type eq 'position' or $type eq 'relative') {
1258 0           my $pos = shift;
1259              
1260 0 0         if ($type eq 'relative') {
1261 0 0         $pos =~ tr/12346789/74182963/ if ($bot->{'h'} eq '6');
1262 0 0         $pos =~ tr/12346789/98764321/ if ($bot->{'h'} eq '2');
1263 0 0         $pos =~ tr/12346789/36928147/ if ($bot->{'h'} eq '4');
1264             }
1265              
1266 0           $x += 1 - ((9 - $pos) % 3),
1267             $y += 1 - int(($pos-1) / 3),
1268              
1269             $bot->{'found'} = bot_scan($bot, $x, $y);
1270 0           obj_flash('scang', $x, $y);
1271             }
1272             elsif ($type eq 'longrange') {
1273 0 0         cmd_scandir($bot, 'front', ($Cols > $Rows) ? $Cols : $Rows);
1274             }
1275             else {
1276 0           cmd_scandir($bot, $type, $Scan_range);
1277             }
1278              
1279 0           return 1; # scan could be used as no-op
1280             }
1281              
1282             sub cmd_scandir {
1283 0     0 0   my ($bot, $dir, $range) = @_;
1284 0           my ($x, $y) = @{$bot}{'x', 'y'};
  0            
1285 0           my ($dx, $dy) = delta($bot->{'h'}, $dir);
1286              
1287 0           foreach my $step (1 .. $range) {
1288 0 0         obj_flash($step == 1 ? 'scan'.$bot->{'h'} : 'scan', $x += $dx, $y += $dy, $step);
1289 0 0         return 1 unless ($bot->{'found'} = bot_scan($bot, $x, $y)) eq 'space';
1290             }
1291              
1292 0           return;
1293             }
1294              
1295             sub cmd_turn {
1296 0     0 0   my ($bot, $dir) = @_;
1297              
1298 0 0         return unless bot_pay($bot, fuel => 1);
1299              
1300 0 0         $bot->{'h'} = (qw/8 6 2 4/)[(index('8624', $bot->{'h'}) +
1301             (($dir eq 'left') ? 3 : 1)) % 4];
1302              
1303 0           return 1;
1304             }
1305              
1306             # ===========================================================================
1307             # Utilities
1308             # ===========================================================================
1309              
1310             sub detonate {
1311 0     0 0   my ($bot, $type, $x, $y) = @_;
1312 0 0 0       obj_flash($Obj{$type}, $x, $y) unless $Obj{$type} and $Obj{$type} eq 'vault';
1313              
1314             # detonation on spot
1315 0 0         if (my $other = bot_at($x, $y)) {
    0          
1316 0           bot_damage($other, $type, $bot);
1317             }
1318             elsif (my $obj = $Obj{obj_at($x, $y)}) {
1319 0 0         if ($obj eq 'fence') {
    0          
1320 0           obj_erase($x, $y);
1321             }
1322             elsif ($obj eq 'vault') {
1323 0           obj_erase($x, $y);
1324 0           detonate($bot, $obj, $x, $y);
1325             }
1326             }
1327              
1328             # splash damage
1329 0           $type .= 's';
1330 0           foreach my $dx (-1 .. 1) {
1331 0           foreach my $dy (-1 .. 1) {
1332 0 0 0       next if $dx == 0 and $dy == 0;
1333              
1334 0 0         my $obj = $Obj{obj_at($x + $dx, $y + $dy)} or next;
1335              
1336 0           $x += $dx; $y += $dy;
  0            
1337              
1338 0 0         if (my $other = bot_at($x, $y)) {
    0          
    0          
    0          
1339 0           bot_damage($other, $type, $bot);
1340             }
1341             elsif ($obj eq 'vault') {
1342 0           obj_erase($x, $y);
1343 0           obj_flash('explode', $x, $y);
1344 0           detonate($bot, 'vault', $x, $y);
1345             }
1346             elsif ($type eq 'destructs') {
1347 0 0 0       obj_erase($x, $y) if ($obj eq 'flag' or $obj eq 'mine' or $obj eq 'fence');
      0        
1348             }
1349             elsif ($type eq 'energys') {
1350 0 0 0       obj_erase($x, $y) if ($obj eq 'flag' or $obj eq 'mine');
1351             }
1352             else {
1353 0 0         obj_erase($x, $y) if ($obj eq 'fence');
1354             }
1355              
1356 0           $x -= $dx; $y -= $dy;
  0            
1357             }
1358             }
1359             }
1360              
1361             sub delta {
1362 0     0 0   my ($head, $dir) = @_;
1363 0   0       $dir ||= 'front';
1364              
1365 0 0         $head =~ tr/8624/0123/ if $dir eq 'front';
1366 0 0         $head =~ tr/8624/1230/ if $dir eq 'right';
1367 0 0         $head =~ tr/8624/2301/ if $dir eq 'back';
1368 0 0         $head =~ tr/8624/3012/ if $dir eq 'left';
1369              
1370 0           return ($head % 2) * (2 - $head),
1371             (($head + 1) % 2) * ($head - 1);
1372             }
1373              
1374             sub display {
1375 0 0   0 0   return if $Continue;
1376 0           my $msg = shift;
1377 0           $msg = sprintf "[%5s] $Msg{$msg}", $Tick, @_;
1378 0           $msg .= (' ' x (79 - length($msg)));
1379              
1380 0 0 0       if ($Console and (!$Mask or $Bots[-1]{dead})) {
      0        
1381 0           $Console->Cursor(0, 24);
1382 0           $Console->Write($msg . (' ' x (79 - length($msg))));
1383             }
1384              
1385 0 0 0       if ($Console and $^O ne 'MSWin32') {
1386 0           $Msglog .= "$msg\n";
1387             }
1388             else {
1389 0           print $msg, "\n";
1390             }
1391             }
1392              
1393             sub ding {
1394 0 0   0 0   return unless $Sound;
1395              
1396 0 0 0       if (exists($Wav{join('_', @_)})) {
    0 0        
1397 0           Win32::Sound::Play($Wav{join('_', @_)}, 1);
1398             }
1399             elsif ($Console and ($_[1] eq 'destruct' or $_[0] eq 'hit' or $_[0] eq
1400             'fire')) {
1401 0           print chr(7);
1402             }
1403             }
1404              
1405             sub do_loop {
1406 0     0 0   $Continue = shift;
1407              
1408 0 0         if ($GUI) {
1409 0           $Top->focusForce;
1410 0           Tk::MainLoop();
1411             }
1412             else {
1413 0           require Term::ReadKey;
1414              
1415 0           ding('game_begin');
1416             # init_map();
1417 0           init_game();
1418 0           $Running = 1;
1419 0           Term::ReadKey::ReadMode(4);
1420              
1421 0           while (1) {
1422 0           while (my($x, $y, $obj) = splice(@Flash, 0, 3)) {
1423 0           obj_set($x, $y, $obj);
1424             }
1425 0 0         tick_bot() if $Running;
1426 0 0         $Console->Display() if $Console;
1427 0 0 0       next if $Mask and $Running and not $Bots[-1]{dead};
      0        
1428 0 0 0       my $key = Term::ReadKey::ReadKey(
    0 0        
1429             ($Running or $Continue) ? -1 : 10
1430             ) or $Continue or next;
1431 0 0 0       if ($key eq 'h') {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
1432 0           $_->{'fuel'} = 0 foreach (@Bots);
1433 0           tick_check();
1434             }
1435             elsif ($key ge '0' and $key le '9') {
1436 0           $Watch->{'id'} = $key;
1437 0           Games::AIBots::bot_watch($Bots[$key-1]);
1438 0           ding('toggle');
1439             }
1440             elsif ($key eq 'q') {
1441 0 0         $Console->Cursor(0, 24) if $Console;
1442 0           print $Msglog;
1443 0           exit;
1444             }
1445             elsif ($key eq 's') {
1446 0           btn_sound();
1447             }
1448             elsif ($key eq 'w') {
1449 0           btn_watch();
1450             }
1451             elsif ($key eq 't') {
1452 0           btn_tempo();
1453             }
1454             elsif ($key eq 'T') {
1455 0           btn_tempo();
1456 0           btn_tempo();
1457             }
1458             elsif ($key eq ' ' or $key eq 'p' or !$Running and $Continue) {
1459             # $Continue--;
1460 0           btn_play();
1461             }
1462             }
1463             }
1464             }
1465              
1466              
1467             %UserCmd = (
1468             k => 'move forward',
1469             j => 'move backward',
1470             h => 'turn left',
1471             l => 'turn right',
1472             b => 'fire bazooka',
1473             g => 'fire grenade',
1474             G => sub { 'fire grenade '.int(Term::ReadKey::ReadKey(0)) },
1475             z => 'fire energy',
1476             x => 'fire laser',
1477             c => 'toggle cloak',
1478             s => 'toggle shield',
1479             m => 'toggle laymine',
1480             K => 'scan front',
1481             H => 'scan left',
1482             L => 'scan right',
1483             J => 'scan perimeter',
1484             I => 'scan longrange',
1485             U => 'scan cross',
1486             O => 'scan corner',
1487             P => sub { 'scan position '.int(Term::ReadKey::ReadKey(0)) },
1488             R => sub { 'scan relative '.int(Term::ReadKey::ReadKey(0)) },
1489             r => 'attempt repair',
1490             d => 'attempt destruct',
1491             q => sub {
1492             $Console->Cursor(0, 24) if $Console;
1493             print $Msglog;
1494             exit;
1495             }
1496             );
1497              
1498             sub user_tick {
1499 0 0   0 0   die "User tick support for non-console mode: Not Yet." unless $Console;
1500 0           my $bot = shift;
1501 0           my $msg = sprintf(
1502             "Score:%d Ammo:%d Life:%d Fuel:%d [%s]",
1503 0           @{$bot}{qw/score ammo life fuel lastcmd/}
1504             );
1505            
1506 0 0         if ($bot->{bumped}) {
1507 0           obj_set(@{$bot}{'bumped_x', 'bumped_y'}, obj_at(@{$bot}{'bumped_x',
  0            
  0            
1508             'bumped_y'}));
1509             }
1510 0           $Console->Attr($main::FG_LIGHTCYAN);
1511 0           $Console->Cursor(40, 4);
1512 0           $Console->Write(substr($msg, 0, index($msg, ' Fuel')).(' ' x
1513             (40 - index($msg, ' Fuel'))));
1514 0           $Console->Cursor(42, 5);
1515 0           $Console->Write(substr($msg, index($msg, 'Fuel')).(' ' x (70 - length($msg))));
1516 0 0         $msg = $bot->{shield} ? '[S]' : '[s]';
1517 0 0         $msg .= $bot->{laymine} ? '[M]' : '[m]';
1518 0 0         $msg .= $bot->{cloak} ? '[C]' : '[c]';
1519 0 0         $msg .= " Bump:$bot->{bumped}" if $bot->{bumped};
1520 0 0         $msg .= " Scan:$bot->{found}" if $bot->{found};
1521 0 0         $msg .= " Enem:$bot->{enemy_l}" if $bot->{enemy_l};
1522 0 0         $msg .= " Frnd:$bot->{friend_l}" if $bot->{friend_l};
1523 0           $Console->Cursor(42, 6);
1524 0           $Console->Write($msg . (' ' x (38 - length($msg))));
1525 0           $Console->Attr($main::FG_WHITE);
1526              
1527 0           $Console->Cursor(0, 0);
1528 0           my $key;
1529 0   0       while (not defined $key or not exists $UserCmd{$key}) {
1530 0           $key = Term::ReadKey::ReadKey(0);
1531             }
1532              
1533 0           my $cmd = $UserCmd{$key};
1534 0 0 0       if ($bot->{enemy_x} and $Console) {
1535 0           $Console->Cursor($bot->{enemy_x} - 1, $bot->{enemy_y} - 1, 0, 0);
1536 0           $Console->Write(' ');
1537             }
1538              
1539 0 0         return ref($cmd) eq 'CODE' ? &{$cmd} : $cmd;
  0            
1540             }
1541              
1542             1;
1543              
1544             =head1 SEE ALSO
1545              
1546             L, L
1547              
1548             =head1 AUTHORS
1549              
1550             Autrijus Tang Eautrijus@autrijus.orgE
1551              
1552             Files under the F directory was contributed by students in
1553             the autonomous learning experimnetal class, Bei'zheng junior high
1554             school, Taipei, Taiwan.
1555              
1556             =head1 COPYRIGHT
1557              
1558             Copyright 2001, 2002 by Autrijus Tang Eautrijus@autrijus.orgE.
1559              
1560             This program is free software; you can redistribute it and/or
1561             modify it under the same terms as Perl itself.
1562              
1563             See L
1564              
1565             =cut