From e21591fc06f2ff9808e422aa2c90ec1c6b5934b9 Mon Sep 17 00:00:00 2001 From: Pragmatic Software Date: Sat, 3 Jul 2021 23:13:36 -0700 Subject: [PATCH] Plugins/Battleship: when sending initial boards, pause game until `pbot.output_queue_flushed` received; added `pause` and `unpause` commands --- Plugins/Battleship.pm | 66 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/Plugins/Battleship.pm b/Plugins/Battleship.pm index d398c9bd..ff1d5987 100644 --- a/Plugins/Battleship.pm +++ b/Plugins/Battleship.pm @@ -114,6 +114,11 @@ sub initialize { # create game state machine $self->create_states; + + # receive notification when all messages in IRC output queue have been sent + $self->{pbot}->{event_dispatcher}->register_handler( + 'pbot.output_queue_flushed', sub { $self->on_output_queue_flushed(@_) } + ); } sub unload { @@ -126,11 +131,30 @@ sub unload { $self->end_game_loop; } +# the game is paused at the beginning when sending the player boards to all +# the players and then resumed when the output queue has depleted. this prevents +# game events from queuing up while the board messages are being slowly +# trickled out to the ircd to avoid filling up its message queue (and getting +# disconnected with 'excess flood'). this event handler resumes the game once +# the boards have finished transmitting, unless the game was manually paused +# by a player. +sub on_output_queue_flushed { + my ($self) = @_; # we don't care about the other event arguments + + print "BATTLESHIDFPF ADF!!\n"; + + # unless paused by a player, resume the game + if (not $self->{state_data}->{paused_by_player}) { + $self->{state_data}->{paused} = 0; + } + return 0; +} + # `battleship` bot command sub cmd_battleship { my ($self, $context) = @_; - my $usage = "Usage: battleship challenge|accept|decline|ready|unready|bomb|board|score|players|join|quit|kick|abort; see also: battleship help "; + my $usage = "Usage: battleship challenge|accept|decline|ready|unready|bomb|board|score|players|pause|quit|kick|abort; see also: battleship help "; # strip leading and trailing whitespace $context->{arguments} =~ s/^\s+|\s+$//g; @@ -284,6 +308,21 @@ sub cmd_battleship { return "/msg $channel $nick: The game has been aborted."; } + when (['pause', 'unpause']) { + if ($command eq 'pause') { + $self->{state_data}->{paused} = 1; + + # this pause was set by a player. + # this is used by on_output_queue_flushed() to know if it's okay to unpause automatically + $self->{state_data}->{paused_by_player} = 1; + } else { + $self->{state_data}->{paused} = 0; + $self->{state_data}->{paused_by_player} = 0; + } + + return "/msg $channel $nick has " . ($self->{state_data}->{paused} ? 'paused' : 'unpaused') . " the game!"; + } + when ('score') { if ($self->{current_state} ne 'move' and $self->{current_state} ne 'attack') { return "There is no Battleship score to show right now."; @@ -613,10 +652,6 @@ sub place_ship { } if ($self->check_ship_placement($x, $y, $o, $l)) { - if ($self->{debug}) { - $self->{pbot}->{logger}->log("SUCCESS!\n"); - } - if (!$o) { $self->{vert}++; @@ -887,7 +922,7 @@ sub perform_attack { $player->{sunk}++; $victim->{ships}--; - my $length = $self->{board}->[$x][$y]->{length}; + my $length = $self->{board}->[$x][$y]->{length}; my %ship_names = ( 5 => 'battleship', @@ -1144,6 +1179,11 @@ sub show_battlefield { sub run_one_state { my ($self) = @_; + # don't run a game loop if we're paused + if ($self->{state_data}->{paused}) { + return; + } + # check for naughty or missing players my $players = 0; @@ -1225,8 +1265,10 @@ sub create_states { # initialize state data $self->{state_data} = { - players => [], # array of player data - ticks => 0, # number of ticks elapsed + players => [], # array of player data + ticks => 0, # number of ticks elapsed + paused => 0, # is the game paused? + paused_by_player => 0, # game was manually paused by a player }; $self->{states} = { @@ -1356,6 +1398,14 @@ sub state_genboard { sub state_showboard { my ($self, $state) = @_; + # pause the game to send the boards to all the players. + # this is due to output pacing; the messages are trickled out slowly + # to avoid overflowing the ircd's receive queue. we do not want the + # game state to advance while the messages are being sent out. the + # game will resume when the `pbot.output_queue_flushed` notification + # is received. + $state->{paused} = 1; + for (my $player = 0; $player < @{$state->{players}}; $player++) { $self->send_message($self->{channel}, "Showing battlefield to $state->{players}->[$player]->{name}..."); $self->show_battlefield($player);