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