Skip to content

Commit

Permalink
Use Hand rather than an array of tiles, use Location rather than an a…
Browse files Browse the repository at this point in the history
…rray.
  • Loading branch information
Thomas Baker committed May 14, 2016
1 parent 0fec6dc commit ba26b81
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 39 deletions.
7 changes: 4 additions & 3 deletions board.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function apply(Move $move) {
return $this->score($move);
}

private function applyWithoutChecks(Move $move, $updateLocations = false) {
public function applyWithoutChecks(Move $move, $updateLocations = false) {
foreach ($move->placements as $placement) {
$this->removeLocation($placement->point());
list($x, $y) = [$placement->point()->x(), $placement->point()->y()];
Expand All @@ -69,7 +69,7 @@ private function addLocation(Point $pointToAdd) {
}
$sharedProperties = (new Hand($neighbors))->sharedProperties();
if ($sharedProperties !== null) {
$this->locations[(string)$pointToAdd] = ['point' => $pointToAdd, 'sharedProperties' => $sharedProperties];
$this->locations[(string)$pointToAdd] = new Location($pointToAdd, $sharedProperties);
}
}

Expand Down Expand Up @@ -164,4 +164,5 @@ public function __toString() {
}
return $s;
}
}
}

16 changes: 16 additions & 0 deletions location.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

class Location {
public function __construct(Point $point, array $sharedProperties) {
$this->point = $point;
$this->sharedProperties = $sharedProperties;
}

public function point() {
return $this->point;
}

public function sharedProperties() {
return $this->sharedProperties;
}
}
122 changes: 88 additions & 34 deletions player.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ public function move(Board $board, $bagIsEmpty) {
if ($board->isEmpty()) {
return $this->startingMove($tiles);
}
return $this->chooseMove($tiles, $board, $bagIsEmpty);
return $this->chooseMove(new Hand($tiles), $board, $bagIsEmpty);
}

