Beispiel #1
0
void GoInfluence::FindInfluence(const GoBoard& board,
                    int nuExpand,
                    int nuShrink,
                    SgBWSet* influence)
{   
    SgBWSet result = SgBWSet(board.All(SG_BLACK), board.All(SG_WHITE));
    SgBWSet next;
    const int size = board.Size();
    for (int i = 1; i <= nuExpand; ++i)
    {
        for (SgBlackWhite c = SG_BLACK; c <= SG_WHITE; ++c)
        {
            SgBlackWhite opp = SgOppBW(c);
            next[c] = result[c].Border(size) - result[opp];
        }
        result[SG_BLACK] |= (next[SG_BLACK] - next[SG_WHITE]);
        result[SG_WHITE] |= (next[SG_WHITE] - next[SG_BLACK]);
    }

    for (int i = 1; i <= nuShrink; ++i)
    {
        result[SG_BLACK] = result[SG_BLACK].Kernel(size);
        result[SG_WHITE] = result[SG_WHITE].Kernel(size);
    }
    
    *influence = result;
}
Beispiel #2
0
void Mesh::DrawPieces(GoBoard &board) {
  glTranslatef(PIECE_OFFSET, BOARD_HEIGHT, PIECE_OFFSET);
  glScalef(PIECE_X_SCALE, PIECE_Y_SCALE, PIECE_Z_SCALE);
  //glTranslatef(0.5f, 0.5f, 0.5f);
  for (int x = 0; x < 9; x++) {
    for (int y = 0; y < 9; y++) {
      char space = board.getPiece(x, y);
      if (space != 0) {
        float color = (space + 1) / 2 + 0.2;
        glColor3f(color, color, color);
        DrawMesh(piece, piece_tri_verts_VBO);
        //glutSolidSphere(0.5, 10, 10);
      }
      glTranslatef(0, 0, BOARD_GRID_SPACING);
    }
    glTranslatef(0, 0, -BOARD_MAX/PIECE_Z_SCALE);
    glTranslatef(BOARD_GRID_SPACING, 0, 0);
  }
  glTranslatef(-BOARD_MAX/PIECE_X_SCALE, 0, 0);

  auto sP = board.getSpeculativePiece();
  //if (board.legalMove(sP.second, sP.first.first, sP.first.second)) {
  glTranslatef(BOARD_GRID_SPACING*sP.first.x, 0, BOARD_GRID_SPACING*sP.first.y);
  float color = (sP.second + 1) / 2 + 0.2;
  glColor3f(color, 0, color);
  //glutSolidSphere(0.5, 10, 10);
  DrawMesh(piece, piece_tri_verts_VBO);
  //}
  //endTranslate();
 
}
void GoBook::Add(const GoBoard& bd, SgPoint move)
{
    if (move != SG_PASS && bd.Occupied(move))
        throw SgException("point is not empty");
    if (! bd.IsLegal(move))
        throw SgException("move is not legal");
    const GoBook::MapEntry* mapEntry = LookupEntry(bd);
    if (mapEntry == 0)
    {
        vector<SgPoint> moves;
        moves.push_back(move);
        GoBoard tempBoard;
        InsertEntry(GetSequence(bd), moves, bd.Size(), tempBoard, 0);
    }
    else
    {
        size_t id = mapEntry->m_id;
        SG_ASSERT(id < m_entries.size());
        Entry& entry = m_entries[id];
        int invRotation = SgPointUtil::InvRotation(mapEntry->m_rotation);
        SgPoint rotMove = SgPointUtil::Rotate(invRotation, move, bd.Size());
        if (! Contains(entry.m_moves, rotMove))
            entry.m_moves.push_back(rotMove);
    }
}
bool GoRegion::ProtectedCuts(const GoBoard& board) const
{
    if (! GetFlag(GO_REGION_CORRIDOR))
        return false;
    if (m_blocks.IsLength(2))
        /* */ return Safe2Cuts(board); /* */ // easy case of only 2 blocks

    bool prot = true;
    SgPointSet allCuts;
    const int size = board.Size();
    GoBlock* block1, *block2;
    for (SgVectorPairIteratorOf<GoBlock> it(m_blocks);
         it.NextPair(block1, block2);)
    {
        SgPointSet lib1(block1->Stones().Border(size));
        SgPointSet lib2(block2->Stones().Border(size));
        SgPointSet cuts(lib1 & lib2 & Points());
        if (! cuts.SubsetOf(board.AllEmpty()))
        // cut occupied by opponent. Bad for us.
            return false;
        else
            allCuts |= cuts;
    }
    // no eye space left ? hard to distinguish false eyes from ok
    // AR why must this be checked??? Should not matter for flat regions.
    // Try to take it out.
    //if (Points().SubsetOf(allCuts | allCuts.Border()))
    //  prot = false;

    return prot;
}
Beispiel #5
0
bool GoEyeUtil::IsVitalPt(const SgPointSet& points, SgPoint p,
                SgBlackWhite opp,
                const GoBoard& bd)
{
    // in corridors a vital point has 2 nbs, in big ones it may have 3 or 4.
    // but: 2 in following
    // example: unsettled nakade, non-flat points are all occupied by opp.
    // .a       a is vital Point.
    //  o.
    //  .
    int numNb = bd.NumEmptyNeighbors(p) + bd.NumNeighbors(p, opp);
    if (numNb >= 2)
    {
        if (numNb >= 4)
            /* */ return true; /* */
        int nu = IsTreeShape(points) ? 2 : 3;
        if (numNb >= nu)
        {
            if (numNb == 2 && bd.IsEmpty(p))
                return IsSplitPt(p, points);
            else
                return true;
        }
    }
    return false;
}
Beispiel #6
0
bool GoEyeUtil::IsSinglePointEye2(const GoBoard& board, SgPoint p, 
                                  SgBlackWhite color, SgVector<SgPoint>& eyes)
{
    // Must be an empty point
    if (! board.IsColor(p, SG_EMPTY))
        return false;
    // All adjacent neighbours must be friendly
    SgBoardColor opp = SgOppBW(color);
    for (SgNb4Iterator adj(p); adj; ++adj)
    {
        SgBoardColor adjColor = board.GetColor(*adj);
        if (adjColor == opp || adjColor == SG_EMPTY)
            return false;
    }
    // All diagonals (with up to one exception) must be friendly or an eye
    int baddiags = 0;
    int maxbaddiags = (board.Line(p) == 1 ? 0 : 1);
    for (SgNb4DiagIterator it(p); it; ++it)
    {
        if (board.IsColor(*it, opp))
            ++baddiags;
        if (board.IsColor(*it, SG_EMPTY) && ! eyes.Contains(*it))
        {
            // Assume this point is an eye and recurse
            eyes.PushBack(p);
            if (! IsSinglePointEye2(board, *it, color, eyes))
                ++baddiags;
            eyes.PopBack();
        }
        if (baddiags > maxbaddiags)
            return false;
    }
    return true;
}
Beispiel #7
0
SgPointSet SpUtil::GetRelevantMoves(GoBoard& bd, SgBlackWhite toPlay,
                                    bool useFilter)
{
    SgPointSet legal;
    for (SgSetIterator it(bd.AllEmpty()); it; ++it)
        if (bd.IsLegal(*it))
            legal.Include(*it);
    if (! useFilter)
        return legal;
    GoSafetySolver safetySolver(bd);
    SgBWSet safe;
    safetySolver.FindSafePoints(&safe);
    SgBlackWhite opp = SgOppBW(toPlay);
    SgPointSet moves;
    const GoRules& rules = bd.Rules();
    const bool captureDead = rules.CaptureDead();
    //const bool japaneseScoring = rules.JapaneseScoring();
    for (SgSetIterator it(legal); it; ++it)
    {
        SgPoint p = *it;
        const bool isSafe = safe[toPlay].Contains(p);
        const bool isSafeOpp = safe[opp].Contains(p);
        const bool hasOppNeighbors = bd.HasNeighbors(p, opp);
        if (  (! isSafe && ! isSafeOpp)
           || (isSafe && captureDead && hasOppNeighbors)
           // || (isSafeOpp && ! japaneseScoring)
           )
            moves.Include(p);
    }
    return moves;
}
GoPlayer::GoPlayer(const GoBoard& bd)
    : GoBoardSynchronizer(bd),
      m_currentNode(0),
      m_bd(bd.Size(), GoSetup(), bd.Rules()),
      m_variant(0)
{
    SetSubscriber(m_bd);
    ClearSearchTraces();
}
Beispiel #9
0
GoBoard* GoBoard::copyBoard() const
{
  GoBoard* nBoard = new GoBoard(Size());

  for(int i = 0; i<movePointer; ++i)
    {
      nBoard->Play(moves[i]->Point);
    }
  return nBoard;
}
int LibertyAveragex10(const GoBoard& board, SgBlackWhite color)
{
    int nuLibs = 0, nuBlocks = 0;
    const int size = board.Size();
    for (SgConnCompIterator it(board.All(color), board.Size()); it; ++it)
    {
        ++nuBlocks;
        nuLibs += ((*it).Border(size) & board.AllEmpty()).Size();
    }
    return (nuBlocks == 0) ? 0 : 10 * nuLibs / nuBlocks;
}
Beispiel #11
0
void GoBook::Entry::ApplyTo(GoBoard& bd) const
{
    if (bd.Size() != m_size)
        bd.Init(m_size);
    GoBoardUtil::UndoAll(bd);
    for (vector<SgPoint>::const_iterator it = m_sequence.begin();
         it != m_sequence.end(); ++it)
    {
        SG_ASSERT(bd.IsLegal(*it));
        bd.Play(*it);
    }
}
Beispiel #12
0
GoSetup GoSetupUtil::CurrentPosSetup(const GoBoard& bd)
{
    GoSetup setup;
    setup.m_player = bd.ToPlay();
    for (GoBoard::Iterator it2(bd); it2; ++it2)
    {
        SgPoint p = *it2;
        if (bd.Occupied(p))
            setup.m_stones[bd.GetColor(p)].Include(p);
    }
    return setup;
}
bool GoRegion::Safe2Cuts(const GoBoard& board) const
{
    SG_ASSERT(m_blocks.IsLength(2));
    const int size = board.Size();
    GoBlock* block1 = m_blocks.Front();
    GoBlock* block2 = m_blocks.Back();
    SgPointSet cuts(Points());
    cuts -= board.AllEmpty();
    if (cuts.IsEmpty())
        /* */ return true; /* */
    cuts &= block1->Stones().Border(size);
    cuts &= block2->Stones().Border(size);
    return cuts.IsEmpty();
}
Beispiel #14
0
bool GoEyeUtil::CanBecomeSinglePointEye (const GoBoard& board, SgPoint p, 
                              const SgPointSet& oppSafe)
{
    SG_ASSERT(! oppSafe[p]);

    for (SgNb4Iterator it(p); it; ++it)
    {
        if (oppSafe[*it])
            return false;
    }

    int nu = 0;
    for (SgNb4DiagIterator dit(p); dit; ++dit)
    {
        if (oppSafe[*dit])
        {
            if (board.Line(p) == 1)
                return false;
            ++nu;
            if (nu > 1)
                return false;
        }
    }
    
    return true;
}
Beispiel #15
0
int GoEyeUtil::CountSinglePointEyes2(const GoBoard& board, SgPoint p)
{
    if (! board.Occupied(p))
        return 0;

    SgBlackWhite color = board.GetColor(p);
    int numeyes = 0;

    for (GoBoard::LibertyIterator lib(board, p); lib; ++lib)
    {
        if (IsSinglePointEye2(board, *lib, color))
            numeyes++;
    }
    
    return numeyes;
}
Beispiel #16
0
vector<SgPoint> GoBook::LookupAllMoves(const GoBoard& bd) const
{
    vector<SgPoint> result;
    const GoBook::MapEntry* mapEntry = LookupEntry(bd);
    if (mapEntry == 0)
        return result;
    size_t id = mapEntry->m_id;
    SG_ASSERT(id < m_entries.size());
    const vector<SgPoint>& moves = m_entries[id].m_moves;
    const int rotation = mapEntry->m_rotation;
    const int size = mapEntry->m_size;
    for (vector<SgPoint>::const_iterator it = moves.begin();
         it != moves.end(); ++it)
    {
        SgPoint p = SgPointUtil::Rotate(rotation, *it, size);
        if (! bd.IsLegal(p))
        {
            // Should not happen with 64-bit hashes, but not impossible
            SgWarning() << "illegal book move (hash code collision?)\n";
            result.clear();
            break;
        }
        result.push_back(p);
    }
    return result;
}
Beispiel #17
0
bool GoEyeUtil::NumberOfMoveToEye(const GoBoard& board, SgBlackWhite color,
                                  SgPoint p, int& number)
{
    SG_ASSERT(board.IsEmpty(p));
    SgBlackWhite att = SgOppBW(color);  // attacker

    if ( board.Line(p) == 1) // corner or edge
    {
        if ( board.Num8Neighbors(p, att) > 0 )
            return false;
        else
        {
            number = board.Num8EmptyNeighbors(p);
            return true;
        }
    }
    else // in center
    {
        if ( board.Num8Neighbors(p, att) >= 2 )
            return false;
        else if ( board.NumNeighbors(p, att) > 0 )
            return false;
        else // only 0 or 1 attacker point and not in NB4
        {
            number = board.Num8EmptyNeighbors(p);
            return true;
        }
    }
    
}
SgPoint GoGtpCommandUtil::StoneArg(const GtpCommand& cmd, std::size_t number,
                                   const GoBoard& board)
{
    SgPoint point = PointArg(cmd, number, board);
    if (board.GetColor(point) == SG_EMPTY)
        throw GtpFailure() << "point " << SgWritePoint(point)
                           << " must be occupied";
    return point;
}
Beispiel #19
0
GoLadderStatus GoLadderUtil::LadderStatus(const GoBoard& bd, SgPoint prey,
                                          bool twoLibIsEscape,
                                          SgPoint* toCapture,
                                          SgPoint* toEscape)
{
    SG_ASSERT(bd.IsValidPoint(prey));
    SG_ASSERT(bd.Occupied(prey));
#ifndef NDEBUG
    SgHashCode oldHash = bd.GetHashCode();
#endif
    // Unsettled only if can capture when hunter plays first, and can escape
    // if prey plays first.
    GoLadder ladder;
    SgBlackWhite preyColor = bd.GetStone(prey);
    SgVector<SgPoint> captureSequence;
    GoLadderStatus status = GO_LADDER_ESCAPED;
    if (ladder.Ladder(bd, prey, SgOppBW(preyColor), &captureSequence,
                      twoLibIsEscape) < 0)
    {
        SgVector<SgPoint> escapeSequence;
        if (ladder.Ladder(bd, prey, preyColor, &escapeSequence,
                          twoLibIsEscape) < 0)
            status = GO_LADDER_CAPTURED;
        else
        {
            status = GO_LADDER_UNSETTLED;
            // Unsettled = ladder depends on who plays first, so there must
            // be a move that can be played.
            SG_ASSERT(captureSequence.NonEmpty());
            // escapeSequence can be empty in 2 libs, prey to play case
            SG_ASSERT(twoLibIsEscape || escapeSequence.NonEmpty());
            if (toCapture)
                *toCapture = captureSequence.Front();
            if (toEscape)
                *toEscape = escapeSequence.IsEmpty() ? SG_PASS :
                                                       escapeSequence.Front();
        }
    }
#ifndef NDEBUG
    // Make sure Ladder didn't change the board position.
    SG_ASSERT(oldHash == bd.GetHashCode());
#endif
    return status;
}
Beispiel #20
0
bool GoEyeUtil::IsSinglePointEye(const GoBoard& bd, SgPoint p,
                                 SgBlackWhite color)
{
    SG_ASSERT(bd.IsEmpty(p));
    const SgBlackWhite opp = SgOppBW(color);
    if (bd.HasEmptyNeighbors(p) || bd.HasNeighbors(p, opp))
        return false;
    if (bd.Line(p) == 1)
        return ! (bd.HasDiagonals(p, SG_EMPTY) || bd.HasDiagonals(p, opp));
    return (bd.NumDiagonals(p, SG_EMPTY) + bd.NumDiagonals(p, opp)) <= 1;
}
Beispiel #21
0
bool GoLadderUtil::IsProtectedLiberty(const GoBoard& bd1, SgPoint liberty,
                                      SgBlackWhite col, bool& byLadder,
                                      bool& isKoCut, bool tryLadder)
{
    byLadder = false;
    isKoCut = false;
    GoModBoard mbd(bd1);
    GoBoard& bd = mbd.Board();

    const SgBlackWhite toPlay = bd1.ToPlay();
    bd.SetToPlay(SgOppBW(col));
    bool isProtected;
    if (! PlayIfLegal(bd, liberty))
        isProtected = bd.LastMoveInfo(GO_MOVEFLAG_SUICIDE);
        // opponent cannot play there
    else
    {
        if (bd.LastMoveInfo(GO_MOVEFLAG_SUICIDE))
           isProtected = true;
        else
        {
            if (bd.InAtari(liberty))
            {
                if (bd.NumStones(liberty) > 1)
                    isProtected = true;
                else
                {
                    SgPoint p = bd.TheLiberty(liberty);
                    if (PlayIfLegal(bd, p))
                    {
                        isProtected =    (bd.NumStones(p) != 1)
                                      || (bd.NumLiberties(p) != 1);
                                      // yes, can re-capture there
                        bd.Undo();
                    }
                    else
                        isProtected = false;

                    if (! isProtected)
                        isKoCut = true;
                }
            }
            else if (tryLadder)
            {
                isProtected = Ladder(bd, liberty, bd.ToPlay(), true);
                if (isProtected)
                    byLadder = true;
            }
            else // don't try ladder
                isProtected = false;
        }
        bd.Undo();
    }
    bd.SetToPlay(toPlay);
    return isProtected;
}
Beispiel #22
0
SgPoint GoUctUtil::GenForcedOpeningMove(const GoBoard& bd)
{
    int sz = bd.Size();
    if (sz < 13 || bd.TotalNumStones(SG_BLACK) > 5
        || bd.TotalNumStones(SG_WHITE) > 5)
        return SG_NULLMOVE;
    SgSList<SgPoint,4> moves;
    if (IsRectEmpty(bd, 1, 5, 1, 5))
        moves.PushBack(Pt(4, 4));
    if (IsRectEmpty(bd, 1, 5, sz - 4, sz))
        moves.PushBack(Pt(4, sz - 3));
    if (IsRectEmpty(bd, sz - 4, sz, 1, 5))
        moves.PushBack(Pt(sz - 3, 4));
    if (IsRectEmpty(bd, sz - 4, sz, sz - 4, sz))
        moves.PushBack(Pt(sz - 3, sz - 3));
    if (moves.IsEmpty())
        return SG_NULLMOVE;
    return moves[SgRandom::Global().Int(moves.Length())];
}
void GoGtpCommandUtil::ParseMultiStoneArgument(GtpCommand& cmd,
                                               const GoBoard& board,
                                               SgBlackWhite& toPlay,
                                               SgBlackWhite& defender,
                                               SgVector<SgPoint>& crucial)
{
    toPlay = GoGtpCommandUtil::BlackWhiteArg(cmd, 0);
    SgDebug() << "Set " << SgBW(toPlay) << " to play\n";
    SgPoint point = GoGtpCommandUtil::StoneArg(cmd, 1, board);
    defender = board.GetColor(point);
    SG_ASSERT(defender == SG_BLACK || defender == SG_WHITE);
    crucial.PushBack(point);
    for (size_t i = 2; i < cmd.NuArg(); ++i)
    {
        SgPoint p = GoGtpCommandUtil::StoneArg(cmd, i, board);
        if (board.GetColor(p) != defender)
            throw GtpFailure("Crucial stones must be same color");
        else
            crucial.PushBack(p);
    }
}
void GoUctFeatures::
FindMoveFeaturesUI(const GoBoard& bd,
                   GoUctPlayoutPolicy<GoBoard>& policy,
                   SgPoint move,
                   FeMoveFeatures& features)
{
    SG_ASSERT(move != SG_PASS);
    if (! bd.IsLegal(move))
        return;
    FeFullBoardFeatures f(bd);
    FindAllFeatures(bd, policy, f);
    features = f.Features()[move];
}
Beispiel #25
0
bool GoLadderUtil::Ladder(const GoBoard& bd, SgPoint prey,
                          SgBlackWhite toPlay, bool twoLibIsEscape,
                          SgVector<SgPoint>* sequence)
{
    SG_ASSERT(bd.IsValidPoint(prey));
    SG_ASSERT(bd.Occupied(prey));
    // @todo for an unsettled block with 2 liberties, it
    // immediately says it can escape, but does not return a move.
    // Sequence is empty.  Have to special case this and look for
    // moves that escape from ladder myself.
#ifndef NDEBUG
    SgHashCode oldHash = bd.GetHashCode();
#endif
    GoLadder ladder;
    int result = ladder.Ladder(bd, prey, toPlay, sequence, twoLibIsEscape);
#ifndef NDEBUG
    // Make sure Ladder didn't change the board position.
    SG_ASSERT(oldHash == bd.GetHashCode());
#endif
    SG_ASSERT(result != 0);
    return (result < 0);
}
Beispiel #26
0
void GoBook::Delete(const GoBoard& bd, SgPoint move)
{
    const GoBook::MapEntry* mapEntry = LookupEntry(bd);
    if (mapEntry == 0)
        return;
    size_t id = mapEntry->m_id;
    SG_ASSERT(id < m_entries.size());
    Entry& entry = m_entries[id];
    int invRotation = SgPointUtil::InvRotation(mapEntry->m_rotation);
    SgPoint rotMove = SgPointUtil::Rotate(invRotation, move, bd.Size());
    if (! Erase(entry.m_moves, rotMove))
        throw SgException("GoBook::Delete: move not found");
}
GoRegionBoard::GoRegionBoard(const GoBoard& board)
    : m_board(board),
      m_region(SgPointArray<GoRegion*>(0)),
      m_block(0),
      m_invalid(true),
      m_computedHealthy(false),
      m_boardSize(board.Size())
{
    m_code.Invalidate();
    m_chainsCode.Invalidate();
    GenBlocksRegions();
    ++s_alloc;
}
Beispiel #28
0
bool GoEyeUtil::CheckInterior(const GoBoard& bd, const SgPointSet& area,
                   SgBlackWhite opp, bool checkBlocks)
{
    bool hasSingleNbPoint = false;
    int nuPoints = 0;
    for (SgSetIterator it(area); it; ++it)
    {
        const SgPoint p(*it);
        if (bd.IsEmpty(p))
        {
            int nuNbs = 0;
            if (area.Contains(p + SG_NS))
                ++nuNbs;
            if (area.Contains(p - SG_NS))
                ++nuNbs;
            if (area.Contains(p + SG_WE))
                ++nuNbs;
            if (area.Contains(p - SG_WE))
                ++nuNbs;
            if (nuNbs == 1)
                hasSingleNbPoint = true;
            else if (nuNbs > 2)
                return false;
        }
        else if (p == bd.Anchor(p))
        {
            if (bd.GetColor(p) != opp)
            // if own stones on inside: not a tree shape.
                return false;
            int nuLibs = bd.NumLiberties(p);
            if (nuLibs == 1)
                hasSingleNbPoint = true;
            else if (checkBlocks && nuLibs > 2)
                return false;
        }
        ++nuPoints;
    }
    return nuPoints == 1 || hasSingleNbPoint;
}
Beispiel #29
0
void GoLadderUtil::FindLadderEscapeMoves(const GoBoard& bd, SgPoint prey, 
                           SgVector<SgPoint>& escapeMoves)
{
    SG_ASSERT(bd.NumLiberties(prey) == 1);
    SG_ASSERT(escapeMoves.IsEmpty());
    
    SgPoint p = bd.TheLiberty(prey);
    SgVector<SgPoint> candidates;
    candidates.PushBack(p);
    if (IsLadderEscapeMove(bd, prey, p))
    	escapeMoves.PushBack(p);
    for (GoAdjBlockIterator<GoBoard> it(bd, prey, 1); it; ++it)
    {
        // check if prey can escape by capturing *it on p.
        SgPoint p = bd.TheLiberty(*it);
        if (! candidates.Contains(p))
        {
		    candidates.PushBack(p);
        	if (IsLadderEscapeMove(bd, prey, p))
            	escapeMoves.PushBack(p);
        }
    }
}                           
Beispiel #30
0
SgPoint GoLadderUtil::TryLadder(const GoBoard& bd, SgPoint prey,
                                SgBlackWhite firstPlayer)
{
    SgVector<SgPoint> sequence;
    bool isCaptured = Ladder(bd, prey, firstPlayer, true, &sequence);
    // if move is same color as prey, we want to escape
    // else we want to capture.
    SgPoint p;
    if (isCaptured != (firstPlayer == bd.GetStone(prey)))
        p = sequence.IsEmpty() ? SG_PASS : sequence.Front();
    else
        p = SG_NULLMOVE;
    return p;
}