# minesweeper.pl
# This is a program to manipulate a win32 minesweeper window. So far it can
# determine a value of a square, click squares, find the size of the grid,
# determine if the game is over, and start a new game.
#
# The original intent of this program was to beat minesweeper automatically,
# but after writing the framework, I decided that I would rather not code
# the algorithm portion. Thus, I'm releasing it in hopes that someone does
# finish the program. If you complete my work, please send me a copy, I'd like
# to see it. root@f0rked.com
#
# 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
#
# Windows versions of ImageMagick (which also install PerlMagick) can be located
# at http://imagemagick.org/script/binary-releases.php
#
# 20050726, Matt Sparks (f0rked), http://f0rked.com

use Win32::Screenshot;
use Win32::GuiTest qw(
    FindWindowLike
    GetWindowRect
    SendMouse
    MouseMoveAbsPix);

# Click the left button of the mouse.
# Arguments: x, y as ABSOLUTE positions on the screen
sub click {
    my($x,$y)=@_;
    MouseMoveAbsPix($x,$y);
    SendMouse("{LEFTCLICK}");
}

# Start a new game
sub new_game {
    click($reset_x,$reset_y);
}

# Focus on the Minesweeper window by clicking a little to the left of the game
# button.
sub focus {
    click($reset_x-50,$reset_y);
}

# Get an image capture of a single field.
# Arguments: sx, sy where 1,1 is the top left field in the grid.
# Returns Image::Magick object
sub capture_square {
    my($sx,$sy)=@_;
    my $image=CaptureRect(
        $l+$square1x+($sx-1)*$square_w,
        $t+$square1y+($sy-1)*$square_h,
        $square_w,
        $square_h);
    return $image;
}

# Determine the value of a single field
# Arguments: sx, sy
# Returns string value
sub value {
    my($sx,$sy)=@_;
    my $sig=capture_square($sx,$sy)->Get("signature");
    print $sig."\n";
    my %values=(
        "empty"        => "0b6f3e019208789db304a8a8c8bd509dacf62050a962ae9a0385733d6b595427",
        "unpressed"    => "35fc6aa19ab4b99bf7d4a750767ee329b773fb2709bec46204d0ffb0a2eae1e0",
        "1"            => "7a66485db1fee47e7c33acff15df5b48feccbc0328ea6e68795e52ce43649e1a",
        "2"            => "ab70100c9ac47c63edf679d838fbb10ca38a567a16132aaf42ed2fe159aa8605",
        "3"            => "799f98eb9f61f3e96def93145a6a065cf872e67647939a7e0f4c623f38f585c3",
        "4"            => "b5b29ae361a9acf85ac81abb440d5a3f7525fe80738a5770df90832d0367f7d6",
        "5"            => "bff653f26af9160d66965635c8306795ca2440cd1e4eebf0f315c7abd0242fc6",
        "6"            => "931b3e6a380fd85ee808fd4ac788123a0873bb3c1c30ec1737cea8e624ff866a",
        "7"            => "e5531a6de436ac50d36096b9d1b17bad2c919923650ca48063119f9868eb3943",
        "8"            => "c18dd2d3747aa97a9f432993de175bd32f8e38a70a8c122c94c737f8909bc3ca",
        "bomb"         => "ad10157084c576142c0b0e811ddf9f935c3aab5925831fe3bf9a2da226c0c6d9",
        "bomb_hilight" => "d748d75fb4fbff41cf54237a5e0fa919189a927f1776683f141a4e38feff06ab");
     while(my($key,$value)=each %values) {
         if ($sig eq $value) { return $key; }
     }
     return 0;
}

# Find the signature of a square. This probably shouldn't be used since all (?)
# of the signatures have already been determined.
sub sig {
    my($sx,$sy)=@_;
    my $im=capture_square($sx,$sy);
    return $im->Get("signature");
}

# Click on a field.
# Arguments: sx, sy
sub press {
    my($sx,$sy)=@_;
    click(
        $l+$square1x+($sx-1)*$square_w+$square_w/2,
        $t+$square1y+($sy-1)*$square_h+$square_w/2);
}

# Is the game over (we hit a mine)? 
# Returns -1 if game is over and we lost, 0 if not over, 1 if over and we won
sub game_over {
    # Capture game button and determine its sig
    # Game button is always at (x,56). X-value must be determined by 
    # calculation using formula: x=w/2-11
    # Size is 26x26
    my $sig=CaptureRect($l+$w/2-11,$t+56,26,26)->Get("signature");
    
    if ($sig eq "efef2037072c56fb029da1dd2cd626282173d0e1b2be39eab3e955cd2bcdc856") {
        return 1;
    }
    elsif ($sig eq "7cf1797ad25730136aa67c0a039b0c596f1aed9de8720999145248c72df52d1b") {
        return -1;
    }
    else { return 0; }
}

# About the window
our $id=(FindWindowLike(0, "^Minesweeper"))[0];
our($l,$t,$r,$b)=GetWindowRect($id);
our($w,$h)=($r-$l,$b-$t);
our($reset_x,$reset_y)=($l+$w/2,$t+70);

# About each square
our($square_w,$square_h)=(16,16); # size of a square: 16x16
our($square1x,$square1y)=(15,96); # location of the top left square: (15,96)

# Figure out our total number of squares
# "header" of window is 96px tall
# left side: 15px, right side: 11px
# bottom is 11px tall
our($squares_x,$squares_y)=(($w-15-11)/$square_w,($h-96-11)/$square_h);
our $squares=$squares_x*$squares_y;

# Demo the program
print "Width: $w, height: $h\n";
print "$squares_x across, $squares_y down, $squares total\n";

print "Focusing on the window\n";
focus;

print "Starting a new game\n";
new_game;

print "Clicking square 4,5\n";
press(4,5);

print "Looping forever until the game ends\n";
while(1) {
    my $go=game_over;
    if ($go==1) { print "Game is over, we won.\n"; last; }
    elsif ($go==-1) { print "Game is over, we lost.\n"; last; } 
    sleep(1);
}

print "I'm done\n";
