[Date Prev][Date Next][Thread Prev][Thread Next] [Search] [Date Index] [Thread Index]

[MacPerl-Toolbox] T([ia]c|oe)



Here's my tic-tac-toe program.

I initially wrote it on the same day I posted the grid-click code; today I added the computer player.

(Yes, I did get the technique of concatenating a line of squares from Darryl Tang's version.)

#!perl -w

use Mac::Events;
use Mac::Windows;
use Mac::QuickDraw;
use Mac::Dialogs;

sub ltwh ($$$$) {
  my ($left, $top, $width, $height) = @_;
  new Rect ($left, $top, $left+$width, $top+$height);
}

@Lines = (
  [[0, 0], [0, 1], [0, 2]],
  [[1, 0], [1, 1], [1, 2]],
  [[2, 0], [2, 1], [2, 2]],

  [[0, 0], [1, 0], [2, 0]],
  [[0, 1], [1, 1], [2, 1]],
  [[0, 2], [1, 2], [2, 2]],

  [[0, 0], [1, 1], [2, 2]],
  [[0, 2], [1, 1], [2, 0]],
);
$Title = 'Tic-Tac-Toe';

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

sub userect {
  SetDialogItemProc($Q_Dlg->window, $_[0], sub {
    FrameRect $Q_Dlg->item_box($_[1]);
  });
}

$Q_Dlg = new MacDialog (
  ltwh(100, 90, 250, 170),
  'Setup', 1, movableDBoxProc, 1,

  [kStaticTextDialogItem, ltwh(10, 10, 380, 16), 'Please select the player types:'],
  [kButtonDialogItem, ltwh(160, 140, 80, 20), 'Begin'],
  [kButtonDialogItem, ltwh( 60, 140, 80, 20), 'Quit'],

  [kRadioButtonDialogItem, ltwh(($w = 15)+8, 62,  80, 16), 'Human'],
  [kRadioButtonDialogItem, ltwh($w+8,        82,  80, 16), 'Computer'],
  [kUserDialogItem,        ltwh($w,          50, 100, 56)],
  [kStaticTextDialogItem,  ltwh($w+8,        42,  70, 16), '"X" Player'],

  [kRadioButtonDialogItem, ltwh(($w = 135)+8, 62,  80, 16), 'Human'],
  [kRadioButtonDialogItem, ltwh($w+8,         82,  80, 16), 'Computer'],
  [kUserDialogItem,        ltwh($w,           50, 100, 56)],
  [kStaticTextDialogItem,  ltwh($w+8,         42,  70, 16), '"O" Player'],
);
$Q_OK = 0;
$Q_Dlg->item_hit(2 => sub {$Q_OK = 1});
$Q_Dlg->item_hit(3 => sub {$Q_OK = -1});
SetDialogDefaultItem($Q_Dlg->window, 2);

$Q_Dlg->item_hit(4 => sub {$IsComp{'x'} = 0; $Q_Dlg->item_value($_[1], 1); $Q_Dlg->item_value($_[1]+1, 0)});
$Q_Dlg->item_hit(5 => sub {$IsComp{'x'} = 1; $Q_Dlg->item_value($_[1], 1); $Q_Dlg->item_value($_[1]-1, 0)});
$Q_Dlg->item_value(4, 1); $IsComp{'x'} = 0;
userect 6;

$Q_Dlg->item_hit(8 => sub {$IsComp{o} = 0; $Q_Dlg->item_value($_[1], 1); $Q_Dlg->item_value($_[1]+1, 0)});
$Q_Dlg->item_hit(9 => sub {$IsComp{o} = 1; $Q_Dlg->item_value($_[1], 1); $Q_Dlg->item_value($_[1]-1, 0)});
$Q_Dlg->item_value(8, 1); $IsComp{'o'} = 0;
userect 10;

WaitNextEvent until $Q_OK;
dispose $Q_Dlg;
exit if $Q_OK == -1;

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

sub Init {
  @Board = (
    [' ', ' ', ' '],
    [' ', ' ', ' '],
    [' ', ' ', ' '],
  );
  $Player = 'o';
  $PWin = undef;
  $Clicks = 0;
  SetPort $win->window;
  InvalRect $win->window->portRect;
  SetWTitle $win->window, "$Title - @{[uc $Player]}'s turn";
  1;
}

sub automove {
  # This algorithm is NOT perfect. I thought about improving
  # it, but why not let the other player win occasionally?

  # The rand() is so that it will randomly select from 
  # equal moves.

  my ($me) = @_;
  my @pmoves;
  my $him = $me eq 'o' ? 'x' : 'o';

  $Board[1][1] eq ' ' and push @pmoves, [10 + rand, 1, 1];
  foreach (@Lines) {
    my $line = $Board[$$_[0][0]][$$_[0][1]]
             . $Board[$$_[1][0]][$$_[1][1]]
             . $Board[$$_[2][0]][$$_[2][1]];

    # Finish my lines
    $line eq " $me$me" and push @pmoves, [5 + rand, $$_[0][0], $$_[0][1]];
    $line eq "$me $me" and push @pmoves, [5 + rand, $$_[1][0], $$_[1][1]];
    $line eq "$me$me " and push @pmoves, [5 + rand, $$_[2][0], $$_[2][1]];

    # Block opposing lines
    $line eq " $him$him" and push @pmoves, [2 + rand, $$_[0][0], $$_[0][1]];
    $line eq "$him $him" and push @pmoves, [2 + rand, $$_[1][0], $$_[1][1]];
    $line eq "$him$him " and push @pmoves, [2 + rand, $$_[2][0], $$_[2][1]];

    # If nothing else, find empty space
    $line =~ /^ ..$/ and push @pmoves, [0 + rand, $$_[0][0], $$_[0][1]];
    $line =~ /^. .$/ and push @pmoves, [0 + rand, $$_[1][0], $$_[1][1]];
    $line =~ /^.. $/ and push @pmoves, [0 + rand, $$_[2][0], $$_[2][1]];
  }
  die unless @pmoves;
  @pmoves = sort {$b->[0] <=> $a->[0]} @pmoves;
  return @{$pmoves[0]}[1..2];
}

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

$win = new MacWindow (
  new Rect (100, 100, 300, 300),
  '',
  1,
  documentProc,
  1,
);

Init();

$win->sethook(layout => sub {
  my $wRect = $win->window->portRect;
  $CWidth = int($wRect->right / 3);
  $CHeight = int($wRect->bottom / 3);
  SizeWindow $win->window, $CWidth * 3, $CHeight * 3;
  SetPort $win->window; InvalRect $wRect;
});
$win->layout;

$win->sethook(redraw => sub {
  PenSize(2, 2); RGBForeColor new RGBColor(0,0,0);
  MoveTo($CWidth   - 1, 0);
  LineTo($CWidth   - 1, $CHeight*3);
  MoveTo($CWidth*2 - 1, 0);
  LineTo($CWidth*2 - 1, $CHeight*3);
  MoveTo(0,         $CHeight - 1);
  LineTo($CWidth*3, $CHeight - 1);
  MoveTo(0,         $CHeight*2 - 1);
  LineTo($CWidth*3, $CHeight*2 - 1);

  PenSize(5, 5);
  for ($h = 0; $h < 3; $h++) {
    for ($v = 0; $v < 3; $v++) {
      my $p = $Board[$h][$v];
      RGBForeColor($p !~ /[XO]/ ? new RGBColor(0,0,0)
                                : new RGBColor(0,65535,0));
      $p = lc $p;
      if ($p eq 'o') {
        FrameOval(new Rect(
          $h * $CWidth + $CWidth * .1,
          $v * $CHeight + $CHeight * .1,
          ($h+1) * $CWidth - $CWidth * .1,
          ($v+1) * $CHeight - $CHeight * .1,
        ));
      } elsif ($p eq 'x') {
        MoveTo(
          $h * $CWidth + $CWidth * .1,
          $v * $CHeight + $CHeight * .1,
        );
        LineTo(
          ($h+1) * $CWidth - $CWidth * .1 - 5,
          ($v+1) * $CHeight - $CHeight * .1 - 5,
        );
        MoveTo(
          ($h+1) * $CWidth - $CWidth * .1 - 5,
          $v * $CHeight + $CHeight * .1,
        );
        LineTo(
          $h * $CWidth + $CWidth * .1,
          ($v+1) * $CHeight - $CHeight * .1 - 5,
        );
      }
    }
  }
});

$win->sethook(click => sub {
  my $pt = $_[1];

  Init() and return if $PWin;

  my $gridh = int($pt->h/$CWidth);
  my $gridv = int($pt->v/$CHeight);
  return if $Board[$gridh][$gridv] ne ' ';

  my $r = new Rect(
    $gridh * $CWidth + ($CWidth * .1),
    $gridv * $CHeight + ($CWidth * .1),
    ($gridh+1) * $CWidth - ($CWidth * .1),
    ($gridv+1) * $CHeight - ($CWidth * .1)
  );

  SetPort($win->window);
  my ($in, $oin) = (1, 1);
  InvertRect($r);
  while (StillDown()) {
    $in = PtInRect(GetMouse, $r);
    if ($in != $oin) {
      InvertRect($r);
    } 
    $oin = $in;
  }
  return unless $in;
  move($gridh, $gridv);
});

sub move {
  my ($gridh, $gridv) = @_;
  return if $Board[$gridh][$gridv] ne ' ';
  $Clicks++;
  $Board[$gridh][$gridv] = $Player;
  $Player = ($Player eq 'o') ? 'x' : 'o';
  if ($Clicks >= 9) {
    SetWTitle $win->window, "$Title - Draw"; $PWin = -1;
  } else {
    SetWTitle $win->window, "$Title - @{[uc $Player]}'s turn";
  }
  InvalRect new Rect(
    $gridh * $CWidth + ($CWidth * .1),
    $gridv * $CHeight + ($CWidth * .1),
    ($gridh+1) * $CWidth - ($CWidth * .1),
    ($gridv+1) * $CHeight - ($CWidth * .1)
  );

  # Win check routine:
  foreach (@Lines) {
    next unless (my $p = $Board[$$_[0][0]][$$_[0][1]]) ne ' ';
    next unless    $p eq $Board[$$_[1][0]][$$_[1][1]];
    next unless    $p eq $Board[$$_[2][0]][$$_[2][1]];

    $PWin = $p;
    $Board[$$_[0][0]][$$_[0][1]] = uc $Board[$$_[0][0]][$$_[0][1]];
    $Board[$$_[1][0]][$$_[1][1]] = uc $Board[$$_[1][0]][$$_[1][1]];
    $Board[$$_[2][0]][$$_[2][1]] = uc $Board[$$_[2][0]][$$_[2][1]];
    InvalRect $win->window->portRect;
    SetWTitle $win->window, "$Title - Won";
  }
}

while ($win->window) {
  WaitNextEvent;
  if (!$PWin and $IsComp{$Player}) {
    move(automove($Player));
  }
}

END {$win->dispose if $win}

__END__

==== Want to unsubscribe from this list?
==== Send mail with body "unsubscribe" to macperl-toolbox-request@macperl.org