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