4 * Description: The TicTacToe game application
6 * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
10 use Friendica\Core\Hook;
13 function tictac_install()
15 Hook::register('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
18 function tictac_app_menu(App $a, array &$b)
20 $b['app_menu'][] = '<div class="app-title"><a href="tictac">' . DI::l10n()->t('Three Dimensional Tic-Tac-Toe') . '</a></div>';
24 * This is a statement rather than an actual function definition. The simple
25 * existence of this method is checked to figure out if the addon offers a
28 function tictac_module() {}
30 function tictac_content(App $a) {
35 $handicap = DI::args()->get(1);
36 $mefirst = DI::args()->get(2);
37 $dimen = DI::args()->get(3);
38 $yours = DI::args()->get(4);
39 $mine = DI::args()->get(5);
41 $yours .= $_POST['move'];
43 elseif(DI::args()->getArgc() > 1) {
44 $handicap = DI::args()->get(1);
51 $o .= '<h3>' . DI::l10n()->t('3D Tic-Tac-Toe') . '</h3><br />';
53 $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine);
56 $o .= '<a href="tictac">' . DI::l10n()->t('New game') . '</a><br />';
57 $o .= '<a href="tictac/1">' . DI::l10n()->t('New game with handicap') . '</a><br />';
58 $o .= '<p>' . DI::l10n()->t('Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. ');
59 $o .= DI::l10n()->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.');
61 $o .= DI::l10n()->t('The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage.');
70 private $first_move = true;
71 private $handicap = 0;
74 private $winning_play;
78 private $crosses = ['011','101','110','112','121','211'];
81 '001','010','011','012','021',
82 '101','110','111','112','121',
83 '201','210','211','212','221');
87 '000','002','020','022',
88 '200','202','220','222'];
91 ['000','001','002','010','011','012','020','021','022'], // horiz 1
92 ['100','101','102','110','111','112','120','121','122'], // 2
93 ['200','201','202','210','211','212','220','221','222'], // 3
94 ['000','010','020','100','110','120','200','210','220'], // vert left
95 ['000','001','002','100','101','102','200','201','202'], // vert top
96 ['002','012','022','102','112','122','202','212','222'], // vert right
97 ['020','021','022','120','121','122','220','221','222'], // vert bot
98 ['010','011','012','110','111','112','210','211','212'], // left vertx
99 ['001','011','021','101','111','221','201','211','221'], // top vertx
100 ['000','001','002','110','111','112','220','221','222'], // diag top
101 ['020','021','022','110','111','112','200','201','202'], // diag bot
102 ['000','010','020','101','111','121','202','212','222'], // diag left
103 ['002','012','022','101','111','121','200','210','220'], // diag right
104 ['002','011','020','102','111','120','202','211','220'], // diag x
105 ['000','011','022','100','111','122','200','211','222'] // diag x
111 ['000','001','002'], // board 0 winners - left corner across
112 ['000','010','020'], // down
113 ['000','011','022'], // diag
114 ['001','011','021'], // middle-top down
115 ['010','011','012'], // middle-left across
116 ['002','011','020'], // right-top diag
117 ['002','012','022'], // right-top down
118 ['020','021','022'], // bottom-left across
119 ['100','101','102'], // board 1 winners
127 ['200','201','202'], // board 2 winners
135 ['000','100','200'], // top-left corner 3d
139 ['001','101','201'], // top-middle 3d
141 ['002','102','202'], // top-right corner 3d
145 ['010','110','210'], // left-middle 3d
147 ['011','111','211'], // middle-middle 3d
148 ['012','112','212'], // right-middle 3d
150 ['020','120','220'], // bottom-left corner 3d
154 ['021','121','221'], // bottom-middle 3d
156 ['022','122','222'], // bottom-right corner 3d
163 function __construct($dimen,$handicap,$mefirst,$yours,$mine) {
165 $this->handicap = (($handicap) ? 1 : 0);
166 $this->mefirst = (($mefirst) ? 1 : 0);
167 $this->yours = str_replace('XXX','',$yours);
169 $this->you = $this->parse_moves('you');
170 $this->me = $this->parse_moves('me');
173 $this->first_move = false;
178 if($this->first_move) {
180 $o .= '<div class="error-message">' . DI::l10n()->t('You go first...') . '</div><br />';
182 $o .= $this->draw_board();
185 $o .= '<div class="error-message">' . DI::l10n()->t('I\'m going first this time...') . ' </div><br />';
190 if($this->check_youwin()) {
191 $o .= '<div class="error-message">' . DI::l10n()->t('You won!') . '</div><br />';
192 $o .= $this->draw_board();
196 if($this->fullboard())
197 $o .= '<div class="error-message">' . DI::l10n()->t('"Cat" game!') . '</div><br />';
199 $move = $this->winning_move();
201 $this->mine .= $move;
202 $this->me = $this->parse_moves('me');
205 $move = $this->defensive_move();
207 $this->mine .= $move;
208 $this->me = $this->parse_moves('me');
211 $move = $this->offensive_move();
213 $this->mine .= $move;
214 $this->me = $this->parse_moves('me');
219 if($this->check_iwon())
220 $o .= '<div class="error-message">' . DI::l10n()->t('I won!') . '</div><br />';
221 if($this->fullboard())
222 $o .= '<div class="error-message">' . DI::l10n()->t('"Cat" game!') . '</div><br />';
223 $o .= $this->draw_board();
227 function parse_moves($player) {
233 while(strlen($str)) {
234 $ret[] = substr($str,0,3);
235 $str = substr($str,3);
241 function check_youwin() {
242 for($x = 0; $x < count($this->winner); $x ++) {
243 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)) {
244 $this->winning_play = $this->winner[$x];
250 function check_iwon() {
251 for($x = 0; $x < count($this->winner); $x ++) {
252 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)) {
253 $this->winning_play = $this->winner[$x];
259 function defensive_move() {
261 for($x = 0; $x < count($this->winner); $x ++) {
262 if(($this->handicap) && in_array('111',$this->winner[$x]))
264 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)))
265 return($this->winner[$x][2]);
266 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)))
267 return($this->winner[$x][1]);
268 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)))
269 return($this->winner[$x][0]);
274 function winning_move() {
276 for($x = 0; $x < count($this->winner); $x ++) {
277 if(($this->handicap) && in_array('111',$this->winner[$x]))
279 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)))
280 return($this->winner[$x][2]);
281 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)))
282 return($this->winner[$x][1]);
283 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)))
284 return($this->winner[$x][0]);
289 function offensive_move() {
291 shuffle($this->planes);
292 shuffle($this->winner);
293 shuffle($this->corners);
294 shuffle($this->crosses);
296 if(! count($this->me)) {
297 if($this->handicap) {
298 $p = $this->uncontested_plane();
299 foreach($this->corners as $c)
301 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
305 if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1)))
307 $p = $this->uncontested_plane();
308 foreach($this->crosses as $c)
310 && (! $this->is_yours($c)) && (! $this->is_mine($c)))
315 if($this->handicap) {
316 if(count($this->me) >= 1) {
317 if(count($this->get_corners($this->me)) == 1) {
318 if(in_array($this->me[0],$this->corners)) {
319 $p = $this->my_best_plane();
320 foreach($this->winner as $w) {
321 if((in_array($w[0],$this->you))
322 || (in_array($w[1],$this->you))
323 || (in_array($w[2],$this->you)))
325 if(in_array($w[0],$this->corners)
326 && in_array($w[2],$this->corners)
327 && in_array($w[0],$p) && in_array($w[2],$p)) {
328 if($this->me[0] == $w[0])
330 elseif($this->me[0] == $w[2])
337 $r = $this->get_corners($this->me);
340 foreach($this->winner as $w) {
341 if(in_array('111',$w))
343 if(($r[0] == $w[0]) || ($r[0] == $w[2]))
345 if(($r[1] == $w[0]) || ($r[1] == $w[2]))
348 if(count($w1) && count($w2)) {
351 if((in_array($a[0],$this->you))
352 || (in_array($a[1],$this->you))
353 || (in_array($a[2],$this->you))
354 || (in_array($b[0],$this->you))
355 || (in_array($b[1],$this->you))
356 || (in_array($b[2],$this->you)))
358 if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) {
361 elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) {
372 //&& (count($this->me) == 1) && (count($this->you) == 1)
373 // && in_array($this->you[0],$this->corners)
374 // && $this->is_neighbor($this->me[0],$this->you[0])) {
376 // Yuck. You foiled my plan. Since you obviously aren't playing to win,
377 // I'll try again. You may keep me busy for a few rounds, but I'm
378 // gonna' get you eventually.
380 // $p = $this->uncontested_plane();
381 // foreach($this->crosses as $c)
382 // if(in_array($c,$p))
388 // find all the winners containing my points.
390 foreach($this->winner as $w)
391 foreach($this->me as $m)
392 if((in_array($m,$w)) && (! in_array($w,$mywinners)))
395 // find all the rules where my points are in the center.
397 if(count($mywinners)) {
398 foreach($mywinners as $w) {
399 foreach($this->me as $m) {
400 if(($m == $w[1]) && ($this->uncontested_winner($w))
401 && (! in_array($w,$trythese)))
408 for($p = 0; $p < count($this->planes); $p ++) {
409 if($this->handicap && in_array('111',$this->planes[$p]))
411 foreach($this->me as $m)
412 if((in_array($m,$this->planes[$p]))
413 && (! in_array($this->planes[$p],$myplanes)))
414 $myplanes[] = $this->planes[$p];
418 // find all winners which share an endpoint, and which are uncontested
420 if(count($trythese) && count($myplanes)) {
421 foreach($trythese as $t) {
422 foreach($this->winner as $w) {
423 if(! $this->uncontested_winner($w))
425 if((in_array($t[0],$w)) || (in_array($t[2],$w))) {
426 foreach($myplanes as $p)
427 if(in_array($w[0],$p) && in_array($w[1],$p) && in_array($w[2],$p) && ($w[1] != $this->me[0]))
428 if(! in_array($w,$candidates))
435 // Find out if we are about to force a win.
436 // Looking for two winning vectors with a common endpoint
437 // and where we own the middle of both - we are now going to
438 // grab the endpoint. The game isn't yet over but we've already won.
440 if(count($candidates)) {
441 foreach($candidates as $c) {
442 if(in_array($c[1],$this->me)) {
444 foreach($trythese as $t)
447 elseif($t[2] == $c[2])
452 // find opponents planes
454 for($p = 0; $p < count($this->planes); $p ++) {
455 if($this->handicap && in_array('111',$this->planes[$p]))
457 if(in_array($this->you[0],$this->planes[$p]))
458 $yourplanes[] = $this->planes[$p];
461 shuffle($this->winner);
462 foreach($candidates as $c) {
464 // We now have a list of winning strategy vectors for our second point
465 // Pick one that will force you into defensive mode.
466 // Pick a point close to you so we don't risk giving you two
467 // in a row when you block us. That would force *us* into
474 if(count($this->you) == 1) {
475 foreach($this->winner as $w) {
476 if(in_array($this->me[0], $w) && in_array($c[1],$w)
477 && $this->uncontested_winner($w)
478 && $this->is_neighbor($this->you[0],$c[1])) {
485 // You're somewhere else entirely or have made more than one move
486 // - any strategy vector which puts you on the defense will have to do
488 foreach($candidates as $c) {
489 foreach($this->winner as $w) {
490 if(in_array($this->me[0], $w) && in_array($c[1],$w)
491 && $this->uncontested_winner($w)) {
498 // worst case scenario, no strategy we can play,
499 // just find an empty space and take it
501 for($x = 0; $x < $this->dimen; $x ++)
502 for($y = 0; $y < $this->dimen; $y ++)
503 for($z = 0; $z < $this->dimen; $z ++)
504 if((! $this->marked_yours($x,$y,$z))
505 && (! $this->marked_mine($x,$y,$z))) {
506 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
508 return(sprintf("%d%d%d",$x,$y,$z));
514 function marked_yours($x,$y,$z) {
515 $str = sprintf("%d%d%d",$x,$y,$z);
516 if(in_array($str,$this->you))
521 function marked_mine($x,$y,$z) {
522 $str = sprintf("%d%d%d",$x,$y,$z);
523 if(in_array($str,$this->me))
528 function is_yours($str) {
529 if(in_array($str,$this->you))
534 function is_mine($str) {
535 if(in_array($str,$this->me))
540 function get_corners($a) {
544 if(in_array($b,$this->corners))
549 function uncontested_winner($w) {
550 if($this->handicap && in_array('111',$w))
553 if(count($this->you)) {
554 foreach($this->you as $you)
555 if(in_array($you,$w))
558 return (($contested) ? false : true);
562 function is_neighbor($p1,$p2) {
563 list($x1,$y1,$z1) = sscanf($p1, "%1d%1d%1d");
564 list($x2,$y2,$z2) = sscanf($p2, "%1d%1d%1d");
566 if((($x1 == $x2) || ($x1 == $x2+1) || ($x1 == $x2-1)) &&
567 (($y1 == $y2) || ($y1 == $y2+1) || ($y1 == $y2-1)) &&
568 (($z1 == $z2) || ($z1 == $z2+1) || ($z1 == $z2-1)))
574 function my_best_plane() {
577 shuffle($this->planes);
578 for($p = 0; $p < count($this->planes); $p ++ ) {
580 if($this->handicap && in_array('111',$this->planes[$p]))
582 if(! in_array($this->me[0],$this->planes[$p]))
584 foreach($this->you as $m) {
585 if(in_array($m,$this->planes[$p]))
589 return($this->planes[$p]);
591 $second_choice = $this->planes[$p];
593 return $second_choice;
602 function uncontested_plane() {
604 shuffle($this->planes);
607 for($p = 0; $p < count($pl); $p ++ ) {
608 if($this->handicap && in_array('111',$pl[$p]))
610 foreach($this->you as $m) {
611 if(in_array($m,$pl[$p]))
624 function fullboard() {
628 function draw_board() {
629 if(! strlen($this->yours))
630 $this->yours = 'XXX';
631 $o .= "<form action=\"tictac/{$this->handicap}/{$this->mefirst}/{$this->dimen}/{$this->yours}/{$this->mine}\" method=\"post\" />";
632 for($x = 0; $x < $this->dimen; $x ++) {
634 for($y = 0; $y < $this->dimen; $y ++) {
636 for($z = 0; $z < $this->dimen; $z ++) {
637 $s = sprintf("%d%d%d",$x,$y,$z);
638 $winner = ((is_array($this->winning_play) && in_array($s,$this->winning_play)) ? " color: #FF0000; " : "");
639 $bordertop = (($y != 0) ? " border-top: 2px solid #000;" : "");
640 $borderleft = (($z != 0) ? " border-left: 2px solid #000;" : "");
641 if($this->handicap && $x == 1 && $y == 1 && $z == 1)
642 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"> </td>";
643 elseif($this->marked_yours($x,$y,$z))
644 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">X</td>";
645 elseif($this->marked_mine($x,$y,$z))
646 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">O</td>";
648 $val = sprintf("%d%d%d",$x,$y,$z);
649 $o .= "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"><input type=\"checkbox\" name=\"move\" value=\"$val\" onclick=\"this.form.submit();\" /></td>";
654 $o .= '</table><br />';