Update t() calls
[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 use Friendica\Core\Addon;
9 use Friendica\Core\L10n;
10
11 function tictac_install() {
12         Addon::registerHook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
13 }
14
15 function tictac_uninstall() {
16         Addon::unregisterHook('app_menu', 'addon/tictac/tictac.php', 'tictac_app_menu');
17
18 }
19
20 function tictac_app_menu($a,&$b) {
21         $b['app_menu'][] = '<div class="app-title"><a href="tictac">' . L10n::t('Three Dimensional Tic-Tac-Toe') . '</a></div>';
22 }
23
24
25 function tictac_module() {
26         return;
27 }
28
29
30
31
32
33 function tictac_content(&$a) {
34
35         $o = '';
36
37   if($_POST['move']) {
38     $handicap = $a->argv[1];
39     $mefirst = $a->argv[2];
40     $dimen = $a->argv[3];
41     $yours = $a->argv[4];
42     $mine  = $a->argv[5];
43
44     $yours .= $_POST['move'];
45   }
46   elseif($a->argc > 1) {
47     $handicap = $a->argv[1];
48     $dimen = 3;
49   }
50   else {
51    $dimen = 3;
52   }
53
54   $o .=  '<h3>' . L10n::t('3D Tic-Tac-Toe') . '</h3><br />';
55
56   $t = new tictac($dimen,$handicap,$mefirst,$yours,$mine);
57   $o .= $t->play();
58
59   $o .=  '<a href="tictac">' . L10n::t('New game') . '</a><br />';
60   $o .=  '<a href="tictac/1">' . L10n::t('New game with handicap') . '</a><br />';
61   $o .=  '<p>' . L10n::t('Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. ');
62   $o .= 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.');
63   $o .= '</p><p>';
64   $o .= L10n::t('The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage.');
65   $o .= '</p>';
66
67   return $o;
68
69 }
70
71 class tictac {
72   private $dimen;
73   private $first_move = true;
74   private $handicap = 0;
75   private $yours;
76   private $mine;
77   private $winning_play;
78   private $you;
79   private $me;
80   private $debug = 1;
81   private $crosses = ['011','101','110','112','121','211'];
82
83 /*
84     '001','010','011','012','021',
85     '101','110','111','112','121',
86     '201','210','211','212','221');
87 */
88
89   private $corners = [
90     '000','002','020','022',
91     '200','202','220','222'];
92
93   private $planes = [
94     ['000','001','002','010','011','012','020','021','022'], // horiz 1
95     ['100','101','102','110','111','112','120','121','122'], // 2
96     ['200','201','202','210','211','212','220','221','222'], // 3
97     ['000','010','020','100','110','120','200','210','220'], // vert left
98     ['000','001','002','100','101','102','200','201','202'], // vert top
99     ['002','012','022','102','112','122','202','212','222'], // vert right
100     ['020','021','022','120','121','122','220','221','222'], // vert bot
101     ['010','011','012','110','111','112','210','211','212'], // left vertx
102     ['001','011','021','101','111','221','201','211','221'], // top vertx
103     ['000','001','002','110','111','112','220','221','222'], // diag top
104     ['020','021','022','110','111','112','200','201','202'], // diag bot
105     ['000','010','020','101','111','121','202','212','222'], // diag left
106     ['002','012','022','101','111','121','200','210','220'], // diag right
107     ['002','011','020','102','111','120','202','211','220'], // diag x
108     ['000','011','022','100','111','122','200','211','222']  // diag x
109
110   ];
111
112
113   private $winner = [
114      ['000','001','002'],         // board 0 winners  - left corner across
115      ['000','010','020'],         // down
116      ['000','011','022'],         // diag
117      ['001','011','021'],         // middle-top down
118      ['010','011','012'],         // middle-left across
119      ['002','011','020'],         // right-top diag
120      ['002','012','022'],         // right-top down
121      ['020','021','022'],        // bottom-left across
122      ['100','101','102'],      // board 1 winners
123      ['100','110','120'],
124      ['100','111','122'],
125      ['101','111','121'],
126      ['110','111','112'],
127      ['102','111','120'],
128      ['102','112','122'],
129      ['120','121','122'],
130      ['200','201','202'],    // board 2 winners
131      ['200','210','220'],
132      ['200','211','222'],
133      ['201','211','221'],
134      ['210','211','212'],
135      ['202','211','220'],
136      ['202','212','222'],
137      ['220','221','222'],
138      ['000','100','200'],      // top-left corner 3d
139      ['000','101','202'],
140      ['000','110','220'],
141      ['000','111','222'],
142      ['001','101','201'],      // top-middle 3d
143      ['001','111','221'],
144      ['002','102','202'],      // top-right corner 3d
145      ['002','101','200'],
146      ['002','112','222'],
147      ['002','111','220'],
148      ['010','110','210'],      // left-middle 3d
149      ['010','111','212'],
150      ['011','111','211'],      // middle-middle 3d
151      ['012','112','212'],      // right-middle 3d
152      ['012','111','210'],
153      ['020','120','220'],      // bottom-left corner 3d
154      ['020','110','200'],
155      ['020','121','222'],
156      ['020','111','202'],
157      ['021','121','221'],      // bottom-middle 3d
158      ['021','111','201'],
159      ['022','122','222'],      // bottom-right corner 3d
160      ['022','121','220'],
161      ['022','112','202'],
162      ['022','111','200']
163
164   ];
165
166   function __construct($dimen,$handicap,$mefirst,$yours,$mine) {
167     $this->dimen = 3;
168     $this->handicap = (($handicap) ? 1 : 0);
169     $this->mefirst = (($mefirst) ? 1 : 0);
170     $this->yours = str_replace('XXX','',$yours);
171     $this->mine  = $mine;
172     $this->you = $this->parse_moves('you');
173     $this->me  = $this->parse_moves('me');
174
175     if(strlen($yours))
176       $this->first_move = false;
177   }
178
179   function play() {
180
181      if($this->first_move) {
182        if(rand(0,1) == 1) {
183          $o .=  '<div class="error-message">' . L10n::t('You go first...') . '</div><br />';
184          $this->mefirst = 0;
185          $o .= $this->draw_board();
186          return $o;
187        }
188        $o .=  '<div class="error-message">' . L10n::t('I\'m going first this time...') . ' </div><br />';
189        $this->mefirst = 1;
190
191      }
192
193      if($this->check_youwin()) {
194        $o .=  '<div class="error-message">' . L10n::t('You won!') . '</div><br />';
195        $o .= $this->draw_board();
196        return $o;
197      }
198
199      if($this->fullboard())
200        $o .=  '<div class="error-message">' . L10n::t('"Cat" game!') . '</div><br />';
201
202      $move = $this->winning_move();
203      if(strlen($move)) {
204        $this->mine .= $move;
205        $this->me = $this->parse_moves('me');
206      }
207      else {
208        $move = $this->defensive_move();
209        if(strlen($move)) {
210          $this->mine .= $move;
211          $this->me = $this->parse_moves('me');
212        }
213        else {
214          $move = $this->offensive_move();
215          if(strlen($move)) {
216            $this->mine .= $move;
217            $this->me = $this->parse_moves('me');
218          }
219        }
220      }
221
222      if($this->check_iwon())
223        $o .=  '<div class="error-message">' . L10n::t('I won!') . '</div><br />';
224      if($this->fullboard())
225        $o .=  '<div class="error-message">' . L10n::t('"Cat" game!') . '</div><br />';
226      $o .= $this->draw_board();
227         return $o;
228   }
229
230   function parse_moves($player) {
231     if($player == 'me')
232       $str = $this->mine;
233     if($player == 'you')
234       $str = $this->yours;
235     $ret = [];
236       while(strlen($str)) {
237          $ret[] = substr($str,0,3);
238          $str = substr($str,3);
239       }
240     return $ret;
241   }
242
243
244   function check_youwin() {
245     for($x = 0; $x < count($this->winner); $x ++) {
246       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)) {
247         $this->winning_play = $this->winner[$x];
248         return true;
249       }
250     }
251     return false;
252   }
253   function check_iwon() {
254     for($x = 0; $x < count($this->winner); $x ++) {
255       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)) {
256         $this->winning_play = $this->winner[$x];
257         return true;
258       }
259     }
260     return false;
261   }
262   function defensive_move() {
263
264     for($x = 0; $x < count($this->winner); $x ++) {
265       if(($this->handicap) && in_array('111',$this->winner[$x]))
266         continue;
267       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)))
268         return($this->winner[$x][2]);
269       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)))
270         return($this->winner[$x][1]);
271       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)))
272         return($this->winner[$x][0]);
273      }
274      return '';
275   }
276
277 function winning_move() {
278
279     for($x = 0; $x < count($this->winner); $x ++) {
280       if(($this->handicap) && in_array('111',$this->winner[$x]))
281         continue;
282       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)))
283         return($this->winner[$x][2]);
284       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)))
285         return($this->winner[$x][1]);
286       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)))
287         return($this->winner[$x][0]);
288      }
289
290 }
291
292   function offensive_move() {
293
294     shuffle($this->planes);
295     shuffle($this->winner);
296     shuffle($this->corners);
297     shuffle($this->crosses);
298
299     if(! count($this->me)) {
300       if($this->handicap) {
301         $p = $this->uncontested_plane();
302         foreach($this->corners as $c)
303           if((in_array($c,$p))
304             && (! $this->is_yours($c)) && (! $this->is_mine($c)))
305               return($c);
306       }
307       else {
308         if((! $this->marked_yours(1,1,1)) && (! $this->marked_mine(1,1,1)))
309           return '111';
310         $p = $this->uncontested_plane();
311         foreach($this->crosses as $c)
312           if((in_array($c,$p))
313             && (! $this->is_yours($c)) && (! $this->is_mine($c)))
314             return($c);
315       }
316     }
317
318     if($this->handicap) {
319       if(count($this->me) >= 1) {
320         if(count($this->get_corners($this->me)) == 1) {
321           if(in_array($this->me[0],$this->corners)) {
322             $p = $this->my_best_plane();
323             foreach($this->winner as $w) {
324               if((in_array($w[0],$this->you))
325               || (in_array($w[1],$this->you))
326               || (in_array($w[2],$this->you)))
327                 continue;
328               if(in_array($w[0],$this->corners)
329                 && in_array($w[2],$this->corners)
330                 && in_array($w[0],$p) && in_array($w[2],$p)) {
331                   if($this->me[0] == $w[0])
332                     return($w[2]);
333                   elseif($this->me[0] == $w[2])
334                     return($w[0]);
335               }
336             }
337           }
338         }
339         else {
340           $r = $this->get_corners($this->me);
341           if(count($r) > 1) {
342             $w1 = []; $w2 = [];
343             foreach($this->winner as $w) {
344               if(in_array('111',$w))
345                 continue;
346               if(($r[0] == $w[0]) || ($r[0] == $w[2]))
347                 $w1[] = $w;
348               if(($r[1] == $w[0]) || ($r[1] == $w[2]))
349                 $w2[] = $w;
350             }
351             if(count($w1) && count($w2)) {
352               foreach($w1 as $a) {
353                 foreach($w2 as $b) {
354                   if((in_array($a[0],$this->you))
355                   || (in_array($a[1],$this->you))
356                   || (in_array($a[2],$this->you))
357                   || (in_array($b[0],$this->you))
358                   || (in_array($b[1],$this->you))
359                   || (in_array($b[2],$this->you)))
360                     continue;
361                   if(($a[0] == $b[0]) && ! $this->is_mine($a[0])) {
362                     return $a[0];
363                   }
364                   elseif(($a[2] == $b[2]) && ! $this->is_mine($a[2])) {
365                     return $a[2];
366                   }
367                 }
368               }
369             }
370           }
371         }
372       }
373     }
374
375  //&& (count($this->me) == 1) && (count($this->you) == 1)
376  //     && in_array($this->you[0],$this->corners)
377  //     && $this->is_neighbor($this->me[0],$this->you[0])) {
378
379       // Yuck. You foiled my plan. Since you obviously aren't playing to win,
380       // I'll try again. You may keep me busy for a few rounds, but I'm
381       // gonna' get you eventually.
382
383 //      $p = $this->uncontested_plane();
384  //     foreach($this->crosses as $c)
385    //     if(in_array($c,$p))
386      //     return($c);
387
388 //    }
389
390
391     // find all the winners containing my points.
392     $mywinners = [];
393     foreach($this->winner as $w)
394       foreach($this->me as $m)
395         if((in_array($m,$w)) && (! in_array($w,$mywinners)))
396           $mywinners[] = $w;
397
398     // find all the rules where my points are in the center.
399       $trythese = [];
400       if(count($mywinners)) {
401         foreach($mywinners as $w) {
402           foreach($this->me as $m) {
403             if(($m == $w[1]) && ($this->uncontested_winner($w))
404               && (! in_array($w,$trythese)))
405             $trythese[] = $w;
406           }
407         }
408       }
409
410       $myplanes = [];
411       for($p = 0; $p < count($this->planes); $p ++) {
412         if($this->handicap && in_array('111',$this->planes[$p]))
413           continue;
414         foreach($this->me as $m)
415           if((in_array($m,$this->planes[$p]))
416             && (! in_array($this->planes[$p],$myplanes)))
417               $myplanes[] = $this->planes[$p];
418       }
419       shuffle($myplanes);
420
421     // find all winners which share an endpoint, and which are uncontested
422       $candidates = [];
423       if(count($trythese) && count($myplanes)) {
424         foreach($trythese as $t) {
425           foreach($this->winner as $w) {
426             if(! $this->uncontested_winner($w))
427               continue;
428             if((in_array($t[0],$w)) || (in_array($t[2],$w))) {
429               foreach($myplanes as $p)
430                 if(in_array($w[0],$p) && in_array($w[1],$p) && in_array($w[2],$p) && ($w[1] != $this->me[0]))
431                   if(! in_array($w,$candidates))
432                     $candidates[] = $w;
433             }
434           }
435         }
436       }
437
438       // Find out if we are about to force a win.
439       // Looking for two winning vectors with a common endpoint
440       // and where we own the middle of both - we are now going to
441       // grab the endpoint. The game isn't yet over but we've already won.
442
443       if(count($candidates)) {
444         foreach($candidates as $c) {
445           if(in_array($c[1],$this->me)) {
446             // return endpoint
447             foreach($trythese as $t)
448               if($t[0] == $c[0])
449                 return($t[0]);
450               elseif($t[2] == $c[2])
451                 return($t[2]);
452           }
453        }
454
455        // find opponents planes
456       $yourplanes = [];
457       for($p = 0; $p < count($this->planes); $p ++) {
458         if($this->handicap && in_array('111',$this->planes[$p]))
459           continue;
460         if(in_array($this->you[0],$this->planes[$p]))
461           $yourplanes[] = $this->planes[$p];
462       }
463
464       shuffle($this->winner);
465       foreach($candidates as $c) {
466
467          // We now have a list of winning strategy vectors for our second point
468          // Pick one that will force you into defensive mode.
469          // Pick a point close to you so we don't risk giving you two
470          // in a row when you block us. That would force *us* into
471          // defensive mode.
472          // We want:        or:         not:
473          //           X|O|     X| |       X| |
474          //            |O|     O|O|        |O|
475          //            | |      | |        |O|
476
477          if(count($this->you) == 1) {
478            foreach($this->winner as $w) {
479              if(in_array($this->me[0], $w) && in_array($c[1],$w)
480                && $this->uncontested_winner($w)
481                && $this->is_neighbor($this->you[0],$c[1])) {
482                  return($c[1]);
483              }
484            }
485          }
486        }
487
488        // You're somewhere else entirely or have made more than one move
489        // - any strategy vector which puts you on the defense will have to do
490
491        foreach($candidates as $c) {
492          foreach($this->winner as $w) {
493            if(in_array($this->me[0], $w) && in_array($c[1],$w)
494              && $this->uncontested_winner($w)) {
495                    return($c[1]);
496            }
497          }
498        }
499      }
500
501     // worst case scenario, no strategy we can play,
502     // just find an empty space and take it
503
504     for($x = 0; $x < $this->dimen; $x ++)
505       for($y = 0; $y < $this->dimen; $y ++)
506         for($z = 0; $z < $this->dimen; $z ++)
507           if((! $this->marked_yours($x,$y,$z))
508             && (! $this->marked_mine($x,$y,$z))) {
509             if($this->handicap && $x == 1 && $y == 1 && $z == 1)
510               continue;
511             return(sprintf("%d%d%d",$x,$y,$z));
512           }
513
514   return '';
515   }
516
517   function marked_yours($x,$y,$z) {
518    $str = sprintf("%d%d%d",$x,$y,$z);
519    if(in_array($str,$this->you))
520      return true;
521    return false;
522   }
523
524   function marked_mine($x,$y,$z) {
525    $str = sprintf("%d%d%d",$x,$y,$z);
526    if(in_array($str,$this->me))
527      return true;
528    return false;
529   }
530
531   function is_yours($str) {
532    if(in_array($str,$this->you))
533      return true;
534    return false;
535   }
536
537   function is_mine($str) {
538    if(in_array($str,$this->me))
539      return true;
540    return false;
541   }
542
543   function get_corners($a) {
544     $total = [];
545     if(count($a))
546       foreach($a as $b)
547         if(in_array($b,$this->corners))
548           $total[] = $b;
549     return $total;
550   }
551
552   function uncontested_winner($w) {
553     if($this->handicap && in_array('111',$w))
554       return false;
555     $contested = false;
556     if(count($this->you)) {
557       foreach($this->you as $you)
558         if(in_array($you,$w))
559           $contested = true;
560     }
561     return (($contested) ? false : true);
562   }
563
564
565   function is_neighbor($p1,$p2) {
566    list($x1,$y1,$z1) = sscanf($p1, "%1d%1d%1d");
567    list($x2,$y2,$z2) = sscanf($p2, "%1d%1d%1d");
568
569    if((($x1 == $x2) || ($x1 == $x2+1) || ($x1 == $x2-1)) &&
570       (($y1 == $y2) || ($y1 == $y2+1) || ($y1 == $y2-1)) &&
571       (($z1 == $z2) || ($z1 == $z2+1) || ($z1 == $z2-1)))
572      return true;
573    return false;
574
575   }
576
577   function my_best_plane() {
578
579     $second_choice = [];
580     shuffle($this->planes);
581     for($p = 0; $p < count($this->planes); $p ++ ) {
582       $contested = 0;
583       if($this->handicap && in_array('111',$this->planes[$p]))
584         continue;
585       if(! in_array($this->me[0],$this->planes[$p]))
586         continue;
587       foreach($this->you as $m) {
588         if(in_array($m,$this->planes[$p]))
589           $contested ++;
590       }
591       if(! $contested)
592         return($this->planes[$p]);
593       if($contested == 1)
594         $second_choice = $this->planes[$p];
595     }
596     return $second_choice;
597   }
598
599
600
601
602
603
604
605   function uncontested_plane() {
606     $freeplane = true;
607     shuffle($this->planes);
608     $pl = $this->planes;
609
610     for($p = 0; $p < count($pl); $p ++ ) {
611         if($this->handicap && in_array('111',$pl[$p]))
612           continue;
613        foreach($this->you as $m) {
614          if(in_array($m,$pl[$p]))
615            $freeplane = false;
616        }
617        if(! $freeplane) {
618          $freeplane = true;
619          continue;
620        }
621        if($freeplane)
622          return($pl[$p]);
623     }
624     return [];
625   }
626
627   function fullboard() {
628    return false;
629   }
630
631   function draw_board() {
632     if(! strlen($this->yours))
633       $this->yours = 'XXX';
634     $o .=  "<form action=\"tictac/{$this->handicap}/{$this->mefirst}/{$this->dimen}/{$this->yours}/{$this->mine}\" method=\"post\" />";
635     for($x = 0; $x < $this->dimen; $x ++) {
636       $o .=  '<table>';
637       for($y = 0; $y < $this->dimen; $y ++) {
638         $o .=  '<tr>';
639         for($z = 0; $z < $this->dimen; $z ++) {
640           $s = sprintf("%d%d%d",$x,$y,$z);
641           $winner = ((is_array($this->winning_play) && in_array($s,$this->winning_play)) ? " color: #FF0000; " : "");
642           $bordertop = (($y != 0) ? " border-top: 2px solid #000;" : "");
643           $borderleft = (($z != 0) ? " border-left: 2px solid #000;" : "");
644           if($this->handicap && $x == 1 && $y == 1 && $z == 1)
645             $o .=  "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\">&nbsp;</td>";
646           elseif($this->marked_yours($x,$y,$z))
647             $o .=  "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">X</td>";
648           elseif($this->marked_mine($x,$y,$z))
649             $o .=  "<td style=\"width: 25px; height: 25px; $bordertop $borderleft $winner\" align=\"center\">O</td>";
650           else {
651             $val = sprintf("%d%d%d",$x,$y,$z);
652             $o .=  "<td style=\"width: 25px; height: 25px; $bordertop $borderleft\" align=\"center\"><input type=\"checkbox\" name=\"move\" value=\"$val\" onclick=\"this.form.submit();\" /></td>";
653           }
654         }
655         $o .=  '</tr>';
656       }
657       $o .=  '</table><br />';
658     }
659     $o .=  '</form>';
660         return $o;
661
662   }
663
664
665 }
666