PageRenderTime 34ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Bundle/LichessBundle/Document/Player.php

http://github.com/ornicar/lichess
PHP | 620 lines | 317 code | 80 blank | 223 comment | 28 complexity | f23e18a4a2b1f1bd7f9afc63dce738b5 MD5 | raw file
  1. <?php
  2. namespace Bundle\LichessBundle\Document;
  3. use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
  4. use Bundle\LichessBundle\Util\KeyGenerator;
  5. use Bundle\LichessBundle\Chess\PieceFilter;
  6. use Doctrine\Common\Collections\Collection;
  7. use FOS\UserBundle\Model\User;
  8. use Bundle\LichessBundle\Chess\Board;
  9. /**
  10. * Represents a single Chess player for one game
  11. *
  12. * @MongoDB\EmbeddedDocument
  13. * @author Thibault Duplessis <thibault.duplessis@gmail.com>
  14. */
  15. class Player
  16. {
  17. /**
  18. * Unique ID of the player for this game
  19. *
  20. * @var string
  21. * @MongoDB\Field(type="string")
  22. */
  23. protected $id;
  24. /**
  25. * User bound to the player - optional
  26. *
  27. * @var User
  28. * @MongoDB\ReferenceOne(targetDocument="Application\UserBundle\Document\User")
  29. */
  30. protected $user = null;
  31. /**
  32. * Fixed ELO of the player user, if any
  33. *
  34. * @var int
  35. * @MongoDB\Field(type="int")
  36. */
  37. protected $elo = null;
  38. /**
  39. * Elo the players gains or loses during this game
  40. *
  41. * @var int
  42. * @MongoDB\Field(type="int")
  43. */
  44. protected $eloDiff = null;
  45. /**
  46. * the player color, white or black
  47. *
  48. * @var string
  49. * @MongoDB\Field(type="string")
  50. */
  51. protected $c;
  52. /**
  53. * Whether the player won the game or not
  54. *
  55. * @var boolean
  56. * @MongoDB\Field(type="boolean")
  57. */
  58. protected $w;
  59. /**
  60. * Whether this player is an Artificial intelligence or not
  61. *
  62. * @var boolean
  63. * @MongoDB\Field(type="boolean")
  64. */
  65. protected $isAi;
  66. /**
  67. * If the player is an AI, its level represents the AI intelligence
  68. *
  69. * @var int
  70. * @MongoDB\Field(type="int")
  71. */
  72. protected $aiLevel;
  73. /**
  74. * the player stack events, compressed for efficient storage
  75. *
  76. * @var string
  77. * @MongoDB\String
  78. */
  79. protected $evts;
  80. /**
  81. * the player pieces, extracted from ps
  82. *
  83. * @var array
  84. */
  85. protected $pieces;
  86. /**
  87. * the player pieces, compressed for efficient storage
  88. *
  89. * @var string
  90. * @MongoDB\String
  91. */
  92. protected $ps;
  93. /**
  94. * Whether the player is offering draw or not
  95. *
  96. * @var bool
  97. * @MongoDB\Field(type="boolean")
  98. */
  99. protected $isOfferingDraw = null;
  100. /**
  101. * Whether the player is proposing takeback or not
  102. *
  103. * @var bool
  104. * @MongoDB\Field(type="boolean")
  105. */
  106. protected $isProposingTakeback = null;
  107. /**
  108. * Whether the player is offering rematch or not
  109. *
  110. * @var bool
  111. * @MongoDB\Field(type="boolean")
  112. */
  113. protected $isOfferingRematch = null;
  114. /**
  115. * Number of turns when last offered a draw
  116. *
  117. * @var int
  118. * @MongoDB\Field(type="int")
  119. */
  120. protected $lastDrawOffer = null;
  121. /**
  122. * @var integer
  123. * @MongoDB\Field(type="int")
  124. */
  125. protected $blurs;
  126. /**
  127. * Array of move times relative to the opponent previous move
  128. * Which really means: the time used to make the move
  129. *
  130. * @var array of int compressed to string
  131. * @MongoDB\String
  132. */
  133. protected $mts = null;
  134. /**
  135. * the player current game
  136. *
  137. * @var Game
  138. */
  139. protected $game;
  140. public function __construct($color)
  141. {
  142. if(!in_array($color, array('white', 'black'))) {
  143. throw new \InvalidArgumentException(sprintf('"%s" is not a valid player color'));
  144. }
  145. $this->c = $color;
  146. $this->generateId();
  147. }
  148. public function getBlurs()
  149. {
  150. return $this->blurs ? $this->blurs : 0;
  151. }
  152. public function getBlurPercent()
  153. {
  154. $nbMoves = $this->getNbMoves();
  155. return $nbMoves == 0 ? 0 : round(($this->getBlurs() * 100) / $nbMoves);
  156. }
  157. public function getNbMoves()
  158. {
  159. return floor(($this->getGame()->getTurns() + ($this->isWhite() ? 1 : 0)) / 2);
  160. }
  161. /**
  162. * Tells if this player saved his move times
  163. *
  164. * @return boolean
  165. */
  166. public function hasMoveTimes()
  167. {
  168. return !empty($this->mts) && strlen($this->mts) > 12;
  169. }
  170. /**
  171. * Gets the moves times
  172. *
  173. * @return array of int
  174. */
  175. public function getMoveTimes()
  176. {
  177. return array_map(function($t) { return (int)$t; }, explode(' ', $this->mts));
  178. }
  179. /**
  180. * @return int
  181. */
  182. public function getEloDiff()
  183. {
  184. return $this->eloDiff;
  185. }
  186. /**
  187. * @param int
  188. * @return null
  189. */
  190. public function setEloDiff($eloDiff)
  191. {
  192. $this->eloDiff = $eloDiff;
  193. }
  194. /**
  195. * @return bool
  196. */
  197. public function getIsOfferingDraw()
  198. {
  199. return $this->isOfferingDraw;
  200. }
  201. /**
  202. * @param bool
  203. * @return null
  204. */
  205. public function setIsOfferingDraw($isOfferingDraw)
  206. {
  207. $this->isOfferingDraw = $isOfferingDraw ?: null;
  208. if($this->isOfferingDraw) {
  209. $this->lastDrawOffer = $this->getGame()->getTurns();
  210. }
  211. }
  212. /**
  213. * @return bool
  214. */
  215. public function getIsProposingTakeback()
  216. {
  217. return $this->isProposingTakeback;
  218. }
  219. /**
  220. * @param bool
  221. * @return null
  222. */
  223. public function setIsProposingTakeback($isProposingTakeback)
  224. {
  225. $this->isProposingTakeback = $isProposingTakeback ?: null;
  226. }
  227. /**
  228. * @return bool
  229. */
  230. public function getIsOfferingRematch()
  231. {
  232. return $this->isOfferingRematch;
  233. }
  234. /**
  235. * @param bool
  236. * @return null
  237. */
  238. public function setIsOfferingRematch($isOfferingRematch)
  239. {
  240. $this->isOfferingRematch = $isOfferingRematch ?: null;
  241. }
  242. public function canProposeTakeback()
  243. {
  244. return $this->getGame()->getIsStarted()
  245. && $this->getGame()->getIsPlayable()
  246. && $this->getGame()->getHasEnoughMovesToTakeback()
  247. && !$this->getIsProposingTakeback();
  248. }
  249. public function canOfferDraw()
  250. {
  251. return $this->getGame()->getIsStarted()
  252. && $this->getGame()->getIsPlayable()
  253. && $this->getGame()->getHasEnoughMovesToDraw()
  254. && !$this->getIsOfferingDraw()
  255. && !$this->getOpponent()->getIsAi()
  256. && !$this->getOpponent()->getIsOfferingDraw()
  257. && !$this->hasOfferedDraw();
  258. }
  259. protected function hasOfferedDraw()
  260. {
  261. if(!$this->lastDrawOffer) {
  262. return false;
  263. }
  264. return $this->lastDrawOffer >= ($this->getGame()->getTurns() - 1);
  265. }
  266. public function canOfferRematch()
  267. {
  268. return $this->getGame()->getIsFinishedOrAborted()
  269. && !$this->getOpponent()->getIsAi();
  270. }
  271. /**
  272. * Get the user bound to this player, if any
  273. *
  274. * @return User or null
  275. */
  276. public function getUser()
  277. {
  278. return $this->user;
  279. }
  280. public function hasUser()
  281. {
  282. return $this->user != null;
  283. }
  284. /**
  285. * Set the user bound to this player
  286. *
  287. * @param User $user
  288. * @return null
  289. */
  290. public function setUser(User $user = null)
  291. {
  292. $this->user = $user;
  293. if($this->user) {
  294. $this->elo = $user->getElo();
  295. $this->getGame()->addUserId($user->getId());
  296. }
  297. }
  298. /**
  299. * @return int
  300. */
  301. public function getElo()
  302. {
  303. return $this->elo;
  304. }
  305. /**
  306. * Get the username of the player, or "Anonymous" if the player is not authenticated
  307. *
  308. * @return string
  309. **/
  310. public function getUsername($default = 'Anonymous')
  311. {
  312. if($this->getIsAi()) {
  313. return sprintf('A.I. level %d', $this->getAiLevel());
  314. }
  315. $user = $this->getUser();
  316. if(!$user) {
  317. return $default;
  318. }
  319. return $user->getUsername();
  320. }
  321. /**
  322. * Get the username and ELO of the player, or "Anonymous" if the player is not authenticated
  323. *
  324. * @return string
  325. **/
  326. public function getUsernameWithElo($default = 'Anonymous')
  327. {
  328. if($this->getIsAi()) {
  329. return sprintf('A.I. level %d', $this->getAiLevel());
  330. }
  331. $user = $this->getUser();
  332. if(!$user) {
  333. return $default;
  334. }
  335. return sprintf('%s (%d)', $user->getUsername(), $this->getElo());
  336. }
  337. /**
  338. * Generate a new ID - don't use once the player is saved
  339. *
  340. * @return null
  341. **/
  342. protected function generateId()
  343. {
  344. if(null !== $this->id) {
  345. throw new \LogicException('Can not change the id of a saved player');
  346. }
  347. $this->id = KeyGenerator::generate(4);
  348. }
  349. /**
  350. * @return string
  351. */
  352. public function getId()
  353. {
  354. return $this->id;
  355. }
  356. /**
  357. * @return string
  358. */
  359. public function getFullId()
  360. {
  361. return $this->game->getId().$this->getId();
  362. }
  363. /**
  364. * @return int
  365. */
  366. public function getAiLevel()
  367. {
  368. return $this->aiLevel;
  369. }
  370. /**
  371. * @param int
  372. */
  373. public function setAiLevel($aiLevel)
  374. {
  375. $this->aiLevel = $aiLevel;
  376. }
  377. /**
  378. * @return array
  379. */
  380. public function getPiecesByClass($class) {
  381. $pieces = array();
  382. foreach($this->getPieces() as $piece) {
  383. if($piece->isClass($class)) {
  384. $pieces[] = $piece;
  385. }
  386. }
  387. return $pieces;
  388. }
  389. public function getNbAlivePieces()
  390. {
  391. $nb = 0;
  392. foreach($this->getPieces() as $piece) {
  393. if(!$piece->getIsDead()) {
  394. ++$nb;
  395. }
  396. }
  397. return $nb;
  398. }
  399. public function getDeadPieces()
  400. {
  401. $pieces = array();
  402. foreach($this->getPieces() as $piece) {
  403. if($piece->getIsDead()) {
  404. $pieces[] = $piece;
  405. }
  406. }
  407. return $pieces;
  408. }
  409. /**
  410. * @return boolean
  411. */
  412. public function getIsAi()
  413. {
  414. return (boolean) $this->isAi;
  415. }
  416. /**
  417. * @return boolean
  418. */
  419. public function getIsHuman()
  420. {
  421. return !$this->getIsAi();
  422. }
  423. /**
  424. * @param boolean
  425. */
  426. public function setIsAi($isAi)
  427. {
  428. $this->isAi = $isAi;
  429. }
  430. /**
  431. * @return boolean
  432. */
  433. public function getIsWinner()
  434. {
  435. return (boolean) $this->w;
  436. }
  437. /**
  438. * @param boolean
  439. */
  440. public function setIsWinner($isWinner)
  441. {
  442. $this->w = $isWinner;
  443. }
  444. /**
  445. * @return array
  446. */
  447. public function getPieces()
  448. {
  449. return $this->pieces;
  450. }
  451. /**
  452. * @param array
  453. */
  454. public function setPieces(array $pieces)
  455. {
  456. $this->pieces = array();
  457. foreach($pieces as $piece) {
  458. $this->addPiece($piece);
  459. }
  460. }
  461. public function addPiece(Piece $piece)
  462. {
  463. $piece->setColor($this->getColor());
  464. $this->pieces[] = $piece;
  465. }
  466. /**
  467. * @return Game
  468. */
  469. public function getGame()
  470. {
  471. return $this->game;
  472. }
  473. /**
  474. * @param Game
  475. */
  476. public function setGame(Game $game)
  477. {
  478. $this->game = $game;
  479. }
  480. /**
  481. * @return string
  482. */
  483. public function getColor()
  484. {
  485. return $this->c;
  486. }
  487. public function getColorLetter()
  488. {
  489. return $this->c{0};
  490. }
  491. public function getOpponent()
  492. {
  493. return $this->getGame()->getPlayer('white' === $this->c ? 'black' : 'white');
  494. }
  495. public function isWhite()
  496. {
  497. return 'white' === $this->c;
  498. }
  499. public function isBlack()
  500. {
  501. return 'black' === $this->c;
  502. }
  503. public function __toString()
  504. {
  505. $string = $this->getColor().' '.($this->getIsAi() ? 'A.I.' : 'Human');
  506. return $string;
  507. }
  508. public function isMyTurn()
  509. {
  510. return $this->game->getTurns() %2 xor 'white' === $this->c;
  511. }
  512. public function getBoard()
  513. {
  514. return $this->getGame()->getBoard();
  515. }
  516. public function compressPieces()
  517. {
  518. $ps = array();
  519. foreach($this->getPieces() as $piece) {
  520. $letter = Piece::classToLetter($piece->getClass());
  521. if ($piece->getIsDead()) $letter = strtoupper($letter);
  522. $ps[] = Board::keyToPiotr($piece->getSquareKey()) . $letter;
  523. }
  524. $this->ps = implode(' ', $ps);
  525. }
  526. public function extractPieces()
  527. {
  528. $pieces = array();
  529. if (!empty($this->ps)) {
  530. foreach(explode(' ', $this->ps) as $p) {
  531. $pos = Board::keyToPos(Board::piotrToKey($p{0}));
  532. $class = Piece::letterToClass(strtolower($p{1}));
  533. $piece = new Piece($pos[0], $pos[1], $class);
  534. if (ctype_upper($p{1})) $piece->setIsDead(true);
  535. $pieces[] = $piece;
  536. }
  537. }
  538. $this->setPieces($pieces);
  539. }
  540. }