PageRenderTime 83ms CodeModel.GetById 45ms RepoModel.GetById 1ms app.codeStats 1ms

/tictac/tictac.php

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