]> git.mxchange.org Git - friendica-addons.git/blob - tictac/tictac.php
3105e2845cc8ad4949f1159cec01530e326e9a19
[friendica-addons.git] / tictac / tictac.php
1 <?php
2 /**
3  * Name: TicTac App
4  * Description: The TicTacToe game application
5  * Version: 1.0
6  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
7  */
8
9 use Friendica\App;
10 use Friendica\Core\Hook;
11 use Friendica\DI;
12
13 function tictac_install()
14 {
15         Hook::register('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
16 }
17
18 function tictac_app_menu(App $a, array &$b)
19 {
20         $b['app_menu'][] = '<div class="app-title"><a href="tictac">' . DI::l10n()->t('Three Dimensional Tic-Tac-Toe') . '</a></div>';
21 }
22
23 /**
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
26  * module.
27  */
28 function tictac_module() {}
29
30 function tictac_content(App $a) {
31
32         $o = '';
33
34   if($_POST['move']) {
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);
40
41     $yours .= $_POST['move'];
42   }
43   elseif(DI::args()->getArgc() > 1) {
44     $handicap = DI::args()->get(1);
45     $dimen = 3;
46   }
47   else {
48    $dimen = 3;
49   }
50
51   $o .=  '<h3>' . DI::l10n()->t('3D Tic-Tac-Toe') . '</h3><br />';
52
53   $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine);
54   $o .= $t->play();
55
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.');
60   $o .= '</p><p>';
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.');
62   $o .= '</p>';
63
64   return $o;
65
66 }
67
68 class tictac {
69   private $dimen;
70   private $first_move = true;
71   private $handicap = 0;
72   private $yours;
73   private $mine;
74   private $winning_play;
75   private $you;
76   private $me;
77   private $debug = 1;
78   private $crosses = ['011','101','110','112','121','211'];
79
80 /*
81     '001','010','011','012','021',
82     '101','110','111','112','121',
83     '201','210','211','212','221');
84 */
85
86   private $corners = [
87     '000','002','020','022',
88     '200','202','220','222'];
89
90   private $planes = [
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
106
107   ];
108
109
110   private $winner = [
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
120      ['100','110','120'],
121      ['100','111','122'],
122      ['101','111','121'],
123      ['110','111','112'],
124      ['102','111','120'],
125      ['102','112','122'],
126      ['120','121','122'],
127      ['200','201','202'],    // board 2 winners
128      ['200','210','220'],
129      ['200','211','222'],
130      ['201','211','221'],
131      ['210','211','212'],
132      ['202','211','220'],
133      ['202','212','222'],
134      ['220','221','222'],
135      ['000','100','200'],      // top-left corner 3d
136      ['000','101','202'],
137      ['000','110','220'],
138      ['000','111','222'],
139      ['001','101','201'],      // top-middle 3d
140      ['001','111','221'],
141      ['002','102','202'],      // top-right corner 3d
142      ['002','101','200'],
143      ['002','112','222'],
144      ['002','111','220'],
145      ['010','110','210'],      // left-middle 3d
146      ['010','111','212'],
147      ['011','111','211'],      // middle-middle 3d
148      ['012','112','212'],      // right-middle 3d
149      ['012','111','210'],
150      ['020','120','220'],      // bottom-left corner 3d
151      ['020','110','200'],
152      ['020','121','222'],
153      ['020','111','202'],
154      ['021','121','221'],      // bottom-middle 3d
155      ['021','111','201'],
156      ['022','122','222'],      // bottom-right corner 3d
157      ['022','121','220'],
158      ['022','112','202'],
159      ['022','111','200']
160
161   ];
162
163   function __construct($dimen,$handicap,$mefirst,$yours,$mine) {
164     $this->dimen = 3;
165     $this->handicap = (($handicap) ? 1 : 0);
166     $this->mefirst = (($mefirst) ? 1 : 0);
167     $this->yours = str_replace('XXX','',$yours);
168     $this->mine  = $mine;
169     $this->you = $this->parse_moves('you');
170     $this->me  = $this->parse_moves('me');
171
172     if(strlen($yours))
173       $this->first_move = false;
174   }
175
176   function play() {
177
178      if($this->first_move) {
179        if(rand(0,1) == 1) {
180          $o .=  '<div class="error-message">' . DI::l10n()->t('You go first...') . '</div><br />';
181          $this->mefirst = 0;
182          $o .= $this->draw_board();
183          return $o;
184        }
185        $o .=  '<div class="error-message">' . DI::l10n()->t('I\'m going first this time...') . ' </div><br />';
186        $this->mefirst = 1;
187
188      }
189
190      if($this->check_youwin()) {
191        $o .=  '<div class="error-message">' . DI::l10n()->t('You won!') . '</div><br />';
192        $o .= $this->draw_board();
193        return $o;
194      }
195
196      if($this->fullboard())
197        $o .=  '<div class="error-message">' . DI::l10n()->t('"Cat" game!') . '</div><br />';
198
199      $move = $this->winning_move();
200      if(strlen($move)) {
201        $this->mine .= $move;
202        $this->me = $this->parse_moves('me');
203      }
204      else {
205        $move = $this->defensive_move();
206        if(strlen($move)) {
207          $this->mine .= $move;
208          $this->me = $this->parse_moves('me');
209        }
210        else {
211          $move = $this->offensive_move();
212          if(strlen($move)) {
213            $this->mine .= $move;
214            $this->me = $this->parse_moves('me');
215          }
216        }
217      }
218
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();
224         return $o;
225   }
226
227   function parse_moves($player) {
228     if($player == 'me')
229       $str = $this->mine;
230     if($player == 'you')
231       $str = $this->yours;
232     $ret = [];
233       while(strlen($str)) {
234          $ret[] = substr($str,0,3);
235          $str = substr($str,3);
236       }
237     return $ret;
238   }
239
240
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];
245         return true;
246       }
247     }
248     return false;
249   }
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];
254         return true;
255       }
256     }
257     return false;
258   }
259   function defensive_move() {
260
261     for($x = 0; $x < count($this->winner); $x ++) {
262       if(($this->handicap) && in_array('111',$this->winner[$x]))
263         continue;
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]);
270      }
271      return '';
272   }
273
274 function winning_move() {
275
276     for($x = 0; $x < count($this->winner); $x ++) {
277       if(($this->handicap) && in_array('111',$this->winner[$x]))
278         continue;
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]);
285      }
286
287 }
288
289   function offensive_move() {
290
291     shuffle($this->planes);
292     shuffle($this->winner);
293     shuffle($this->corners);
294     shuffle($this->crosses);
295
296     if(! count($this->me)) {
297       if($this->handicap) {
298         $p = $this->uncontested_plane();
299         foreach($this->corners as $c)
300           if((in_array($c,$p))
301             && (! $this->is_yours($c)) && (! $this->is_mine($c)))
302               return($c);
303       }
304       else {
305         if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1)))
306           return '111';
307         $p = $this->uncontested_plane();
308         foreach($this->crosses as $c)
309           if((in_array($c,$p))
310             && (! $this->is_yours($c)) && (! $this->is_mine($c)))
311             return($c);
312       }
313     }
314
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)))
324                 continue;
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])
329                     return($w[2]);
330                   elseif($this->me[0] == $w[2])
331                     return($w[0]);
332               }
333             }
334           }
335         }
336         else {
337           $r = $this->get_corners($this->me);
338           if(count($r) > 1) {
339             $w1 = []; $w2 = [];
340             foreach($this->winner as $w) {
341               if(in_array('111',$w))
342                 continue;
343               if(($r[0] == $w[0]) || ($r[0] == $w[2]))
344                 $w1[] = $w;
345               if(($r[1] == $w[0]) || ($r[1] == $w[2]))
346                 $w2[] = $w;
347             }
348             if(count($w1) && count($w2)) {
349               foreach($w1 as $a) {
350                 foreach($w2 as $b) {
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)))
357                     continue;
358                   if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) {
359                     return $a[0];
360                   }
361                   elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) {
362                     return $a[2];
363                   }
364                 }
365               }
366             }
367           }
368         }
369       }
370     }
371
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])) {
375
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.
379
380 //      $p = $this->uncontested_plane();
381  //     foreach($this->crosses as $c)
382    //     if(in_array($c,$p))
383      //     return($c);
384
385 //    }
386
387
388     // find all the winners containing my points.
389     $mywinners = [];
390     foreach($this->winner as $w)
391       foreach($this->me as $m)
392         if((in_array($m,$w)) && (! in_array($w,$mywinners)))
393           $mywinners[] = $w;
394
395     // find all the rules where my points are in the center.
396       $trythese = [];
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)))
402             $trythese[] = $w;
403           }
404         }
405       }
406
407       $myplanes = [];
408       for($p = 0; $p < count($this->planes); $p ++) {
409         if($this->handicap && in_array('111',$this->planes[$p]))
410           continue;
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];
415       }
416       shuffle($myplanes);
417
418     // find all winners which share an endpoint, and which are uncontested
419       $candidates = [];
420       if(count($trythese) && count($myplanes)) {
421         foreach($trythese as $t) {
422           foreach($this->winner as $w) {
423             if(! $this->uncontested_winner($w))
424               continue;
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))
429                     $candidates[] = $w;
430             }
431           }
432         }
433       }
434
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.
439
440       if(count($candidates)) {
441         foreach($candidates as $c) {
442           if(in_array($c[1],$this->me)) {
443             // return endpoint
444             foreach($trythese as $t)
445               if($t[0] == $c[0])
446                 return($t[0]);
447               elseif($t[2] == $c[2])
448                 return($t[2]);
449           }
450        }
451
452        // find opponents planes
453       $yourplanes = [];
454       for($p = 0; $p < count($this->planes); $p ++) {
455         if($this->handicap && in_array('111',$this->planes[$p]))
456           continue;
457         if(in_array($this->you[0],$this->planes[$p]))
458           $yourplanes[] = $this->planes[$p];
459       }
460
461       shuffle($this->winner);
462       foreach($candidates as $c) {
463
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
468          // defensive mode.
469          // We want:        or:         not:
470          //           X|O|     X| |       X| |
471          //            |O|     O|O|        |O|
472          //            | |      | |        |O|
473
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])) {
479                  return($c[1]);
480              }
481            }
482          }
483        }
484
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
487
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)) {
492                    return($c[1]);
493            }
494          }
495        }
496      }
497
498     // worst case scenario, no strategy we can play,
499     // just find an empty space and take it
500
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)
507               continue;
508             return(sprintf("%d%d%d",$x,$y,$z));
509           }
510
511   return '';
512   }
513
514   function marked_yours($x,$y,$z) {
515    $str = sprintf("%d%d%d",$x,$y,$z);
516    if(in_array($str,$this->you))
517      return true;
518    return false;
519   }
520
521   function marked_mine($x,$y,$z) {
522    $str = sprintf("%d%d%d",$x,$y,$z);
523    if(in_array($str,$this->me))
524      return true;
525    return false;
526   }
527
528   function is_yours($str) {
529    if(in_array($str,$this->you))
530      return true;
531    return false;
532   }
533
534   function is_mine($str) {
535    if(in_array($str,$this->me))
536      return true;
537    return false;
538   }
539
540   function get_corners($a) {
541     $total = [];
542     if(count($a))
543       foreach($a as $b)
544         if(in_array($b,$this->corners))
545           $total[] = $b;
546     return $total;
547   }
548
549   function uncontested_winner($w) {
550     if($this->handicap && in_array('111',$w))
551       return false;
552     $contested = false;
553     if(count($this->you)) {
554       foreach($this->you as $you)
555         if(in_array($you,$w))
556           $contested = true;
557     }
558     return (($contested) ? false : true);
559   }
560
561
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");
565
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)))
569      return true;
570    return false;
571
572   }
573
574   function my_best_plane() {
575
576     $second_choice = [];
577     shuffle($this->planes);
578     for($p = 0; $p < count($this->planes); $p ++ ) {
579       $contested = 0;
580       if($this->handicap && in_array('111',$this->planes[$p]))
581         continue;
582       if(! in_array($this->me[0],$this->planes[$p]))
583         continue;
584       foreach($this->you as $m) {
585         if(in_array($m,$this->planes[$p]))
586           $contested ++;
587       }
588       if(! $contested)
589         return($this->planes[$p]);
590       if($contested == 1)
591         $second_choice = $this->planes[$p];
592     }
593     return $second_choice;
594   }
595
596
597
598
599
600
601
602   function uncontested_plane() {
603     $freeplane = true;
604     shuffle($this->planes);
605     $pl = $this->planes;
606
607     for($p = 0; $p < count($pl); $p ++ ) {
608         if($this->handicap && in_array('111',$pl[$p]))
609           continue;
610        foreach($this->you as $m) {
611          if(in_array($m,$pl[$p]))
612            $freeplane = false;
613        }
614        if(! $freeplane) {
615          $freeplane = true;
616          continue;
617        }
618        if($freeplane)
619          return($pl[$p]);
620     }
621     return [];
622   }
623
624   function fullboard() {
625    return false;
626   }
627
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 ++) {
633       $o .=  '<table>';
634       for($y = 0; $y < $this->dimen; $y ++) {
635         $o .=  '<tr>';
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\">&nbsp;</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>";
647           else {
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>";
650           }
651         }
652         $o .=  '</tr>';
653       }
654       $o .=  '</table><br />';
655     }
656     $o .=  '</form>';
657         return $o;
658
659   }
660
661
662 }
663