Three Dimensional Tic-Tac-Toe
'; } function tictac_module() { return; } function tictac_content(&$a) { $o = ''; if($_POST['move']) { $handicap = $a->argv[1]; $mefirst = $a->argv[2]; $dimen = $a->argv[3]; $yours = $a->argv[4]; $mine = $a->argv[5]; $yours .= $_POST['move']; } elseif($a->argc > 1) { $handicap = $a->argv[1]; $dimen = 3; } else { $dimen = 3; } $o .= '

3D Tic-Tac-Toe


'; $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine); $o .= $t->play(); $o .= 'New game
'; $o .= 'New game with handicap
'; $o .= <<< EOT

Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. In this case there are three levels. You win by getting three in a row on any level, as well as up, down, and diagonally across the different levels.

The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage.

EOT; return $o; } class tictac { private $dimen; private $first_move = true; private $handicap = 0; private $yours; private $mine; private $winning_play; private $you; private $me; private $debug = 1; private $crosses = array('011','101','110','112','121','211'); /* '001','010','011','012','021', '101','110','111','112','121', '201','210','211','212','221'); */ private $corners = array( '000','002','020','022', '200','202','220','222'); private $planes = array( array('000','001','002','010','011','012','020','021','022'), // horiz 1 array('100','101','102','110','111','112','120','121','122'), // 2 array('200','201','202','210','211','212','220','221','222'), // 3 array('000','010','020','100','110','120','200','210','220'), // vert left array('000','001','002','100','101','102','200','201','202'), // vert top array('002','012','022','102','112','122','202','212','222'), // vert right array('020','021','022','120','121','122','220','221','222'), // vert bot array('010','011','012','110','111','112','210','211','212'), // left vertx array('001','011','021','101','111','221','201','211','221'), // top vertx array('000','001','002','110','111','112','220','221','222'), // diag top array('020','021','022','110','111','112','200','201','202'), // diag bot array('000','010','020','101','111','121','202','212','222'), // diag left array('002','012','022','101','111','121','200','210','220'), // diag right array('002','011','020','102','111','120','202','211','220'), // diag x array('000','011','022','100','111','122','200','211','222') // diag x ); private $winner = array( array('000','001','002'), // board 0 winners - left corner across array('000','010','020'), // down array('000','011','022'), // diag array('001','011','021'), // middle-top down array('010','011','012'), // middle-left across array('002','011','020'), // right-top diag array('002','012','022'), // right-top down array('020','021','022'), // bottom-left across array('100','101','102'), // board 1 winners array('100','110','120'), array('100','111','122'), array('101','111','121'), array('110','111','112'), array('102','111','120'), array('102','112','122'), array('120','121','122'), array('200','201','202'), // board 2 winners array('200','210','220'), array('200','211','222'), array('201','211','221'), array('210','211','212'), array('202','211','220'), array('202','212','222'), array('220','221','222'), array('000','100','200'), // top-left corner 3d array('000','101','202'), array('000','110','220'), array('000','111','222'), array('001','101','201'), // top-middle 3d array('001','111','221'), array('002','102','202'), // top-right corner 3d array('002','101','200'), array('002','112','222'), array('002','111','220'), array('010','110','210'), // left-middle 3d array('010','111','212'), array('011','111','211'), // middle-middle 3d array('012','112','212'), // right-middle 3d array('012','111','210'), array('020','120','220'), // bottom-left corner 3d array('020','110','200'), array('020','121','222'), array('020','111','202'), array('021','121','221'), // bottom-middle 3d array('021','111','201'), array('022','122','222'), // bottom-right corner 3d array('022','121','220'), array('022','112','202'), array('022','111','200') ); function __construct($dimen,$handicap,$mefirst,$yours,$mine) { $this->dimen = 3; $this->handicap = (($handicap) ? 1 : 0); $this->mefirst = (($mefirst) ? 1 : 0); $this->yours = str_replace('XXX','',$yours); $this->mine = $mine; $this->you = $this->parse_moves('you'); $this->me = $this->parse_moves('me'); if(strlen($yours)) $this->first_move = false; } function play() { if($this->first_move) { if(rand(0,1) == 1) { $o .= '
You go first...

'; $this->mefirst = 0; $o .= $this->draw_board(); return $o; } $o .= '
I\'m going first this time...

'; $this->mefirst = 1; } if($this->check_youwin()) { $o .= '
You won!

'; $o .= $this->draw_board(); return $o; } if($this->fullboard()) $o .= 'Cat game!'; $move = $this->winning_move(); if(strlen($move)) { $this->mine .= $move; $this->me = $this->parse_moves('me'); } else { $move = $this->defensive_move(); if(strlen($move)) { $this->mine .= $move; $this->me = $this->parse_moves('me'); } else { $move = $this->offensive_move(); if(strlen($move)) { $this->mine .= $move; $this->me = $this->parse_moves('me'); } } } if($this->check_iwon()) $o .= '
I won!

'; if($this->fullboard()) $o .= 'Cat game!'; $o .= $this->draw_board(); return $o; } function parse_moves($player) { if($player == 'me') $str = $this->mine; if($player == 'you') $str = $this->yours; $ret = array(); while(strlen($str)) { $ret[] = substr($str,0,3); $str = substr($str,3); } return $ret; } function check_youwin() { for($x = 0; $x < count($this->winner); $x ++) { if(in_array($this->winner[$x][0],$this->you) && in_array($this->winner[$x][1],$this->you) && in_array($this->winner[$x][2],$this->you)) { $this->winning_play = $this->winner[$x]; return true; } } return false; } function check_iwon() { for($x = 0; $x < count($this->winner); $x ++) { if(in_array($this->winner[$x][0],$this->me) && in_array($this->winner[$x][1],$this->me) && in_array($this->winner[$x][2],$this->me)) { $this->winning_play = $this->winner[$x]; return true; } } return false; } function defensive_move() { for($x = 0; $x < count($this->winner); $x ++) { if(($this->handicap) && in_array('111',$this->winner[$x])) continue; if(in_array($this->winner[$x][0],$this->you) && in_array($this->winner[$x][1],$this->you) && (! in_array($this->winner[$x][2],$this->me))) return($this->winner[$x][2]); if(in_array($this->winner[$x][0],$this->you) && in_array($this->winner[$x][2],$this->you) && (! in_array($this->winner[$x][1],$this->me))) return($this->winner[$x][1]); if(in_array($this->winner[$x][1],$this->you) && in_array($this->winner[$x][2],$this->you) && (! in_array($this->winner[$x][0],$this->me))) return($this->winner[$x][0]); } return ''; } function winning_move() { for($x = 0; $x < count($this->winner); $x ++) { if(($this->handicap) && in_array('111',$this->winner[$x])) continue; if(in_array($this->winner[$x][0],$this->me) && in_array($this->winner[$x][1],$this->me) && (! in_array($this->winner[$x][2],$this->you))) return($this->winner[$x][2]); if(in_array($this->winner[$x][0],$this->me) && in_array($this->winner[$x][2],$this->me) && (! in_array($this->winner[$x][1],$this->you))) return($this->winner[$x][1]); if(in_array($this->winner[$x][1],$this->me) && in_array($this->winner[$x][2],$this->me) && (! in_array($this->winner[$x][0],$this->you))) return($this->winner[$x][0]); } } function offensive_move() { shuffle($this->planes); shuffle($this->winner); shuffle($this->corners); shuffle($this->crosses); if(! count($this->me)) { if($this->handicap) { $p = $this->uncontested_plane(); foreach($this->corners as $c) if((in_array($c,$p)) && (! $this->is_yours($c)) && (! $this->is_mine($c))) return($c); } else { if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1))) return '111'; $p = $this->uncontested_plane(); foreach($this->crosses as $c) if((in_array($c,$p)) && (! $this->is_yours($c)) && (! $this->is_mine($c))) return($c); } } if($this->handicap) { if(count($this->me) >= 1) { if(count($this->get_corners($this->me)) == 1) { if(in_array($this->me[0],$this->corners)) { $p = $this->my_best_plane(); foreach($this->winner as $w) { if((in_array($w[0],$this->you)) || (in_array($w[1],$this->you)) || (in_array($w[2],$this->you))) continue; if(in_array($w[0],$this->corners) && in_array($w[2],$this->corners) && in_array($w[0],$p) && in_array($w[2],$p)) { if($this->me[0] == $w[0]) return($w[2]); elseif($this->me[0] == $w[2]) return($w[0]); } } } } else { $r = $this->get_corners($this->me); if(count($r) > 1) { $w1 = array(); $w2 = array(); foreach($this->winner as $w) { if(in_array('111',$w)) continue; if(($r[0] == $w[0]) || ($r[0] == $w[2])) $w1[] = $w; if(($r[1] == $w[0]) || ($r[1] == $w[2])) $w2[] = $w; } if(count($w1) && count($w2)) { foreach($w1 as $a) { foreach($w2 as $b) { if((in_array($a[0],$this->you)) || (in_array($a[1],$this->you)) || (in_array($a[2],$this->you)) || (in_array($b[0],$this->you)) || (in_array($b[1],$this->you)) || (in_array($b[2],$this->you))) continue; if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) { return $a[0]; } elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) { return $a[2]; } } } } } } } } //&& (count($this->me) == 1) && (count($this->you) == 1) // && in_array($this->you[0],$this->corners) // && $this->is_neighbor($this->me[0],$this->you[0])) { // Yuck. You foiled my plan. Since you obviously aren't playing to win, // I'll try again. You may keep me busy for a few rounds, but I'm // gonna' get you eventually. // $p = $this->uncontested_plane(); // foreach($this->crosses as $c) // if(in_array($c,$p)) // return($c); // } // find all the winners containing my points. $mywinners = array(); foreach($this->winner as $w) foreach($this->me as $m) if((in_array($m,$w)) && (! in_array($w,$mywinners))) $mywinners[] = $w; // find all the rules where my points are in the center. $trythese = array(); if(count($mywinners)) { foreach($mywinners as $w) { foreach($this->me as $m) { if(($m == $w[1]) && ($this->uncontested_winner($w)) && (! in_array($w,$trythese))) $trythese[] = $w; } } } $myplanes = array(); for($p = 0; $p < count($this->planes); $p ++) { if($this->handicap && in_array('111',$this->planes[$p])) continue; foreach($this->me as $m) if((in_array($m,$this->planes[$p])) && (! in_array($this->planes[$p],$myplanes))) $myplanes[] = $this->planes[$p]; } shuffle($myplanes); // find all winners which share an endpoint, and which are uncontested $candidates = array(); if(count($trythese) && count($myplanes)) { foreach($trythese as $t) { foreach($this->winner as $w) { if(! $this->uncontested_winner($w)) continue; if((in_array($t[0],$w)) || (in_array($t[2],$w))) { foreach($myplanes as $p) if(in_array($w[0],$p) && in_array($w[1],$p) && in_array($w[2],$p) && ($w[1] != $this->me[0])) if(! in_array($w,$candidates)) $candidates[] = $w; } } } } // Find out if we are about to force a win. // Looking for two winning vectors with a common endpoint // and where we own the middle of both - we are now going to // grab the endpoint. The game isn't yet over but we've already won. if(count($candidates)) { foreach($candidates as $c) { if(in_array($c[1],$this->me)) { // return endpoint foreach($trythese as $t) if($t[0] == $c[0]) return($t[0]); elseif($t[2] == $c[2]) return($t[2]); } } // find opponents planes $yourplanes = array(); for($p = 0; $p < count($this->planes); $p ++) { if($this->handicap && in_array('111',$this->planes[$p])) continue; if(in_array($this->you[0],$this->planes[$p])) $yourplanes[] = $this->planes[$p]; } shuffle($this->winner); foreach($candidates as $c) { // We now have a list of winning strategy vectors for our second point // Pick one that will force you into defensive mode. // Pick a point close to you so we don't risk giving you two // in a row when you block us. That would force *us* into // defensive mode. // We want: or: not: // X|O| X| | X| | // |0| O|O| |O| // | | | | |O| if(count($this->you) == 1) { foreach($this->winner as $w) { if(in_array($this->me[0], $w) && in_array($c[1],$w) && $this->uncontested_winner($w) && $this->is_neighbor($this->you[0],$c[1])) { return($c[1]); } } } } // You're somewhere else entirely or have made more than one move // - any strategy vector which puts you on the defense will have to do foreach($candidates as $c) { foreach($this->winner as $w) { if(in_array($this->me[0], $w) && in_array($c[1],$w) && $this->uncontested_winner($w)) { return($c[1]); } } } } // worst case scenario, no strategy we can play, // just find an empty space and take it for($x = 0; $x < $this->dimen; $x ++) for($y = 0; $y < $this->dimen; $y ++) for($z = 0; $z < $this->dimen; $z ++) if((! $this->marked_yours($x,$y,$z)) && (! $this->marked_mine($x,$y,$z))) { if($this->handicap && $x == 1 && $y == 1 && $z == 1) continue; return(sprintf("%d%d%d",$x,$y,$z)); } return ''; } function marked_yours($x,$y,$z) { $str = sprintf("%d%d%d",$x,$y,$z); if(in_array($str,$this->you)) return true; return false; } function marked_mine($x,$y,$z) { $str = sprintf("%d%d%d",$x,$y,$z); if(in_array($str,$this->me)) return true; return false; } function is_yours($str) { if(in_array($str,$this->you)) return true; return false; } function is_mine($str) { if(in_array($str,$this->me)) return true; return false; } function get_corners($a) { $total = array(); if(count($a)) foreach($a as $b) if(in_array($b,$this->corners)) $total[] = $b; return $total; } function uncontested_winner($w) { if($this->handicap && in_array('111',$w)) return false; $contested = false; if(count($this->you)) { foreach($this->you as $you) if(in_array($you,$w)) $contested = true; } return (($contested) ? false : true); } function is_neighbor($p1,$p2) { list($x1,$y1,$z1) = sscanf($p1, "%1d%1d%1d"); list($x2,$y2,$z2) = sscanf($p2, "%1d%1d%1d"); if((($x1 == $x2) || ($x1 == $x2+1) || ($x1 == $x2-1)) && (($y1 == $y2) || ($y1 == $y2+1) || ($y1 == $y2-1)) && (($z1 == $z2) || ($z1 == $z2+1) || ($z1 == $z2-1))) return true; return false; } function my_best_plane() { $second_choice = array(); shuffle($this->planes); for($p = 0; $p < count($this->planes); $p ++ ) { $contested = 0; if($this->handicap && in_array('111',$this->planes[$p])) continue; if(! in_array($this->me[0],$this->planes[$p])) continue; foreach($this->you as $m) { if(in_array($m,$this->planes[$p])) $contested ++; } if(! $contested) return($this->planes[$p]); if($contested == 1) $second_choice = $this->planes[$p]; } return $second_choice; } function uncontested_plane() { $freeplane = true; shuffle($this->planes); $pl = $this->planes; for($p = 0; $p < count($pl); $p ++ ) { if($this->handicap && in_array('111',$pl[$p])) continue; foreach($this->you as $m) { if(in_array($m,$pl[$p])) $freeplane = false; } if(! $freeplane) { $freeplane = true; continue; } if($freeplane) return($pl[$p]); } return array(); } function fullboard() { return false; } function draw_board() { if(! strlen($this->yours)) $this->yours = 'XXX'; $o .= "
handicap}/{$this->mefirst}/{$this->dimen}/{$this->yours}/{$this->mine}\" method=\"post\" />"; for($x = 0; $x < $this->dimen; $x ++) { $o .= ''; for($y = 0; $y < $this->dimen; $y ++) { $o .= ''; for($z = 0; $z < $this->dimen; $z ++) { $s = sprintf("%d%d%d",$x,$y,$z); $winner = ((is_array($this->winning_play) && in_array($s,$this->winning_play)) ? " color: #FF0000; " : ""); $bordertop = (($y != 0) ? " border-top: 2px solid #000;" : ""); $borderleft = (($z != 0) ? " border-left: 2px solid #000;" : ""); if($this->handicap && $x == 1 && $y == 1 && $z == 1) $o .= ""; elseif($this->marked_yours($x,$y,$z)) $o .= ""; elseif($this->marked_mine($x,$y,$z)) $o .= ""; else { $val = sprintf("%d%d%d",$x,$y,$z); $o .= ""; } } $o .= ''; } $o .= '
 XO

'; } $o .= '
'; return $o; } }