protected function chooseMove(array $tiles, Board $board, $bagIsEmpty) {
$usefulSets = $this->usefulSets($tiles);
protected function chooseMove(Hand $hand, Board $board, $bagIsEmpty) {
$usefulSets = $this->usefulSets($hand);
list($max, $bestMove) = [0, new Move([])];
foreach ($usefulSets as $usefulSet) {
$move = $this->bestMoveWith($usefulSet, $board);
if ($move) {
$score = $this->evaluate($tiles, $move, $board, $bagIsEmpty);
$score = $this->evaluate($hand, $move, $board, $bagIsEmpty);
if ($score > $max) {
$max = $score;
$bestMove = $move;
Expand All @@ -84,26 +84,23 @@ protected function chooseMove(array $tiles, Board $board, $bagIsEmpty) {
return $bestMove;
}

protected function evaluate(array $tiles, Move $move, Board $board, $bagIsEmpty) {
protected function evaluate(Hand $hand, Move $move, Board $board, $bagIsEmpty) {
$score = $board->score($move);
if (count($move->placements()) === count($tiles) && $bagIsEmpty) {
//BAKERT bug here ... we have already removed the non unique tiles
if (count($move->placements()) === $hand->size() && $bagIsEmpty) {
$score += Score::FINISHING_BONUS;
}
return $score;
}

private function usefulSets(array $tiles) {
Assert::type($tiles, Tile);
if (array_unique($tiles) !== $tiles) {
throw new QwirkleException("fuck");
}
private function usefulSets(Hand $hand) {
$usefulSets = [];
foreach (array_merge(Color::colors(), Shape::shapes()) as $property) {
$propertySet = (new Hand($tiles))->withProperty($property->name());
$propertySet = $hand->withProperty($property->name());
if ($propertySet) {
foreach ($this->powerset($propertySet) as $set) {
if (count($set) > 0) {
$usefulSets[] = $set;
$usefulSets[] = new Hand($set);
}
}
}
Expand All @@ -121,53 +118,110 @@ private function powerset(array $items) {
return $results;
}

private function bestMoveWith(array $tiles, Board $board) {
private function bestMoveWith(Hand $hand, Board $board) {
list($max, $bestMove) = [0, new Move([])];
foreach ($board->attachmentLocations() as $point) {
foreach ($this->permutations($tiles, $board, $point) as $move) {
if ($board->isLegal($move)) {
$score = $board->score($move);
if ($score > $max) {
$max = $score;
$bestMove = $move;
}
foreach ($board->attachmentLocations() as $location) {
//BAKERT shoulld location be a class and does that add much overhead?
// Skip if a quick look convinces us the move will be illegal. For speed.
// Shaves about 20s off a 48s game.
if (!$this->quickCheckLocation($location, $hand)) {
continue;
}
foreach ($this->permutations($hand, $board, $location->point()) as $move) {
$score = $board->score($move);
if ($score > $max) {
$max = $score;
$bestMove = $move;
}
}
}
return $bestMove;
}

private function permutations(array $tiles, Board $board, Point $point) {
private function quickCheckLocation(Location $location, Hand $hand) {
foreach ($hand->sharedProperties() as $property) {
if (in_array($property, $location->sharedProperties())) {
return true;
}
}
return false;
}

private function permutations(Hand $hand, Board $board, Point $point) {
$moves = [];
foreach ([Direction::down(), Direction::right()] as $direction) {
if (count($tiles) === 1 && $direction === Direction::right()) {
if ($hand->size() === 1 && $direction === Direction::right()) {
continue; // No point in checking single tiles more than once in each spot.
}
for ($i = 0; $i < count($tiles); $i++) {
for ($i = 0; $i < $hand->size(); $i++) {
list($steps, $p) = [$i, $point];
while ($steps > 0) {
$p = $p->next($direction->opposite());
if ($board->at($p) === null) {
$steps -= 1;
}
}
foreach ($this->orderings($tiles) as $tilesToLay) {
$layingP = $p;
$tilesHand = new Hand($tilesToLay);
$placements = [];
while ($tilesToLay) {
if ($board->at($layingP) === null) {
$placements[] = new Placement($layingP, array_shift($tilesToLay));
//BAKERT perf improvement - don't try illegal tiles in the initial slot
//BAKERT at some point in here we know every tile and every point we are going to lay
// but we will do things like try a set of 4 xs in every possible permutation against a circle

//BAKERT experimental dual mode to reduce worst case but keep faster easy case
if ($hand->size() >= 3 /* make this a constant BAKERT */) {
$move = $this->tryFit($hand->tiles(), $board, $p, $direction);
if ($move !== null) {
$moves[] = $move;
}
} else {
foreach ($this->orderings($hand->tiles()) as $tilesToLay) {
$layingP = $p;
$placements = [];
while ($tilesToLay) {
if ($board->at($layingP) === null) {
$placements[] = new Placement($layingP, array_shift($tilesToLay));
}
$layingP = $layingP->next($direction);
}
$move = new Move($placements);
//BAKERT
// if (Game::$turn === 51) {
// $board2 = clone $board;
// $board2->applyWithoutChecks($move);
// echo $board2;
// }
if ($board->isLegal($move)) {
$moves[] = $move;
break; // We found a legal move using these tiles in this direction from this start position - we cannot do better score-wise.
}
$layingP = $layingP->next($direction);
}
$moves[] = new Move($placements);
}
}
}
return $moves;
}

private function tryFit(array $tiles, Board $board, Point $point, Direction $direction, array $rawMove = []) {
if (count($tiles) === 0) {
return new Move($rawMove);
}
foreach ($tiles as $tile) {
$placement = new Placement($point, $tile);
$newRawMove = array_merge($rawMove, [$placement]);
$move = new Move($newRawMove);
//BAKERT experiment with these numbers
if ((count($rawMove) <= 2 && count($tiles) > 1) || $board->isLegal($move)) {
$pos = array_search($tile, $tiles);
unset($tiles[$pos]);
$board = clone $board;
$board->applyWithoutChecks($move);
$move = $this->tryFit($tiles, $board, $point->next($direction), $direction, $newRawMove);
if ($move !== null) {
return $move;
}
}
}
return null;
}

private function orderings(array $items, array $processed = []) {
$result = [];
foreach ($items as $key => $value) {
Expand Down
5 changes: 3 additions & 2 deletions players/cautiousplayer.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

class CautiousPlayer extends Player {
public function evaluate(array $tiles, Move $move, Board $board, $bagIsEmpty) {
public function evaluate(Hand $hand, Move $move, Board $board, $bagIsEmpty) {
$score = $board->score($move);
if (count($move->placements()) === count($tiles) && $bagIsEmpty) {
//BAKERT bug here ... we have already removed the non unique tiles
if (count($move->placements()) === $hand->size() && $bagIsEmpty) {
$score += Score::FINISHING_BONUS;
} elseif ($this->enablesQwirkle($move, $board, $bagIsEmpty) && $score < 9) {
$score -= 3.5;
Expand Down

0 comments on commit ba26b81

Please sign in to comment.