4 function tictac_install() {
5 register_hook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
8 function tictac_uninstall() {
9 unregister_hook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
13 function tictac_app_menu($a,&$b) {
14 $b['app_menu'] .= '<a href="tictac">Three Dimensional Tic-Tac-Toe</a><br />';
18 function tictac_module() {
26 function tictac_content(&$a) {
31 $handicap = $a->argv[1];
32 $mefirst = $a->argv[2];
37 $yours .= $_POST['move'];
39 elseif($a->argc > 1) {
40 $handicap = $a->argv[1];
47 $o .= '<h3>3D Tic-Tac-Toe</h3><br />';
49 $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine);
52 $o .= '<a href="tictac">New game</a><br />';
53 $o .= '<a href="tictac/1">New game with handicap</a><br />';
56 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.
59 The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage.
68 private $first_move = true;
69 private $handicap = 0;
72 private $winning_play;
76 private $crosses = array('011','101','110','112','121','211');
79 '001','010','011','012','021',
80 '101','110','111','112','121',
81 '201','210','211','212','221');
84 private $corners = array(
85 '000','002','020','022',
86 '200','202','220','222');
88 private $planes = array(
89 array('000','001','002','010','011','012','020','021','022'), // horiz 1
90 array('100','101','102','110','111','112','120','121','122'), // 2
91 array('200','201','202','210','211','212','220','221','222'), // 3
92 array('000','010','020','100','110','120','200','210','220'), // vert left
93 array('000','001','002','100','101','102','200','201','202'), // vert top
94 array('002','012','022','102','112','122','202','212','222'), // vert right
95 array('020','021','022','120','121','122','220','221','222'), // vert bot
96 array('010','011','012','110','111','112','210','211','212'), // left vertx
97 array('001','011','021','101','111','221','201','211','221'), // top vertx
98 array('000','001','002','110','111','112','220','221','222'), // diag top
99 array('020','021','022','110','111','112','200','201','202'), // diag bot
100 array('000','010','020','101','111','121','202','212','222'), // diag left
101 array('002','012','022','101','111','121','200','210','220'), // diag right
102 array('002','011','020','102','111','120','202','211','220'), // diag x
103 array('000','011','022','100','111','122','200','211','222') // diag x
108 private $winner = array(
109 array('000','001','002'), // board 0 winners - left corner across
110 array('000','010','020'), // down
111 array('000','011','022'), // diag
112 array('001','011','021'), // middle-top down
113 array('010','011','012'), // middle-left across
114 array('002','011','020'), // right-top diag
115 array('002','012','022'), // right-top down
116 array('020','021','022'), // bottom-left across
117 array('100','101','102'), // board 1 winners
118 array('100','110','120'),
119 array('100','111','122'),
120 array('101','111','121'),
121 array('110','111','112'),
122 array('102','111','120'),
123 array('102','112','122'),
124 array('120','121','122'),
125 array('200','201','202'), // board 2 winners
126 array('200','210','220'),
127 array('200','211','222'),
128 array('201','211','221'),
129 array('210','211','212'),
130 array('202','211','220'),
131 array('202','212','222'),
132 array('220','221','222'),
133 array('000','100','200'), // top-left corner 3d
134 array('000','101','202'),
135 array('000','110','220'),
136 array('000','111','222'),
137 array('001','101','201'), // top-middle 3d
138 array('001','111','221'),
139 array('002','102','202'), // top-right corner 3d
140 array('002','101','200'),
141 array('002','112','222'),
142 array('002','111','220'),
143 array('010','110','210'), // left-middle 3d
144 array('010','111','212'),
145 array('011','111','211'), // middle-middle 3d
146 array('012','112','212'), // right-middle 3d
147 array('012','111','210'),
148 array('020','120','220'), // bottom-left corner 3d
149 array('020','110','200'),
150 array('020','121','222'),
151 array('020','111','202'),
152 array('021','121','221'), // bottom-middle 3d
153 array('021','111','201'),
154 array('022','122','222'), // bottom-right corner 3d
155 array('022','121','220'),
156 array('022','112','202'),
157 array('022','111','200')
161 function __construct($dimen,$handicap,$mefirst,$yours,$mine) {
163 $this->handicap = (($handicap) ? 1 : 0);
164 $this->mefirst = (($mefirst) ? 1 : 0);
165 $this->yours = str_replace('XXX','',$yours);
167 $this->you = $this->parse_moves('you');
168 $this->me = $this->parse_moves('me');
171 $this->first_move = false;
176 if($this->first_move) {
178 $o .= '<div class="errmsg">You go first...</div><br />';
180 $o .= $this->draw_board();
183 $o .= '<div class="errmsg">I\'m going first this time...</div><br />';
188 if($this->check_youwin()) {
189 $o .= '<div class="errmsg">You won!</div><br />';
190 $o .= $this->draw_board();
194 if($this->fullboard())
197 $move = $this->winning_move();
199 $this->mine .= $move;
200 $this->me = $this->parse_moves('me');
203 $move = $this->defensive_move();
205 $this->mine .= $move;
206 $this->me = $this->parse_moves('me');
209 $move = $this->offensive_move();
211 $this->mine .= $move;
212 $this->me = $this->parse_moves('me');
217 if($this->check_iwon())
218 $o .= '<div class="errmsg">I won!</div><br />';
219 if($this->fullboard())
221 $o .= $this->draw_board();
225 function parse_moves($player) {
231 while(strlen($str)) {
232 $ret[] = substr($str,0,3);
233 $str = substr($str,3);
239 function check_youwin() {
240 for($x = 0; $x < count($this->winner); $x ++) {
241 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)) {
242 $this->winning_play = $this->winner[$x];
248 function check_iwon() {
249 for($x = 0; $x < count($this->winner); $x ++) {
250 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)) {
251 $this->winning_play = $this->winner[$x];
257 function defensive_move() {
259 for($x = 0; $x < count($this->winner); $x ++) {
260 if(($this->handicap) && in_array('111',$this->winner[$x]))
262 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)))
263 return($this->winner[$x][2]);
264 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)))
265 return($this->winner[$x][1]);
266 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)))
267 return($this->winner[$x][0]);
272 function winning_move() {
274 for($x = 0; $x < count($this->winner); $x ++) {
275 if(($this->handicap) && in_array('111',$this->winner[$x]))
277 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)))
278 return($this->winner[$x][2]);
279 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)))
280 return($this->winner[$x][1]);
281 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)))
282 return($this->winner[$x][0]);
287 function offensive_move() {
289 shuffle($this->planes);
290 shuffle($this->winner);
291 shuffle($this->corners);
292 shuffle($this->crosses);
294 if(! count($this->me)) {
295 if($this->handicap) {
296 $p = $this->uncontested_plane();
297 foreach($this->corners as $c)
299 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
303 if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1)))
305 $p = $this->uncontested_plane();
306 foreach($this->crosses as $c)
308 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
313 if($this->handicap) {
314 if(count($this->me) >= 1) {
315 if(count($this->get_corners($this->me)) == 1) {
316 if(in_array($this->me[0],$this->corners)) {
317 $p = $this->my_best_plane();
318 foreach($this->winner as $w) {
319 if((in_array($w[0],$this->you))
320 || (in_array($w[1],$this->you))
321 || (in_array($w[2],$this->you)))
323 if(in_array($w[0],$this->corners)
324 && in_array($w[2],$this->corners)
325 && in_array($w[0],$p) && in_array($w[2],$p)) {
326 if($this->me[0] == $w[0])
328 elseif($this->me[0] == $w[2])
335 $r = $this->get_corners($this->me);
337 $w1 = array(); $w2 = array();
338 foreach($this->winner as $w) {
339 if(in_array('111',$w))
341 if(($r[0] == $w[0]) || ($r[0] == $w[2]))
343 if(($r[1] == $w[0]) || ($r[1] == $w[2]))
346 if(count($w1) && count($w2)) {
349 if((in_array($a[0],$this->you))
350 || (in_array($a[1],$this->you))
351 || (in_array($a[2],$this->you))
352 || (in_array($b[0],$this->you))
353 || (in_array($b[1],$this->you))
354 || (in_array($b[2],$this->you)))
356 if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) {
359 elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) {
370 //&& (count($this->me) == 1) && (count($this->you) == 1)
371 // && in_array($this->you[0],$this->corners)
372 // && $this->is_neighbor($this->me[0],$this->you[0])) {
374 // Yuck. You foiled my plan. Since you obviously aren't playing to win,
375 // I'll try again. You may keep me busy for a few rounds, but I'm
376 // gonna' get you eventually.
378 // $p = $this->uncontested_plane();
379 // foreach($this->crosses as $c)
380 // if(in_array($c,$p))
386 // find all the winners containing my points.
387 $mywinners = array();
388 foreach($this->winner as $w)
389 foreach($this->me as $m)
390 if((in_array($m,$w)) && (! in_array($w,$mywinners)))
393 // find all the rules where my points are in the center.
395 if(count($mywinners)) {
396 foreach($mywinners as $w) {
397 foreach($this->me as $m) {
398 if(($m == $w[1]) && ($this->uncontested_winner($w))
399 && (! in_array($w,$trythese)))
406 for($p = 0; $p < count($this->planes); $p ++) {
407 if($this->handicap && in_array('111',$this->planes[$p]))
409 foreach($this->me as $m)
410 if((in_array($m,$this->planes[$p]))
411 && (! in_array($this->planes[$p],$myplanes)))
412 $myplanes[] = $this->planes[$p];
416 // find all winners which share an endpoint, and which are uncontested
417 $candidates = array();
418 if(count($trythese) && count($myplanes)) {
419 foreach($trythese as $t) {
420 foreach($this->winner as $w) {
421 if(! $this->uncontested_winner($w))
423 if((in_array($t[0],$w)) || (in_array($t[2],$w))) {
424 foreach($myplanes as $p)
425 if(in_array($w[0],$p) && in_array($w[1],$p) && in_array($w[2],$p) && ($w[1] != $this->me[0]))
426 if(! in_array($w,$candidates))
433 // Find out if we are about to force a win.
434 // Looking for two winning vectors with a common endpoint
435 // and where we own the middle of both - we are now going to
436 // grab the endpoint. The game isn't yet over but we've already won.
438 if(count($candidates)) {
439 foreach($candidates as $c) {
440 if(in_array($c[1],$this->me)) {
442 foreach($trythese as $t)
445 elseif($t[2] == $c[2])
450 // find opponents planes
451 $yourplanes = array();
452 for($p = 0; $p < count($this->planes); $p ++) {
453 if($this->handicap && in_array('111',$this->planes[$p]))
455 if(in_array($this->you[0],$this->planes[$p]))
456 $yourplanes[] = $this->planes[$p];
459 shuffle($this->winner);
460 foreach($candidates as $c) {
462 // We now have a list of winning strategy vectors for our second point
463 // Pick one that will force you into defensive mode.
464 // Pick a point close to you so we don't risk giving you two
465 // in a row when you block us. That would force *us* into
472 if(count($this->you) == 1) {
473 foreach($this->winner as $w) {
474 if(in_array($this->me[0], $w) && in_array($c[1],$w)
475 && $this->uncontested_winner($w)
476 && $this->is_neighbor($this->you[0],$c[1])) {
483 // You're somewhere else entirely or have made more than one move
484 // - any strategy vector which puts you on the defense will have to do
486 foreach($candidates as $c) {
487 foreach($this->winner as $w) {
488 if(in_array($this->me[0], $w) && in_array($c[1],$w)
489 && $this->uncontested_winner($w)) {
496 // worst case scenario, no strategy we can play,
497 // just find an empty space and take it
499 for($x = 0; $x < $this->dimen; $x ++)
500 for($y = 0; $y < $this->dimen; $y ++)
501 for($z = 0; $z < $this->dimen; $z ++)
502 if((! $this->marked_yours($x,$y,$z))
503 && (! $this->marked_mine($x,$y,$z))) {
504 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
506 return(sprintf("%d%d%d",$x,$y,$z));
512 function marked_yours($x,$y,$z) {
513 $str = sprintf("%d%d%d",$x,$y,$z);
514 if(in_array($str,$this->you))
519 function marked_mine($x,$y,$z) {
520 $str = sprintf("%d%d%d",$x,$y,$z);
521 if(in_array($str,$this->me))
526 function is_yours($str) {
527 if(in_array($str,$this->you))
532 function is_mine($str) {
533 if(in_array($str,$this->me))
538 function get_corners($a) {
542 if(in_array($b,$this->corners))
547 function uncontested_winner($w) {
548 if($this->handicap && in_array('111',$w))
551 if(count($this->you)) {
552 foreach($this->you as $you)
553 if(in_array($you,$w))
556 return (($contested) ? false : true);
560 function is_neighbor($p1,$p2) {
561 list($x1,$y1,$z1) = sscanf($p1, "%1d%1d%1d");
562 list($x2,$y2,$z2) = sscanf($p2, "%1d%1d%1d");
564 if((($x1 == $x2) || ($x1 == $x2+1) || ($x1 == $x2-1)) &&
565 (($y1 == $y2) || ($y1 == $y2+1) || ($y1 == $y2-1)) &&
566 (($z1 == $z2) || ($z1 == $z2+1) || ($z1 == $z2-1)))
572 function my_best_plane() {
574 $second_choice = array();
575 shuffle($this->planes);
576 for($p = 0; $p < count($this->planes); $p ++ ) {
578 if($this->handicap && in_array('111',$this->planes[$p]))
580 if(! in_array($this->me[0],$this->planes[$p]))
582 foreach($this->you as $m) {
583 if(in_array($m,$this->planes[$p]))
587 return($this->planes[$p]);
589 $second_choice = $this->planes[$p];
591 return $second_choice;
600 function uncontested_plane() {
602 shuffle($this->planes);
605 for($p = 0; $p < count($pl); $p ++ ) {
606 if($this->handicap && in_array('111',$pl[$p]))
608 foreach($this->you as $m) {
609 if(in_array($m,$pl[$p]))
622 function fullboard() {
626 function draw_board() {
627 if(! strlen($this->yours))
628 $this->yours = 'XXX';
629 $o .= "<form action=\"tictac/{$this->handicap}/{$this->mefirst}/{$this->dimen}/{$this->yours}/{$this->mine}\" method=\"post\" />";
630 for($x = 0; $x < $this->dimen; $x ++) {
632 for($y = 0; $y < $this->dimen; $y ++) {
634 for($z = 0; $z < $this->dimen; $z ++) {
635 $s = sprintf("%d%d%d",$x,$y,$z);
636 $winner = ((is_array($this->winning_play) && in_array($s,$this->winning_play)) ? " color: #FF0000; " : "");
637 $bordertop = (($y != 0) ? " border-top: 2px solid #000;" : "");
638 $borderleft = (($z != 0) ? " border-left: 2px solid #000;" : "");
639 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
640 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"> </td>";
641 elseif($this->marked_yours($x,$y,$z))
642 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">X</td>";
643 elseif($this->marked_mine($x,$y,$z))
644 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">O</td>";
646 $val = sprintf("%d%d%d",$x,$y,$z);
647 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"><input type=\"checkbox\" name=\"move\" value=\"$val\" onclick=\"this.form.submit();\" /></td>";
652 $o .= '</table><br />';