Official Addons for the Friendica Communications Platform. (please note that this is a clone of the repository at github, issues are handled there)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

661 lines
20 KiB

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