void Player::create_children_simple(const Board & board, Node * node){ assert(node->children.empty()); node->children.alloc(board.movesremain(), ctmem); Node * child = node->children.begin(), * end = node->children.end(); Board::MoveIterator moveit = board.moveit(prunesymmetry); int nummoves = 0; for(; !moveit.done() && child != end; ++moveit, ++child){ *child = Node(*moveit); nummoves++; } if(prunesymmetry) node->children.shrink(nummoves); //shrink the node to ignore the extra moves else //both end conditions should happen in parallel assert(moveit.done() && child == end); PLUS(nodes, node->children.num()); }
GTPResponse GTP::gtp_patterns(vecstr args) { bool symmetric = true; bool invert = true; std::string ret; const Board & board = *hist; for(Board::MoveIterator move = board.moveit(); !move.done(); ++move) { ret += move->to_s() + " "; unsigned int p = board.pattern(*move); if(symmetric) p = board.pattern_symmetry(p); if(invert && board.toplay() == Side::P2) p = board.pattern_invert(p); ret += to_str(p); ret += "\n"; } return GTPResponse(true, ret); }
GTPResponse HavannahGTP::gtp_patterns(vecstr args){ bool symmetric = true; bool invert = true; string ret; Board board = game.getboard(); for(Board::MoveIterator move = board.moveit(); !move.done(); ++move){ ret += move->to_s() + " "; unsigned int p = board.pattern(*move); if(symmetric) p = board.pattern_symmetry(p); if(invert && board.toplay() == 2) p = board.pattern_invert(p); ret += to_str(p); ret += "\n"; } return GTPResponse(true, ret); }
bool SolverPNS::pns(const Board & board, PNSNode * node, int depth, uint32_t tp, uint32_t td){ iters++; if(maxdepth < depth) maxdepth = depth; if(node->children.empty()){ if(ctmem.memalloced() >= memlimit) return false; int numnodes = board.movesremain(); nodes += node->alloc(numnodes, ctmem); if(lbdist) dists.run(&board); int i = 0; for(Board::MoveIterator move = board.moveit(true); !move.done(); ++move){ int outcome, pd; if(ab){ Board next = board; next.move(*move, false, false); pd = 0; outcome = (ab == 1 ? solve1ply(next, pd) : solve2ply(next, pd)); nodes_seen += pd; }else{ outcome = board.test_win(*move); pd = 1; } if(lbdist && outcome < 0) pd = dists.get(*move); node->children[i] = PNSNode(*move).outcome(outcome, board.toplay(), ties, pd); i++; } node->children.shrink(i); //if symmetry, there may be extra moves to ignore nodes_seen += i; updatePDnum(node); return true; } bool mem; do{ PNSNode * child = node->children.begin(), * child2 = node->children.begin(), * childend = node->children.end(); uint32_t tpc, tdc; if(df){ for(PNSNode * i = node->children.begin(); i != childend; i++){ if(i->delta <= child->delta){ child2 = child; child = i; }else if(i->delta < child2->delta){ child2 = i; } } tpc = min(INF32/2, (td + child->phi - node->delta)); tdc = min(tp, (uint32_t)(child2->delta*(1.0 + epsilon) + 1)); }else{ tpc = tdc = 0; while(child->delta != node->phi) child++; } Board next = board; next.move(child->move, false, false); uint64_t itersbefore = iters; mem = pns(next, child, depth + 1, tpc, tdc); child->work += iters - itersbefore; if(child->phi == 0 || child->delta == 0) //clear child's children nodes -= child->dealloc(ctmem); if(updatePDnum(node) && !df) break; }while(!timeout && mem && (!df || (node->phi < tp && node->delta < td))); return mem; }
bool SolverPNS2::SolverThread::pns(const Board & board, PNSNode * node, int depth, uint32_t tp, uint32_t td){ iters++; if(solver->maxdepth < depth) solver->maxdepth = depth; if(node->children.empty()){ if(node->terminal()) return true; if(solver->ctmem.memalloced() >= solver->memlimit) return false; if(!node->children.lock()) return false; int numnodes = board.movesremain(); CompactTree<PNSNode>::Children temp; temp.alloc(numnodes, solver->ctmem); PLUS(solver->nodes, numnodes); if(solver->lbdist) dists.run(&board); int i = 0; for(Board::MoveIterator move = board.moveit(true); !move.done(); ++move){ int outcome, pd; if(solver->ab){ Board next = board; next.move(*move); pd = 0; outcome = (solver->ab == 1 ? solve1ply(next, pd) : solve2ply(next, pd)); PLUS(solver->nodes_seen, pd); }else{ outcome = board.test_win(*move); pd = 1; } if(solver->lbdist && outcome < 0) pd = dists.get(*move); temp[i] = PNSNode(*move).outcome(outcome, board.toplay(), solver->ties, pd); i++; } temp.shrink(i); //if symmetry, there may be extra moves to ignore node->children.swap(temp); assert(temp.unlock()); PLUS(solver->nodes_seen, i); updatePDnum(node); return true; } bool mem; do{ PNSNode * child = node->children.begin(), * child2 = node->children.begin(), * childend = node->children.end(); uint32_t tpc, tdc; if(solver->df){ for(PNSNode * i = node->children.begin(); i != childend; i++){ if(i->refdelta() <= child->refdelta()){ child2 = child; child = i; }else if(i->refdelta() < child2->refdelta()){ child2 = i; } } tpc = min(INF32/2, (td + child->phi - node->delta)); tdc = min(tp, (uint32_t)(child2->delta*(1.0 + solver->epsilon) + 1)); }else{ tpc = tdc = 0; for(PNSNode * i = node->children.begin(); i != childend; i++) if(child->refdelta() > i->refdelta()) child = i; } Board next = board; next.move(child->move); child->ref(); uint64_t itersbefore = iters; mem = pns(next, child, depth + 1, tpc, tdc); child->deref(); PLUS(child->work, iters - itersbefore); if(updatePDnum(node) && !solver->df) break; }while(!solver->timeout && mem && (!solver->df || (node->phi < tp && node->delta < td))); return mem; }
GTPResponse GTP::gtp_all_legal(vecstr args) { std::string ret; for(Board::MoveIterator move = hist->moveit(); !move.done(); ++move) ret += move->to_s() + " "; return GTPResponse(true, ret); }
PairMove Player::PlayerUCT::rollout_choose_move(Board & board, const Move & prev, int & doinstwin, bool checkrings){ //look for instant wins if(player->instantwin == 1 && --doinstwin >= 0){ for(Board::MoveIterator m = board.moveit(); !m.done(); ++m) if(board.test_win(*m, board.toplay(), checkrings) > 0) return *m; } //look for instant wins and forced replies if(player->instantwin == 2 && --doinstwin >= 0){ Move loss = M_UNKNOWN; for(Board::MoveIterator m = board.moveit(); !m.done(); ++m){ if(board.test_win(*m, board.toplay(), checkrings) > 0) //win return *m; if(board.test_win(*m, 3 - board.toplay(), checkrings) > 0) //lose loss = *m; } if(loss != M_UNKNOWN) return loss; } if(player->instantwin >= 3 && --doinstwin >= 0){ Move start, cur, loss = M_UNKNOWN; int turn = 3 - board.toplay(); if(player->instantwin == 4){ //must have an edge or corner connection, or it has nothing to offer a group towards a win, ignores rings const Board::Cell * c = board.cell(prev); if(c->numcorners() == 0 && c->numedges() == 0) goto skipinstwin3; } // logerr(board.to_s(true)); //find the first empty cell int dir = -1; for(int i = 0; i <= 5; i++){ start = prev + neighbours[i]; if(!board.onboard(start) || board.get(start) != turn){ dir = (i + 5) % 6; break; } } if(dir == -1) //possible if it's in the middle of a ring, which is possible if rings are being ignored goto skipinstwin3; cur = start; // logerr(prev.to_s() + ":"); //follow contour of the current group looking for wins do{ // logerr(" " + to_str((int)cur.y) + "," + to_str((int)cur.x)); //check the current cell if(board.onboard(cur) && board.get(cur) == 0 && board.test_win(cur, turn, checkrings) > 0){ // logerr(" loss"); if(loss == M_UNKNOWN) loss = cur; else if(loss != cur) return PairMove(loss, cur); //game over, two wins found for opponent } //advance to the next cell for(int i = 5; i <= 9; i++){ int nd = (dir + i) % 6; Move next = cur + neighbours[nd]; if(!board.onboard(next) || board.get(next) != turn){ cur = next; dir = nd; break; } } }while(cur != start); //potentially skips part of it when the start is in a pocket, rare bug // logerr("\n"); if(loss != M_UNKNOWN) return loss; } skipinstwin3: //force a bridge reply if(player->rolloutpattern){ Move move = rollout_pattern(board, prev); if(move != M_UNKNOWN) return move; } //reuse the last good reply if(player->lastgoodreply && prev != M_SWAP){ Move move = goodreply[board.toplay()-1][board.xy(prev)]; if(move != M_UNKNOWN && board.valid_move_fast(move)) return move; } return M_UNKNOWN; }
//play a random game starting from a board state, and return the results of who won int Player::PlayerUCT::rollout(Board & board, Move move, int depth){ int won; int num = board.movesremain(); bool wrand = (player->weightedrandom); if(wrand){ wtree[0].resize(board.vecsize()); wtree[1].resize(board.vecsize()); int set = 0; for(Board::MoveIterator m = board.moveit(false, false); !m.done(); ++m){ int i = board.xy(*m); moves[i] = *m; unsigned int p = board.pattern(i); wtree[0].set_weight_fast(i, player->gammas[p]); wtree[1].set_weight_fast(i, player->gammas[board.pattern_invert(p)]); set++; } wtree[0].rebuild_tree(); wtree[1].rebuild_tree(); }else{ int i = 0; for(Board::MoveIterator m = board.moveit(false, false); !m.done(); ++m) moves[i++] = *m; i = num; while(i > 1){ int j = rand32() % i--; Move tmp = moves[j]; moves[j] = moves[i]; moves[i] = tmp; } // random_shuffle(moves, moves + num); } int doinstwin = player->instwindepth; if(doinstwin < 0) doinstwin *= - board.get_size(); bool checkrings = (unitrand() < player->checkrings); //only check rings to the specified depth int checkdepth = (int)player->checkringdepth; //if it's negative, check for that fraction of the remaining moves if(player->checkringdepth < 0) checkdepth = (int)ceil(num * player->checkringdepth * -1); //only allow rings bigger than the minimum ring size, incrementing by the ringincr after each move int minringsize = (int)player->minringsize; int ringcounterfull = (int)player->ringincr; //if it's negative, scale by the fraction of remaining moves if(player->ringincr < 0) ringcounterfull = (int)ceil(num * player->ringincr * -1); int ringcounter = ringcounterfull; int ringperm = player->ringperm; Move * nextmove = moves; Move forced = M_UNKNOWN; while((won = board.won()) < 0){ int turn = board.toplay(); if(forced == M_UNKNOWN){ //do a complex choice PairMove pair = rollout_choose_move(board, move, doinstwin, checkrings); move = pair.a; forced = pair.b; //or the simple random choice if complex found nothing if(move == M_UNKNOWN){ do{ if(wrand){ int j = wtree[turn-1].choose(); // assert(j >= 0); wtree[0].set_weight(j, 0); wtree[1].set_weight(j, 0); move = moves[j]; }else{ move = *nextmove; nextmove++; } }while(!board.valid_move_fast(move)); } }else{ move = forced; forced = M_UNKNOWN; } movelist.addrollout(move, turn); board.move(move, true, false, (checkrings ? minringsize : 0), ringperm); if(--ringcounter == 0){ minringsize++; ringcounter = ringcounterfull; } depth++; checkrings &= (depth < checkdepth); if(wrand){ //update neighbour weights for(const MoveValid * i = board.nb_begin(move), *e = board.nb_end(i); i < e; i++){ if(i->onboard() && board.get(i->xy) == 0){ unsigned int p = board.pattern(i->xy); wtree[0].set_weight(i->xy, player->gammas[p]); wtree[1].set_weight(i->xy, player->gammas[board.pattern_invert(p)]); } } } } gamelen.add(depth); if(won > 0) wintypes[won-1][(int)board.getwintype()].add(depth); //update the last good reply table if(player->lastgoodreply && won > 0){ MoveList::RaveMove * rave = movelist.begin(), *raveend = movelist.end(); int m = -1; while(rave != raveend){ if(m >= 0){ if(rave->player == won && *rave != M_SWAP) goodreply[rave->player - 1][m] = *rave; else if(player->lastgoodreply == 2) goodreply[rave->player - 1][m] = M_UNKNOWN; } m = board.xy(*rave); ++rave; } } movelist.finishrollout(won); return won; }
bool Player::PlayerUCT::create_children(Board & board, Node * node, int toplay){ if(!node->children.lock()) return false; if(player->dists || player->detectdraw){ dists.run(&board, (player->dists > 0), (player->detectdraw ? 0 : toplay)); if(player->detectdraw){ // assert(node->outcome == -3); node->outcome = dists.isdraw(); //could be winnable by only one side if(node->outcome == 0){ //proven draw, neither side can influence the outcome node->bestmove = *(board.moveit()); //just choose the first move since all are equal at this point node->children.unlock(); return true; } } } CompactTree<Node>::Children temp; temp.alloc(board.movesremain(), player->ctmem); int losses = 0; Node * child = temp.begin(), * end = temp.end(), * loss = NULL; Board::MoveIterator move = board.moveit(player->prunesymmetry); int nummoves = 0; for(; !move.done() && child != end; ++move, ++child){ *child = Node(*move); if(player->minimax){ child->outcome = board.test_win(*move); if(player->minimax >= 2 && board.test_win(*move, 3 - board.toplay()) > 0){ losses++; loss = child; } if(child->outcome == toplay){ //proven win from here, don't need children node->outcome = child->outcome; node->proofdepth = 1; node->bestmove = *move; node->children.unlock(); temp.dealloc(player->ctmem); return true; } } if(player->knowledge) add_knowledge(board, node, child); nummoves++; } if(player->prunesymmetry) temp.shrink(nummoves); //shrink the node to ignore the extra moves else //both end conditions should happen in parallel assert(move.done() && child == end); //Make a macro move, add experience to the move so the current simulation continues past this move if(losses == 1){ Node macro = *loss; temp.dealloc(player->ctmem); temp.alloc(1, player->ctmem); macro.exp.addwins(player->visitexpand); *(temp.begin()) = macro; }else if(losses >= 2){ //proven loss, but at least try to block one of them node->outcome = 3 - toplay; node->proofdepth = 2; node->bestmove = loss->move; node->children.unlock(); temp.dealloc(player->ctmem); return true; } if(player->dynwiden > 0) //sort in decreasing order by knowledge sort(temp.begin(), temp.end(), sort_node_know); PLUS(player->nodes, temp.num()); node->children.swap(temp); assert(temp.unlock()); return true; }
GTPResponse HavannahGTP::gtp_all_legal(vecstr args){ string ret; for(Board::MoveIterator move = game.getboard().moveit(); !move.done(); ++move) ret += move_str(*move) + " "; return GTPResponse(true, ret); }