App-SweeperBot

 view release on metacpan or  search on metacpan

lib/App/SweeperBot.pm  view on Meta::CPAN

package App::SweeperBot;

# minesweeper.pl
#
# Win32::Screenshot, Win32::GuiTest, and Image::Magick are needed for this
# program. Use ActivePerl's PPM to install the first two:
#   ppm> install Win32-GuiTest
#   ppm> install http://theoryx5.uwinnipeg.ca/ppms/Win32-Screenshot.ppd
#
# The version of Image-Magick used by this code can be found at
# http://www.bribes.org/perl/ppmdir.html .  Different ImageMagick
# distributions may result in different signature codes.
#
# 20050726, Matt Sparks (f0rked), http://f0rked.com

=head1 NAME

App::SweeperBot - Play windows minesweeper, automatically!

=head1 SYNOPSIS

	C:\Path\To\Distribution> SweeperBot.exe

=head1 DESCRIPTION

This is alpha code, and released for testing and demonstration
purposes only.  It is still under active development.

Using this code for playing minesweeper on a production basis is
strongly discouraged.

=head1 METHODS

=cut

use strict;
use warnings;
use Carp;
use NEXT;

use 5.006;

our $VERSION = '0.03';

use Scalar::Util qw(looks_like_number);
use Win32::Process qw(NORMAL_PRIORITY_CLASS);

use constant DEBUG => 0;
use constant VERBOSE => 0;
use constant CHEAT => 1;
use constant UBER_CHEAT => 0;

use constant SMILEY_LENGTH => 26;

# The minimum and maximum top dressings define the range in which
# we'll look for a smiley, which we use to calibrate our board.  Different
# windows themes put them in different places.

use constant MINIMUM_TOP_DRESSING => 56;
use constant MAXIMUM_TOP_DRESSING => 75;

my $Smiley_offset = 0;

use constant CHEAT_SAFE    => "d0737abfd3abdacfeb15d559e28c2f0b3662a7aa03ac5b7a58afc422110db75a";	# Old 58
# use constant CHEAT_SAFE    => "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e";	# Old 510

lib/App/SweeperBot.pm  view on Meta::CPAN

# Click the left button of the mouse.
# Arguments: x, y as ABSOLUTE positions on the screen
sub click {
    my($this, $x,$y,$button)=@_;
    $button ||= "{LEFTCLICK}";
    MouseMoveAbsPix($x,$y);
    print "Button: $button ($x,$y)\n" if DEBUG;
    SendMouse($button);
    return;
}

=head2 new_game

	$sweeperbot->new_game;

Starts a new game of minesweeper.  C<locate_minesweeper()> must
have been called previously for this to work.

Does not return a value, nor does it check to see if a new game
has been successfully started.

=cut


# TODO: Rather than using the reset variables, we should properly
# calculate the location of our reset button.  We have calibration
# code elsewhere that essentially finds the smiley, we just have to
# click on it.

sub new_game {
    my ($this) = @_;
    our ($reset_x,$reset_y);
    $this->click($reset_x,$reset_y);
    return;
}

=head2 focus

	$sweeperbot->focus;

Focuses on t he  minesweeper window by clicking a little left of the
smiley.  Does not check for success.  Returns nothing.

=cut

# Focus on the Minesweeper window by clicking a little to the left of the game
# button.
sub focus {
    my ($this) = @_;
    our ($reset_x, $reset_y);
    $this->click($reset_x - FOCUS_X_OFFSET ,$reset_y);
    return;
}

=head2 capture_square

	my $image = $sweeperbot->capture_square($x,$y);

Captures the square ($x,$y) of the minesweeper board.  (1,1) is
the top-left of the grid.  No checking is done to see if the square
is actually on the board.  Returns the image as an L<Image::Magick>
object.

=head3 Bugs in capture_square

On failure to capture the image, this returns an empty
L<Image::Magick> object.  This is considered a bug; in the future
C<capture_square> will throw an exception on error.

C<capture_square> depends upon calibration routines that are
currently implemented in the L</value> method; calling it before
the first call to L</value> can result in incorrect or inconsistent
results.  In future releases C<capture_square> will automatically
calibrate itself if required.

=cut

# TODO GuiTest doesn't check the Image::Magick return codes, it
# just assumes everything works.  We should consider writing our
# own code that _does_ test, since these diagnostics are very
# useful when things go wrong.

sub capture_square {
    my($this, $sx,$sy)=@_;
    our($l,$t);
    my $image=CaptureRect(
        $l+SQUARE1X+($sx-1)*SQUARE_W,
        $t+$Square1Y+($sy-1)*SQUARE_H,
        SQUARE_W,
        SQUARE_H
    );
    return $image;
}

=head2 value

	my $value = $sweeperbot->value($x,$y);

Returns the value in position ($x,$y) of the board, square
(1,1) is considered the top-left of the grid.  Possible values
are given below:

	0-8		# Number of adjacent mines (0 = empty)
	bomb		# A bomb (only when game lost)
	bomb_hilight	# The bomb we hit (only when game lost)
	flag		# A flag
	unpressed	# An unpressed square

Support of question-marks is not provided, but may be included
in a future version.

Throws an exception on failure.

=cut

sub value {
    my($this, $sx,$sy)=@_;

    if (not $Square1Y) {
	# We haven't calibrated our board yet.  Let's see if we can
	# find a square we recognise.

        CALIBRATION: {
	    for (my $i = MIN_SQUARE1Y; $i <= MAX_SQAURE1Y; $i++) {
	        $Square1Y = $i;

	        warn "Trying to calibrate board $i pixels down\n" if DEBUG;

	        my $sig = $this->capture_square(1,1)->Get("signature");

	        # Known signature, break out of calibration loop.
	        last CALIBRATION if ($contents_of_square{$sig});
	    }

	# If we're here, we couldn't calibrate
	die "Board calibration failed\n";
        }
    }



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