#!/usr/bin/perl use warnings; use strict; use MIME::Base64; use threads; use threads::shared; # apparently the &delete_event sub needs this to exit without GLib errors # it depends where you create the thread # ...i'm creating it well down in the Gtk2 code Glib::Object->set_threadsafe (1); use Gtk2 qw/-init -threads-init/; # usage: ./scriptname number_horses # the animated gif support in Gtk2 is a bit limited, and # it will only load from a disk file..... so i include # the base64 encoded image at the bottom, and write it to # disk, if not already done if( ! -e './horse.gif' ){ open (FH, '>', 'horse.gif') or die "$!\n"; print FH get_horsey(); close FH; } my $numworkers = shift || 6; my %track; share my $finish; $finish = 0; foreach my $tr(1..$numworkers){ share ($track{$tr}{'go'}); share ($track{$tr}{'die'}); share ($track{$tr}{'tot'}); $track{$tr}{'go'} = 0; $track{$tr}{'die'} = 0; $track{$tr}{'tot'} = 0; } my $window = Gtk2::Window->new; $window->signal_connect('destroy', \&delete_event); $window->set( border_width => 15 ); $window->set_size_request(800,600); my $vbox0 = Gtk2::VBox->new(0,0); $window->add($vbox0); my $hbox = Gtk2::HBox->new( 0, 6 ); $hbox->set_size_request(5,50); $vbox0->pack_start($hbox,0,0,0); my $sbutton = Gtk2::Button->new(' Start '); $sbutton->signal_connect( "clicked" => \&restart ); # connect the 'clicked' signal of the button to our callback my $sconnect = $sbutton->signal_connect( "clicked" => \&start ); $hbox->pack_start($sbutton,0,0,0); my $lab = Gtk2::Label->new(); $lab->set_markup("Race to 500"); $hbox->pack_start($lab,0,0,0); my $ebutton = Gtk2::Button->new('Exit'); $ebutton->signal_connect( "clicked" => \&delete_event ); $hbox->pack_end($ebutton,0,0,0); my $winlab = Gtk2::Label->new(); $winlab->set_markup(":Winner"); $hbox->pack_end($winlab,0,0,0); my $wins = Gtk2::Label->new(); $wins->set_markup(" ? "); $hbox->pack_end($wins,0,0,0); my $pba = Gtk2::Gdk::PixbufAnimation->new_from_file ('horse.gif'); my $static = $pba->get_static_image; my $vbox = Gtk2::VBox->new(); my $sw = Gtk2::ScrolledWindow->new (undef, undef); $sw->set_shadow_type ('etched-out'); $sw->set_policy ('automatic', 'automatic'); $sw->set_size_request (500, 500); $sw->set_border_width(5); $vbox0->pack_start($sw,1,1,0); $sw->add_with_viewport($vbox); foreach my $tr (1..$numworkers){ $track{$tr}{'image'} = Gtk2::Image->new_from_animation ($pba); $track{$tr}{'trak'} = Gtk2::Frame->new($tr); $vbox->pack_start( $track{$tr}{'trak'}, 0, 0, 0 ); $track{$tr}{'trak'}->set_border_width(3); $track{$tr}{'hbox'} = Gtk2::HBox->new( 0, 6 ); $track{$tr}{'hbox'}->set_size_request(5,50); $track{$tr}{'trak'}->add( $track{$tr}{'hbox'} ); $track{$tr}{'hbox'}->pack_start( $track{$tr}{'image'}, 0, 0, 0 ); $track{$tr}{'score'} = Gtk2::Label->new(); $track{$tr}{'score'}->set_markup("$tr"); $track{$tr}{'hbox'}->pack_end( $track{$tr}{'score'}, 0, 0, 0 ); $track{$tr}{'thread'} = threads->new(\&work,$tr,$wins); } $window->show_all(); $winlab->hide; $wins->hide; Gtk2->main; sub start{ $finish = 0; $sbutton->set_label('Restart'); $sbutton->signal_connect( "clicked" => \&restart ); # randomized start out of the box first my @shuffled = map {$_->[0]} sort { $a->[1] <=> $b->[1]} map {[$_, rand(1)]} (1..$numworkers); foreach my $tr (@shuffled){ lock $track{$tr}{'go'}; #added as test fix $track{$tr}{'go'} = 1; } } sub restart{ $finish = 1; foreach my $tr (1..$numworkers){ lock $track{$tr}{'go'}; #added as test fix $track{$tr}{'go'} = 0; lock $track{$tr}{'die'}; #added as test fix $track{$tr}{'tot'} = 0; $track{$tr}{'hbox'}->set_child_packing ($track{$tr}{'image'}, 0, 0, 0 , 'start'); $track{$tr}{'score'}->set_markup("0"); $track{$tr}{'score'}->set_markup("0"); } $sbutton->set_label(' Start '); $sbutton->signal_connect( "clicked" => \&start ); $winlab->hide; $wins->hide; } sub delete_event{ foreach my $tr (1..$numworkers){ lock $track{$tr}{'go'}; #added as test fix $track{$tr}{'go'} = 0; lock $track{$tr}{'die'}; #added as test fix $track{$tr}{'die'} = 1; $track{$tr}{'thread'}->join; } Gtk2->main_quit; return 0; } sub work{ my ($tr, $wins ) = @_; $|++; while(1){ if($track{$tr}{'die'} == 1){ return }; if ( $track{$tr}{'go'} == 1 ){ while(1) { my $whip = 2 - rand (1); lock $track{$tr}{'tot'}; $track{$tr}{'tot'} += (rand 5) + $whip; if ( $finish == 0){ Glib::Idle->add( sub{ my $tot = sprintf('%.2f', $track{$tr}{'tot'} ); $track{$tr}{'hbox'}->set_child_packing ($track{$tr}{'image'}, 0, 0,$tot , 'start'); $track{$tr}{'score'}->set_markup("$tot"); #check for winner if($track{$tr}{'tot'} > 500){ lock($finish); # prevent thread races :-) $finish = 1; $track{$tr}{'score'}->set_markup("$tot"); $wins->set_markup("$tr"); $wins->show; $winlab->show; } }); } if($track{$tr}{'die'} == 1){ return }; if ( $track{$tr}{'go'} == 0 ){last } select(undef,undef,undef,.1); } # end of go loop }else { select (undef,undef,undef,.1) } #sleep while loop } #end of main while loop } sub get_horsey{ return decode_base64( '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' ) or die $!; }