Games-Risk

 view release on metacpan or  search on metacpan

lib/Games/Risk/Tk/Main.pm  view on Meta::CPAN

#
# This file is part of Games-Risk
#
# This software is Copyright (c) 2008 by Jerome Quelin.
#
# This is free software, licensed under:
#
#   The GNU General Public License, Version 3, June 2007
#
use 5.010;
use strict;
use warnings;

package Games::Risk::Tk::Main;
# ABSTRACT: main prisk window
$Games::Risk::Tk::Main::VERSION = '4.000';
use POE          qw{ Loop::Tk };
use Image::Magick;
use Image::Size  qw{ imgsize };
use List::Util   qw{ min };
use MIME::Base64 qw{ encode_base64 };
use Moose;
use MooseX::Has::Sugar;
use MooseX::POE;
use MooseX::SemiAffordanceAccessor;
use Readonly;
use Tk;
use Tk::Action;
use Tk::Balloon;
use Tk::JPEG;
use Tk::PNG;
use Tk::Role::HasWidgets 1.112070; # _del_w
use Tk::Sugar;
use Tk::ToolBar;

with 'Tk::Role::HasWidgets';

use Games::Risk::I18n   qw{ T };
use Games::Risk::Logger qw{ debug };
use Games::Risk::Utils  qw{ $SHAREDIR };


Readonly my $K  => $poe_kernel;
Readonly my $mw => $poe_main_window; # already created by poe
Readonly my $WAIT_CLEAN_AI    => 1.000;
Readonly my $WAIT_CLEAN_HUMAN => 0.250;
Readonly my $FLASHDELAY       => 0.150;


# -- attributes

# a hash with all the actions.
has _actions => (
    ro,
    traits  => ['Hash'],
    isa     => 'HashRef[Tk::Action]',
    default => sub { {} },
    handles => {
        _set_action => 'set',
        _action     => 'get',
    },
);

# it's not usually a good idea to retain a reference on a poe session,
# since poe is already taking care of the references for us. however, we
# need the session to call ->postback() to set the various gui callbacks
# that will be fired upon gui events.
has _session => ( rw, weak_ref, isa=>'POE::Session' );

# zoom information
has _orig_bg_size => ( rw, isa=>'Games::Risk::Point' );
has _zoom         => ( rw, isa=>'Games::Risk::Point' );

# greyscale image
has _greyscale => ( rw, isa=>'Tk::Photo' );

