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