| Games-Pandemic documentation | Contained in the Games-Pandemic distribution. |
Games::Pandemic::Controller - controller for a pandemic game
version 1.111030
Special event card: move a player in any city.
Player has closed current game.
Player wishes to move game forward.
Request from $player to remove some @cards from her hands.
Special event card: rearrange infections to come.
Special event card: add a new research station.
Create a new game: (re-)initialize the map, and various internal states.
Special event card: prevent disease propagation during this turn.
Special event card: remove a city from the game.
Jerome Quelin
This software is Copyright (c) 2009 by Jerome Quelin.
This is free software, licensed under:
The GNU General Public License, Version 2, June 1991
| Games-Pandemic documentation | Contained in the Games-Pandemic distribution. |
# # This file is part of Games-Pandemic # # This software is Copyright (c) 2009 by Jerome Quelin. # # This is free software, licensed under: # # The GNU General Public License, Version 2, June 1991 # use 5.010; use strict; use warnings; package Games::Pandemic::Controller; BEGIN { $Games::Pandemic::Controller::VERSION = '1.111030'; } # ABSTRACT: controller for a pandemic game use List::Util qw{ shuffle }; use List::MoreUtils qw{ all }; use MooseX::Singleton; # should come before any other moose use MooseX::POE; use MooseX::SemiAffordanceAccessor; use Readonly; use Games::Pandemic::Card::Epidemic; use Games::Pandemic::Deck; use Games::Pandemic::Map::Pandemic; use Games::Pandemic::Player; Readonly my $K => $poe_kernel; # -- accessors # -- initialization # # START() # # called as poe session initialization. # sub START { $K->alias_set('controller'); } # -- public events event action => sub { my ($action, @params) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $player = $game->curplayer; # FIXME: check src vs current player $K->yield("_action_$action", @params); }; event airlift => sub { my ($player, $card, $selplayer, $city) = @_[ARG0..$#_]; # basic checks return unless $player->owns_card($card); my $from = $selplayer->location; return if $from eq $city; # play special card: move player $selplayer->set_location($city); _check_auto_clean_on_cure(); $K->post( main => 'player_move', $selplayer, $from, $city ); # drop the card my $game = Games::Pandemic->instance; $player->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $player, $card ); # check that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; event close => sub { my $game = Games::Pandemic->instance; $game->clear_map; $game->clear_cards_deck; $game->clear_infection_deck; $game->clear_players; $game->clear_players_in_turn; $game->clear_curplayer; $game->clear_too_many_cards; $game->has_ended; }; event continue => sub { my $game = Games::Pandemic->instance; # FIXME: check src vs current player given ( $game->state ) { when ('end_of_actions') { $K->yield('_draw_cards' ); } when ('end_of_cards') { $K->yield('_propagate' ); } when ('end_of_propagation') { $K->yield('_next_player'); } } }; event drop_cards => sub { my ($player, @cards) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $deck = $game->cards; # FIXME: check src vs $player # remove cards from player hands foreach my $card ( @cards ) { $player->drop_card( $card ); $deck->discard( $card ); $K->post( main => 'drop_card', $player, $card ); } # check again that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; event forecast => sub { my ($player, $card, @cards) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $deck = $game->infection; # basic checks return unless $player->owns_card($card); # check if cards are the correct ones my @curr = $deck->future; splice @curr, 0, @curr-6; return if join('+', sort @cards) ne join('+', sort @curr); # rearrange the cards $deck->next for 1..6; $deck->refill( reverse @cards ); # drop the card $player->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $player, $card ); # check that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; event government_grant => sub { my ($player, $card, $city) = @_[ARG0..$#_]; # basic checks return unless $player->owns_card($card); return if $city->has_station; # play special card: build station my $game = Games::Pandemic->instance; $city->build_station; $game->dec_stations; $K->post( main => 'build_station', $city ); # drop the card $player->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $player, $card ); # check that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; event new_game => sub { my $game = Games::Pandemic->instance; $game->has_started; # create the map my $map = Games::Pandemic::Map::Pandemic->new; $game->set_map( $map ); # 5 research stations available. FIXME: should it be part of the map? $game->set_stations( 5 ); # no epidemics and no outbreaks yet $game->set_epidemics( 0 ); $game->set_outbreaks( 0 ); # create the player cards deck my @pcards = shuffle $map->cards; { # insert epidemic cards my $nbinit = 8; # FIXME: 2 players * 4 cards, should not be fixed my $epidemics = 4; # FIXME: depends on game difficulty my $nbleft = scalar(@pcards) - $nbinit; my $perheap = $nbleft / $epidemics; foreach my $i ( reverse 0 .. $epidemics-1 ) { my $offset = int( $i * $perheap + rand($perheap) ); splice @pcards, $offset, 0, Games::Pandemic::Card::Epidemic->new; } } my $pcards = Games::Pandemic::Deck->new( cards => \@pcards ); $game->set_cards( $pcards ); # create the infection deck my @icards = shuffle $map->disease_cards; my $icards = Games::Pandemic::Deck->new( cards => \@icards ); $game->set_infection( $icards ); # do the initial infections foreach my $nb ( $map->start_diseases ) { my $card = $icards->next; $K->yield( _infect => $card->city, $nb ); $icards->discard( $card ); } # signal main window that we have started a new game $K->post( main => 'new_game' ); # create the players # FIXME: by now we're creating a fixed set of players, should be # configurable # FIXME: initial number of card depends of map / number of players my @roles = shuffle qw{ Researcher Scientist OperationsExpert Medic }; $K->yield( _new_player => $_, 4 ) for @roles[0..1]; #$K->yield( _new_player => 'Researcher', 4 ); #$K->yield( _new_player => 'Scientist', 4 ); #$K->yield( _new_player => 'Medic', 4 ); #$K->yield( _new_player => 'Dispatcher', 4 ); #$K->yield( _new_player => 'OperationsExpert', 4 ); # start the game $K->yield( '_next_player' ); }; event one_quiet_night => sub { my ($player, $card) = @_[ARG0..$#_]; # basic check return unless $player->owns_card($card); # play special card my $game = Games::Pandemic->instance; $game->disable_propagation; # FIXME: update gui? # drop the card $player->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $player, $card ); # check that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; event resilient_population => sub { my ($player, $card, $city) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $deck = $game->infection; my $after_epidemic = ($deck->nbdiscards == 0); my @past = $after_epidemic ? ( reverse $deck->future )[ 0 .. $deck->previous_nbdiscards-1 ] : $deck->past; # basic checks return unless $player->owns_card($card); return unless grep { $_->city eq $city } @past; # play special card: make population resilient if ( $after_epidemic ) { my @future = $deck->future; $deck->clear_cards; $deck->refill( grep { $_->city ne $city } @future ); } else { $deck->clear_pile; $deck->discard( grep { $_->city ne $city } @past ); } # drop the card $player->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $player, $card ); # check that there are not too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); return; } $K->yield( $game->next_step ) if $game->too_many_cards; $game->clear_too_many_cards; }; # -- private event # # event: _action_build() # # request to build a research station. # event _action_build => sub { my $game = Games::Pandemic->instance; my $curp = $game->curplayer; # invalid request return $K->yield('_next_action') unless $curp->is_build_possible; # FIXME: check research station count # build station my $city = $curp->location; $city->build_station; $game->dec_stations; $K->post( main => 'build_station', $city ); # player loose a card if ( not $curp->can_build_anywhere ) { my $card = $curp->owns_city_card($city); $curp->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $curp, $card ); } $K->yield('_action_done'); }; # # event: _action_charter($player, $city) # # request to charter $player to $city. # event _action_charter => sub { my ($player, $city) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; return $K->yield('_next_action') if $player ne $curp; # FIXME: dispatcher # get the card my $card = $curp->owns_city_card($player->location); return $K->yield('_next_action') unless defined $card; # move the player my $from = $player->location; $player->set_location($city); $K->post( main => 'player_move', $player, $from, $city ); _check_auto_clean_on_cure(); $K->yield('_action_done'); # drop the card $curp->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $curp, $card ); }; # # event: _action_treat($disease) # # request to treat $disease from current player location. # event _action_treat => sub { my $disease = $_[ARG0]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; my $city = $curp->location; my $nb = $city->get_infection($disease); return $K->yield('_next_action') if $nb == 0; my $nbtreat = ( $curp->treat_all || $disease->has_cure ) ? $nb : 1; $city->treat($disease, $nbtreat); $disease->return($nbtreat); $K->post( main => 'treatment', $city ); # check if disease is eradicated $K->yield( '_eradicate', $disease ) if $disease->has_cure && $disease->nbleft == $disease->nbmax; $K->yield('_action_done'); }; # # event: _action_discover($disease, @cards) # # request to discover a cure. # event _action_discover => sub { my ($disease, @cards) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; my $deck = $game->cards; # various checks return $K->yield('_next_action') if $disease->has_cure # nothing to do || !$curp->location->has_station # no research station || scalar(@cards) != $curp->cards_needed # not enough cards || not(all { $_->isa('Games::Pandemic::Card::City') } @cards) # not the right cards || not(all { $curp->owns_card($_) } @cards) # not the right player || not(all { $_->city->disease eq $disease } @cards) # wrong cards ; # yup, we can exchange cards for a cure foreach my $card ( @cards ) { $curp->drop_card($card); $deck->discard($card); $K->post( main => 'drop_card', $curp, $card ); } $disease->find_cure; $K->post( main => 'cure', $disease ); _check_auto_clean_on_cure(); # check if disease is eradicated $K->yield( '_eradicate', $disease ) if $disease->has_cure && $disease->nbleft == $disease->nbmax; # check if game is won return $K->yield('_all_cures_discovered') if all { $_->has_cure } $game->map->all_diseases; $K->yield('_action_done'); }; # # event: _action_done() # # action is finished. # event _action_done => sub { my $game = Games::Pandemic->instance; # turn is done my $curp = $game->curplayer; $curp->action_done; $K->post( main => 'action_done' ); # next step would be... my $event = $curp->actions_left == 0 ? '_end_of_actions' : '_next_action'; # ... unless a player has too many cards foreach my $player ( $game->all_players ) { next if $player->nb_cards <= $player->max_cards; $game->set_too_many_cards($player); $game->set_next_step($event); $K->post( main => 'too_many_cards', $player ); return; } # everything's fine, we can continue $K->yield( $event ); }; # # event: _action_fly($player, $city) # # request to fly $player to $city. # event _action_fly => sub { my ($player, $city) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; return $K->yield('_next_action') if $player ne $curp; # FIXME: dispatcher # get the card my $card = $curp->owns_city_card($city); return $K->yield('_next_action') unless defined $card; # move the player my $from = $player->location; $player->set_location($city); $K->post( main => 'player_move', $player, $from, $city ); _check_auto_clean_on_cure(); $K->yield('_action_done'); # drop the card $curp->drop_card( $card ); $game->cards->discard( $card ); $K->post( main => 'drop_card', $curp, $card ); }; # # event: _action_move($player, $city) # # request to move $player to $city by proximity. # event _action_move => sub { my ($player, $city) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; return $K->yield('_next_action') if $player ne $curp; # FIXME: dispatcher if ( $player->can_travel_to($city) ) { my $from = $player->location; $player->set_location($city); $K->post( main => 'player_move', $player, $from, $city ); _check_auto_clean_on_cure(); $K->yield('_action_done'); } else { # invalid move $K->yield('_next_action'); } }; # # event: _action_pass() # # user wishes to pass. # event _action_pass => sub { my $game = Games::Pandemic->instance; my $curp = $game->curplayer; # nothing to do - user is just passing $curp->set_actions_left(0); $K->post( main => 'action_done' ); $K->yield( '_end_of_actions' ); }; # # event: _action_share($card, $player) # # request to give $card to $player. # event _action_share => sub { my ($card, $player) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; my $city = $curp->location; return $K->yield('_next_action') if $player eq $curp; return $K->yield('_next_action') if $player->location ne $city; return $K->yield('_next_action') unless $curp->owns_card($card); return $K->yield('_next_action') unless $card->isa('Games::Pandemic::Card::City'); return $K->yield('_next_action') unless $card->city eq $city || $curp->can_share_anywhere; # give the card $curp->drop_card( $card ); $player->gain_card( $card ); $K->post( main => 'drop_card', $curp, $card ); $K->post( main => 'gain_card', $player, $card ); $K->yield('_action_done'); }; # # event: _action_shuttle($player, $city) # # request to move $player to $city by research station shuttle. # event _action_shuttle => sub { my ($player, $city) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $curp = $game->curplayer; return $K->yield('_next_action') if $player ne $curp; # FIXME: dispatcher # get the card return $K->yield('_next_action') unless $player->can_shuttle_to($city); # move the player my $from = $player->location; $player->set_location($city); $K->post( main => 'player_move', $player, $from, $city ); _check_auto_clean_on_cure(); $K->yield('_action_done'); }; # # event: _all_cures_discovered() # # sent when game has been won. # event _all_cures_discovered => sub { $K->post( main => 'all_cures_discovered' ); $K->yield( '_game_over' ); }; # # _deal_card( $player, $nb ); # # deal $nb cards to $player. check whether player has too much cards in # her hands, and also for game over condition. # event _deal_card => sub { my ($player, $nb) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; my $deck = $game->cards; # deal some cards to the players foreach my $i ( 1 .. $nb ) { my $card = $deck->next; if ( not defined $card ) { # no more cards - game is over $K->yield('_no_more_cards'); return; } # check if we hit a new epidemic if ( $card->isa('Games::Pandemic::Card::Epidemic') ) { $K->yield( '_epidemic', $card ); next; } # regular card, dealing it to player $player->gain_card($card); $K->post(main=>'gain_card', $player, $card); } # check if player has too much cards if ( $player->nb_cards > $player->max_cards ) { $game->set_too_many_cards($player); $K->post( main => 'too_many_cards', $player ); } }; # # event: _draw_cards() # # sent when player needs to draw her cards. # event _draw_cards => sub { my $game = Games::Pandemic->instance; my $curp = $game->curplayer; # FIXME: is 2 cards fixed or map-dependant? $K->yield( '_deal_card', $curp, 2 ); $game->clear_next_step; $game->set_state('end_of_cards'); $K->post( main => 'end_of_cards' ); }; # # event: _game_over() # # sent when game is over (either win or loose), user cannot do anything # but close the game. # event _game_over => sub { my $game = Games::Pandemic->instance; $game->has_ended; $K->post( main => 'game_over' ); }; # # event: _end_of_actions() # # sent when player finished her actions # event _end_of_actions => sub { my $game = Games::Pandemic->instance; $game->set_state('end_of_actions'); $K->post( main => 'end_of_actions' ); }; # # event: _epidemic( $card ) # # an epidemic $card has been drawn. # event _epidemic => sub { my $game = Games::Pandemic->instance; my $deck = $game->infection; # epidemic first strikes hard a new city... my $card = $deck->last; my $city = $card->city; $K->yield( '_infect', $city, 3 ); $K->post( main => 'epidemic', $city ); # then already hit cities ready for a new turn... $deck->discard( $card ); my @cards = $deck->past; $deck->clear_pile; $deck->refill( shuffle @cards ); # update infection rate $game->inc_epidemics; # discard the epidemic card $game->cards->discard( $_[ARG0] ); }; # # event: _eradicate($disease) # # $disease has been eradicated. # event _eradicate => sub { my $disease = $_[ARG0]; $disease->eradicate; $K->post( main => 'eradicate', $disease ); }; # # _infect( $city [, $nb [, $disease [, $seen ] ] ] ); # # infect $city with $nb items of $disease. perform an outbreak on # neighbour cities if needed. $nb defaults to 1, $disease defaults to # the city default disease. if there's an outbreak, keep a list of # cities already C<$seen> (a hash reference). # event _infect => sub { my ($city, $nb, $disease, $seen) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; $nb //= 1; $disease //= $city->disease; $seen //= {}; # FIXME: padre// # disease eradicated: no infection! \o/ return if $disease->is_eradicated; return if $disease->has_cure && grep { $_->location eq $city } grep { $_->auto_clean_on_cure } $game->all_players; # perform the infection & update the gui my ($outbreak, $nbreal) = $city->infect($nb, $disease); # update the disease $disease->take($nbreal); if ( $disease->nbleft <= 0 ) { $K->yield('_no_more_cubes', $disease); return; } $K->post( main => 'infection', $city, $outbreak ); return unless $outbreak && !$seen->{$city}; # update number of outbreaks $game->inc_outbreaks; if ( $game->nb_outbreaks == 8 ) { # FIXME: map dependant? $K->yield('_too_many_outbreaks'); return; } # chaining infections $seen->{$city}++; $K->yield( '_infect', $_, 1, $disease, $seen ) for $city->neighbours; }; # # event: _new_player( $role, $nb ) # # request to create & store a new player, having $role and starting with # $nb cards. # event _new_player => sub { my ($role, $nbcards) = @_[ARG0..$#_]; my $game = Games::Pandemic->instance; $role = "Games::Pandemic::Role::$role"; my $player = Games::Pandemic::Player->new_with_traits(traits=>[$role]); $game->add_player($player); $K->post( main => 'new_player', $player ); $K->yield( '_deal_card', $player, $nbcards ); }; # # event: _next_action() # # sent for player to continue its turn # event _next_action => sub { $K->post( main => 'next_action' ); }; # # event: _next_player( $player ) # # sent when $player should start to play its turn. # event _next_player => sub { my $game = Games::Pandemic->instance; my $player = $game->next_player; if ( not defined $player ) { $game->reinit_players( $game->all_players ); $player = $game->next_player; } $game->set_curplayer( $player ); $game->set_state('actions'); $player->set_actions_left(4); $K->post( main => 'next_player', $player ); $K->yield( '_next_action' ); }; # # event: _no_more_cards() # # sent when controller cannot deal any more cards, and game is over. # event _no_more_cards => sub { $K->post( main => 'no_more_cards' ); $K->yield('_game_over'); }; # # event: _no_more_cubes($disease) # # sent when a $disease has spread over any control, and game is over. # event _no_more_cubes => sub { my $disease = $_[ARG0]; $K->post( main => 'no_more_cubes', $disease ); $K->yield('_game_over'); }; # # event: _propagate() # # sent to do the regular disease propagation. # event _propagate => sub { my $game = Games::Pandemic->instance; if ( $game->propagation ) { my $icards = $game->infection; # propagate diseases do { my $card = $icards->next; $K->yield( _infect => $card->city, 1 ); $icards->discard( $card ); } for 1 .. $game->infection_rate; } else { $game->enable_propagation; } # update game state $game->set_state('end_of_propagation'); $K->post( main => 'end_of_propagation' ); }; # # event: _too_many_outbreaks() # # sent when there are too many outbreaks, and game is over. # event _too_many_outbreaks => sub { $K->post( main => 'too_many_outbreaks' ); $K->yield('_game_over'); }; # -- private subs sub _check_auto_clean_on_cure { my $game = Games::Pandemic->instance; # only diseases with a cure are eligible my @diseases = grep { $_->has_cure } $game->map->all_diseases ; foreach my $player ( $game->all_players ) { # only works for player with auto_clean_on_cure property next unless $player->auto_clean_on_cure; # check all diseases my $city = $player->location; foreach my $disease ( @diseases ) { my $nb = $city->get_infection( $disease ); next unless $nb; # no infection, move on # yup, let's treat automatically # FIXME - should be in a sub? $city->treat($disease, $nb); $disease->return($nb); $K->post( main => 'treatment', $city ); # check if disease is eradicated $K->yield( '_eradicate', $disease ) if $disease->has_cure && $disease->nbleft == $disease->nbmax; } } } no Moose; __PACKAGE__->meta->make_immutable; 1;
__END__