# the strings that will appear in the status bar
has _status => (

lib/Games/Risk/Tk/Main.pm  view on Meta::CPAN

    };



    event chnum => \&_do_country_redraw;
    event chown => \&_do_country_redraw;
    event _country_redraw => \&_do_country_redraw;
    sub _do_country_redraw {
        my ($self, $country) = @_[OBJECT, ARG0];
        my $c = $self->_w('canvas');

        my $id    = $country->id;
        my $owner = $country->owner;
        my $fakein  = $self->_fake_armies_in->{$id}  // 0;
        my $fakeout = $self->_fake_armies_out->{$id} // 0;
        my $armies  = ($country->armies // 0) + $fakein - $fakeout;

        # change radius to reflect number of armies
        my ($radius, $fill_color, $text) = defined $owner
                ? (8, $owner->color, $armies)
                : (6,       'white', '');
        $radius += min(16,$armies-1)/2;

        my $zoom = $self->_zoom;
        my $x = $country->coordx * $zoom->coordx;
        my $y = $country->coordy * $zoom->coordy;
        my $x1 = $x - $radius; my $x2 = $x + $radius;
        my $y1 = $y - $radius; my $y2 = $y + $radius;

        # update canvas
        $c->delete( "country$id" );
        #  - circle
        $c->createOval(
            $x1, $y1, $x2, $y2,
            -fill    => $fill_color,
            -outline => 'black',
            -tags    => [ "country$id", 'circle' ],
        );

        #  - text
        $c->createText(
            $x, $y+1,
            -fill => 'white',
            -tags => [ "country$id", 'text' ],
            -text => $text,
        );

        $c->raise("country$id&&circle", 'all');
        $c->raise("country$id&&text",   'all');
    };



    event flash_country => sub {
        my ($self, $country, $on, $left) = @_[OBJECT, ARG0 .. $#_];
        my $c = $self->_w('canvas');

        # first time that the country is flashed
        if ( not defined $on ) {
            # load greyscale image...
            my $magick = Image::Magick->new;
            $magick->Read( Games::Risk->new->map->greyscale );

            # and paint everything that isn't the country in white
            my $id = $country->id;
            my $grey = "rgb($id,$id,$id)";
            $magick->FloodfillPaint(fuzz=>0, fill=>'white', bordercolor=>$grey, invert=>1);
            $magick->Negate;                        # turn white in black
            $magick->Transparent( color=>'black' ); # mark black as transparent

            # resize the image to fit canvas zoom
            my $zoom = $self->_zoom;
            my $width  = $magick->Get('width');
            my $height = $magick->Get('height');
            $magick->Scale(width=>$width * $zoom->coordx, height=>$height * $zoom->coordy);

            # remove all the uninteresting bits around the country itself
            $magick->Trim;
            my $coordx = $magick->Get('page.x');
            my $coordy = $magick->Get('page.y');
            $magick->Set(page=>'0x0+0+0');          # reset the page (resize image to trimmed zone)

            # create the image and display it on the canvas
            my $img = $c->Photo( -data => encode_base64( $magick->ImageToBlob ) );
            $c->createImage($coordx, $coordy, -anchor=>'nw', -image=>$img, -tags=>["flash$country"]);

            $on   = 1;
            $left = 8;
        }
        my $method = $on ? 'raise' : 'lower';
        $c->$method("flash$country", 'background' );
        if ( $left ) {
            $K->delay( flash_country => $FLASHDELAY => $country, !$on, $left-1 );
        } else {
            $c->delete( "flash$country" );
        }
    };



    event game_over => sub {
        my ($self, $winner) = @_[OBJECT, ARG0];

        # update gui
        my $c = $self->_w('canvas');
        $c->CanvasBind('<1>', undef);
        $c->CanvasBind('<3>', undef);
        $self->_w('lab_step_attack')->configure(disabled);
        $self->_action('attack_redo')->disable;
        $self->_action('attack_done')->disable;

        # announce the winner
        require Games::Risk::Tk::GameOver;
        Games::Risk::Tk::GameOver->new(
            parent => $mw,
            winner => $self->_curplayer,
        );
    };



lib/Games/Risk/Tk/Main.pm  view on Meta::CPAN

    };

    # event: _canvas_attack_cancel()
    # user wants to deselect a country to attack from.
    event _canvas_attack_cancel => sub {
        my ($self, $s) = @_[OBJECT, SESSION];

        # cancel attack source
        $self->_clear_src;
        $self->_w('canvas')->CanvasBind( '<1>', $s->postback('_canvas_attack_from') );

        # update status msg
        $self->_set_status( sprintf T('Attacking from ...') );
    };

    # event: _canvas_attack_target()
    # user wants to select target for her attack.
    event _canvas_attack_target => sub {
        my $self = shift;

        my $curplayer = $self->_curplayer;
        my $country   = $self->_country;

        # checks...
        return unless defined $country;
        if ( $country->owner->name eq $curplayer->name ) {
            # we own this country too, let's just change source of attack.
            $K->yield('_canvas_attack_from');
            return;
        }
        return unless $country->is_neighbour( $self->_src );

        # update status msg
        $self->_set_status( sprintf T('Attacking %s from %s'),
            $country->name, $self->_src->name );

        # store opponent
        $self->_set_dst( $country );

        # update gui to reflect new state
        $self->_w('canvas')->CanvasBind('<1>', undef);
        $self->_w('canvas')->CanvasBind('<3>', undef);
        $self->_action('attack_done')->disable;

        # signal controller
        $K->post(risk => attack => $self->_src, $country);
    };

    # event: _canvas_configure( undef, [$canvas, $w, $h] )
    # Called when canvas is reconfigured. new width and height available
    # with ($w, $h). note that reconfigure is also window motion.
    event _canvas_configure => sub {
        my ($self, $args) = @_[OBJECT, ARG1];
        my ($c, $neww, $newh) = @$args;

        # check if we're at startup screen...
        my $map = Games::Risk->new->map;
        if ( defined $map ) {
            # in a game
            # create a new image resized to fit new dims
            my $magick = Image::Magick->new;
            $magick->Read( $map->background );
            $magick->Scale(width=>$neww, height=>$newh);

            # install this new image inplace of previous background
            my $img = $c->Photo( -data => encode_base64( $magick->ImageToBlob ) );
            $c->delete('background');
            $c->createImage(0, 0, -anchor=>'nw', -image=>$img, -tags=>['background']);
            $c->lower('background', 'all');

            # update zoom factors. note that we don't want to resize greyscale
            # image since a) it takes time, which is unneeded since this image
            # is not displayed and b) greyscale are quite close from country to
            # country, and resizing will blur this to the point that it's no
            # longer usable. therefore, just storing a zoom factor and using it
            # will be enough for greyscale.
            my $zoom = $self->_zoom;
            my $orig = $self->_orig_bg_size;
            $zoom->set_coordx( $neww / $orig->coordx );
            $zoom->set_coordy( $newh / $orig->coordy );

            # force country redraw, for them to be correctly placed on the new
            # map.
            $K->yield('_country_redraw', $_) foreach $map->countries;

        } else {
            # delete existing images
            $c->delete('startup');

            # create the initial welcome screen
            my @tags = ( -tags => ['startup'] );
            # first a background image...
            $c->createImage (
                $neww/2, $newh/2,
                -anchor => 'center',
                -image  => $mw->Photo( -file=>$SHAREDIR->file( "images", "splash.jpg") ),
                @tags,
            );
        }
    };

    # event: _canvas_motion( undef, [$canvas, $x, $y] )
    # Called when mouse is moving over the $canvas at coords ($x,$y).
    event _canvas_motion => sub {
        my ($self, $args) = @_[OBJECT, ARG1];

        my (undef, $x,$y) = @$args; # first param is canvas

        # correct with zoom factor
        my $zoom = $self->_zoom;
        $x /= defined($zoom) ? $zoom->coordx : 1;
        $y /= defined($zoom) ? $zoom->coordy : 1;

        # get greyscale pointed by mouse, this may die if moving too fast
        # outside of the canvas. we just need the 'red' component, since
        # green and blue will be the same.
        my $grey = 0;
        eval { ($grey) = $self->_greyscale->get($x,$y) };
        return unless defined $self->_map;
        my $country    = $self->_map->country_get($grey);



( run in 1.818 second using v1.01-cache-2.11-cpan-39bf76dae61 )