4 * Description: The TicTacToe game application
6 * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
10 function tictac_install() {
11 register_hook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
14 function tictac_uninstall() {
15 unregister_hook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
19 function tictac_app_menu($a,&$b) {
20 $b['app_menu'][] = '<div class="app-title"><a href="tictac">' . t('Three Dimensional Tic-Tac-Toe') . '</a></div>';
24 function tictac_module() {
32 function tictac_content(&$a) {
37 $handicap = $a->argv[1];
38 $mefirst = $a->argv[2];
43 $yours .= $_POST['move'];
45 elseif($a->argc > 1) {
46 $handicap = $a->argv[1];
53 $o .= '<h3>' . t('3D Tic-Tac-Toe') . '</h3><br />';
55 $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine);
58 $o .= '<a href="tictac">' . t('New game') . '</a><br />';
59 $o .= '<a href="tictac/1">' . t('New game with handicap') . '</a><br />';
60 $o .= '<p>' . t('Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. ');
61 $o .= t('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.');
63 $o .= t('The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage.');
72 private $first_move = true;
73 private $handicap = 0;
76 private $winning_play;
80 private $crosses = array('011','101','110','112','121','211');
83 '001','010','011','012','021',
84 '101','110','111','112','121',
85 '201','210','211','212','221');
88 private $corners = array(
89 '000','002','020','022',
90 '200','202','220','222');
92 private $planes = array(
93 array('000','001','002','010','011','012','020','021','022'), // horiz 1
94 array('100','101','102','110','111','112','120','121','122'), // 2
95 array('200','201','202','210','211','212','220','221','222'), // 3
96 array('000','010','020','100','110','120','200','210','220'), // vert left
97 array('000','001','002','100','101','102','200','201','202'), // vert top
98 array('002','012','022','102','112','122','202','212','222'), // vert right
99 array('020','021','022','120','121','122','220','221','222'), // vert bot
100 array('010','011','012','110','111','112','210','211','212'), // left vertx
101 array('001','011','021','101','111','221','201','211','221'), // top vertx
102 array('000','001','002','110','111','112','220','221','222'), // diag top
103 array('020','021','022','110','111','112','200','201','202'), // diag bot
104 array('000','010','020','101','111','121','202','212','222'), // diag left
105 array('002','012','022','101','111','121','200','210','220'), // diag right
106 array('002','011','020','102','111','120','202','211','220'), // diag x
107 array('000','011','022','100','111','122','200','211','222') // diag x
112 private $winner = array(
113 array('000','001','002'), // board 0 winners - left corner across
114 array('000','010','020'), // down
115 array('000','011','022'), // diag
116 array('001','011','021'), // middle-top down
117 array('010','011','012'), // middle-left across
118 array('002','011','020'), // right-top diag
119 array('002','012','022'), // right-top down
120 array('020','021','022'), // bottom-left across
121 array('100','101','102'), // board 1 winners
122 array('100','110','120'),
123 array('100','111','122'),
124 array('101','111','121'),
125 array('110','111','112'),
126 array('102','111','120'),
127 array('102','112','122'),
128 array('120','121','122'),
129 array('200','201','202'), // board 2 winners
130 array('200','210','220'),
131 array('200','211','222'),
132 array('201','211','221'),
133 array('210','211','212'),
134 array('202','211','220'),
135 array('202','212','222'),
136 array('220','221','222'),
137 array('000','100','200'), // top-left corner 3d
138 array('000','101','202'),
139 array('000','110','220'),
140 array('000','111','222'),
141 array('001','101','201'), // top-middle 3d
142 array('001','111','221'),
143 array('002','102','202'), // top-right corner 3d
144 array('002','101','200'),
145 array('002','112','222'),
146 array('002','111','220'),
147 array('010','110','210'), // left-middle 3d
148 array('010','111','212'),
149 array('011','111','211'), // middle-middle 3d
150 array('012','112','212'), // right-middle 3d
151 array('012','111','210'),
152 array('020','120','220'), // bottom-left corner 3d
153 array('020','110','200'),
154 array('020','121','222'),
155 array('020','111','202'),
156 array('021','121','221'), // bottom-middle 3d
157 array('021','111','201'),
158 array('022','122','222'), // bottom-right corner 3d
159 array('022','121','220'),
160 array('022','112','202'),
161 array('022','111','200')
165 function __construct($dimen,$handicap,$mefirst,$yours,$mine) {
167 $this->handicap = (($handicap) ? 1 : 0);
168 $this->mefirst = (($mefirst) ? 1 : 0);
169 $this->yours = str_replace('XXX','',$yours);
171 $this->you = $this->parse_moves('you');
172 $this->me = $this->parse_moves('me');
175 $this->first_move = false;
180 if($this->first_move) {
182 $o .= '<div class="error-message">' . t('You go first...') . '</div><br />';
184 $o .= $this->draw_board();
187 $o .= '<div class="error-message">' . t('I\'m going first this time...') . ' </div><br />';
192 if($this->check_youwin()) {
193 $o .= '<div class="error-message">' . t('You won!') . '</div><br />';
194 $o .= $this->draw_board();
198 if($this->fullboard())
199 $o .= '<div class="error-message">' . t('"Cat" game!') . '</div><br />';
201 $move = $this->winning_move();
203 $this->mine .= $move;
204 $this->me = $this->parse_moves('me');
207 $move = $this->defensive_move();
209 $this->mine .= $move;
210 $this->me = $this->parse_moves('me');
213 $move = $this->offensive_move();
215 $this->mine .= $move;
216 $this->me = $this->parse_moves('me');
221 if($this->check_iwon())
222 $o .= '<div class="error-message">' . t('I won!') . '</div><br />';
223 if($this->fullboard())
224 $o .= '<div class="error-message">' . t('"Cat" game!') . '</div><br />';
225 $o .= $this->draw_board();
229 function parse_moves($player) {
235 while(strlen($str)) {
236 $ret[] = substr($str,0,3);
237 $str = substr($str,3);
243 function check_youwin() {
244 for($x = 0; $x < count($this->winner); $x ++) {
245 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)) {
246 $this->winning_play = $this->winner[$x];
252 function check_iwon() {
253 for($x = 0; $x < count($this->winner); $x ++) {
254 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)) {
255 $this->winning_play = $this->winner[$x];
261 function defensive_move() {
263 for($x = 0; $x < count($this->winner); $x ++) {
264 if(($this->handicap) && in_array('111',$this->winner[$x]))
266 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)))
267 return($this->winner[$x][2]);
268 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)))
269 return($this->winner[$x][1]);
270 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)))
271 return($this->winner[$x][0]);
276 function winning_move() {
278 for($x = 0; $x < count($this->winner); $x ++) {
279 if(($this->handicap) && in_array('111',$this->winner[$x]))
281 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)))
282 return($this->winner[$x][2]);
283 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)))
284 return($this->winner[$x][1]);
285 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)))
286 return($this->winner[$x][0]);
291 function offensive_move() {
293 shuffle($this->planes);
294 shuffle($this->winner);
295 shuffle($this->corners);
296 shuffle($this->crosses);
298 if(! count($this->me)) {
299 if($this->handicap) {
300 $p = $this->uncontested_plane();
301 foreach($this->corners as $c)
303 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
307 if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1)))
309 $p = $this->uncontested_plane();
310 foreach($this->crosses as $c)
312 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
317 if($this->handicap) {
318 if(count($this->me) >= 1) {
319 if(count($this->get_corners($this->me)) == 1) {
320 if(in_array($this->me[0],$this->corners)) {
321 $p = $this->my_best_plane();
322 foreach($this->winner as $w) {
323 if((in_array($w[0],$this->you))
324 || (in_array($w[1],$this->you))
325 || (in_array($w[2],$this->you)))
327 if(in_array($w[0],$this->corners)
328 && in_array($w[2],$this->corners)
329 && in_array($w[0],$p) && in_array($w[2],$p)) {
330 if($this->me[0] == $w[0])
332 elseif($this->me[0] == $w[2])
339 $r = $this->get_corners($this->me);
341 $w1 = array(); $w2 = array();
342 foreach($this->winner as $w) {
343 if(in_array('111',$w))
345 if(($r[0] == $w[0]) || ($r[0] == $w[2]))
347 if(($r[1] == $w[0]) || ($r[1] == $w[2]))
350 if(count($w1) && count($w2)) {
353 if((in_array($a[0],$this->you))
354 || (in_array($a[1],$this->you))
355 || (in_array($a[2],$this->you))
356 || (in_array($b[0],$this->you))
357 || (in_array($b[1],$this->you))
358 || (in_array($b[2],$this->you)))
360 if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) {
363 elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) {
374 //&& (count($this->me) == 1) && (count($this->you) == 1)
375 // && in_array($this->you[0],$this->corners)
376 // && $this->is_neighbor($this->me[0],$this->you[0])) {
378 // Yuck. You foiled my plan. Since you obviously aren't playing to win,
379 // I'll try again. You may keep me busy for a few rounds, but I'm
380 // gonna' get you eventually.
382 // $p = $this->uncontested_plane();
383 // foreach($this->crosses as $c)
384 // if(in_array($c,$p))
390 // find all the winners containing my points.
391 $mywinners = array();
392 foreach($this->winner as $w)
393 foreach($this->me as $m)
394 if((in_array($m,$w)) && (! in_array($w,$mywinners)))
397 // find all the rules where my points are in the center.
399 if(count($mywinners)) {
400 foreach($mywinners as $w) {
401 foreach($this->me as $m) {
402 if(($m == $w[1]) && ($this->uncontested_winner($w))
403 && (! in_array($w,$trythese)))
410 for($p = 0; $p < count($this->planes); $p ++) {
411 if($this->handicap && in_array('111',$this->planes[$p]))
413 foreach($this->me as $m)
414 if((in_array($m,$this->planes[$p]))
415 && (! in_array($this->planes[$p],$myplanes)))
416 $myplanes[] = $this->planes[$p];
420 // find all winners which share an endpoint, and which are uncontested
421 $candidates = array();
422 if(count($trythese) && count($myplanes)) {
423 foreach($trythese as $t) {
424 foreach($this->winner as $w) {
425 if(! $this->uncontested_winner($w))
427 if((in_array($t[0],$w)) || (in_array($t[2],$w))) {
428 foreach($myplanes as $p)
429 if(in_array($w[0],$p) && in_array($w[1],$p) && in_array($w[2],$p) && ($w[1] != $this->me[0]))
430 if(! in_array($w,$candidates))
437 // Find out if we are about to force a win.
438 // Looking for two winning vectors with a common endpoint
439 // and where we own the middle of both - we are now going to
440 // grab the endpoint. The game isn't yet over but we've already won.
442 if(count($candidates)) {
443 foreach($candidates as $c) {
444 if(in_array($c[1],$this->me)) {
446 foreach($trythese as $t)
449 elseif($t[2] == $c[2])
454 // find opponents planes
455 $yourplanes = array();
456 for($p = 0; $p < count($this->planes); $p ++) {
457 if($this->handicap && in_array('111',$this->planes[$p]))
459 if(in_array($this->you[0],$this->planes[$p]))
460 $yourplanes[] = $this->planes[$p];
463 shuffle($this->winner);
464 foreach($candidates as $c) {
466 // We now have a list of winning strategy vectors for our second point
467 // Pick one that will force you into defensive mode.
468 // Pick a point close to you so we don't risk giving you two
469 // in a row when you block us. That would force *us* into
476 if(count($this->you) == 1) {
477 foreach($this->winner as $w) {
478 if(in_array($this->me[0], $w) && in_array($c[1],$w)
479 && $this->uncontested_winner($w)
480 && $this->is_neighbor($this->you[0],$c[1])) {
487 // You're somewhere else entirely or have made more than one move
488 // - any strategy vector which puts you on the defense will have to do
490 foreach($candidates as $c) {
491 foreach($this->winner as $w) {
492 if(in_array($this->me[0], $w) && in_array($c[1],$w)
493 && $this->uncontested_winner($w)) {
500 // worst case scenario, no strategy we can play,
501 // just find an empty space and take it
503 for($x = 0; $x < $this->dimen; $x ++)
504 for($y = 0; $y < $this->dimen; $y ++)
505 for($z = 0; $z < $this->dimen; $z ++)
506 if((! $this->marked_yours($x,$y,$z))
507 && (! $this->marked_mine($x,$y,$z))) {
508 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
510 return(sprintf("%d%d%d",$x,$y,$z));
516 function marked_yours($x,$y,$z) {
517 $str = sprintf("%d%d%d",$x,$y,$z);
518 if(in_array($str,$this->you))
523 function marked_mine($x,$y,$z) {
524 $str = sprintf("%d%d%d",$x,$y,$z);
525 if(in_array($str,$this->me))
530 function is_yours($str) {
531 if(in_array($str,$this->you))
536 function is_mine($str) {
537 if(in_array($str,$this->me))
542 function get_corners($a) {
546 if(in_array($b,$this->corners))
551 function uncontested_winner($w) {
552 if($this->handicap && in_array('111',$w))
555 if(count($this->you)) {
556 foreach($this->you as $you)
557 if(in_array($you,$w))
560 return (($contested) ? false : true);
564 function is_neighbor($p1,$p2) {
565 list($x1,$y1,$z1) = sscanf($p1, "%1d%1d%1d");
566 list($x2,$y2,$z2) = sscanf($p2, "%1d%1d%1d");
568 if((($x1 == $x2) || ($x1 == $x2+1) || ($x1 == $x2-1)) &&
569 (($y1 == $y2) || ($y1 == $y2+1) || ($y1 == $y2-1)) &&
570 (($z1 == $z2) || ($z1 == $z2+1) || ($z1 == $z2-1)))
576 function my_best_plane() {
578 $second_choice = array();
579 shuffle($this->planes);
580 for($p = 0; $p < count($this->planes); $p ++ ) {
582 if($this->handicap && in_array('111',$this->planes[$p]))
584 if(! in_array($this->me[0],$this->planes[$p]))
586 foreach($this->you as $m) {
587 if(in_array($m,$this->planes[$p]))
591 return($this->planes[$p]);
593 $second_choice = $this->planes[$p];
595 return $second_choice;
604 function uncontested_plane() {
606 shuffle($this->planes);
609 for($p = 0; $p < count($pl); $p ++ ) {
610 if($this->handicap && in_array('111',$pl[$p]))
612 foreach($this->you as $m) {
613 if(in_array($m,$pl[$p]))
626 function fullboard() {
630 function draw_board() {
631 if(! strlen($this->yours))
632 $this->yours = 'XXX';
633 $o .= "<form action=\"tictac/{$this->handicap}/{$this->mefirst}/{$this->dimen}/{$this->yours}/{$this->mine}\" method=\"post\" />";
634 for($x = 0; $x < $this->dimen; $x ++) {
636 for($y = 0; $y < $this->dimen; $y ++) {
638 for($z = 0; $z < $this->dimen; $z ++) {
639 $s = sprintf("%d%d%d",$x,$y,$z);
640 $winner = ((is_array($this->winning_play) && in_array($s,$this->winning_play)) ? " color: #FF0000; " : "");
641 $bordertop = (($y != 0) ? " border-top: 2px solid #000;" : "");
642 $borderleft = (($z != 0) ? " border-left: 2px solid #000;" : "");
643 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
644 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"> </td>";
645 elseif($this->marked_yours($x,$y,$z))
646 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">X</td>";
647 elseif($this->marked_mine($x,$y,$z))
648 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">O</td>";
650 $val = sprintf("%d%d%d",$x,$y,$z);
651 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"><input type=\"checkbox\" name=\"move\" value=\"$val\" onclick=\"this.form.submit();\" /></td>";
656 $o .= '</table><br />';