2017-05-17 7 views
1

내 체스 엔진에 대한 무의식 검색으로 알파 베타 검색을 구현했습니다. 그러나 대부분의 위치에서 내 검색 프로그램에 표시된 것처럼 정지 시간은 총 실행 시간의 80-90 %를 차지합니다. 내 가지 치기에 버그가 있니?체스 : quiescence search dominating runtime

저는 알파 베타 루틴과 정지 루틴을 모두 포함 시켰습니다.

내 정지 검색은 this pseudocode을 직접 기반으로합니다.

// Perform the alpha-beta search. 
func ab(b *dragontoothmg.Board, alpha int16, beta int16, depth int8, halt chan bool, stop *bool) (int16, dragontoothmg.Move) { 
    nodeCount++ 

    if *stop { 
     return alpha, 0 
    } 

    found, tableMove, tableEval, tableDepth, tableNodeType := transtable.Get(b) 
    if found && tableDepth >= depth { 
     if tableNodeType == transtable.Exact { 
      return tableEval, tableMove 
     } else if tableNodeType == transtable.LowerBound { 
      alpha = max(alpha, tableEval) 
     } else { // upperbound 
      beta = min(beta, tableEval) 
     } 
     if alpha >= beta { 
      return tableEval, tableMove 
     } 
    } 
    if depth == 0 { 
     //return eval.Evaluate(b), 0 
     return quiesce(b, alpha, beta, stop), 0 
    } 

    alpha0 := alpha 
    bestVal := int16(negInf) 
    moves := b.GenerateLegalMoves() 
    var bestMove dragontoothmg.Move 
    if len(moves) > 0 { 
     bestMove = moves[0] // randomly pick some move 
    } 
    for _, move := range moves { 
     unapply := b.Apply(move) 
     var score int16 
     score, _ = ab(b, -beta, -alpha, depth-1, halt, stop) 
     score = -score 
     unapply() 
     if score > bestVal { 
      bestMove = move 
      bestVal = score 
     } 
     alpha = max(alpha, score) 
     if alpha >= beta { 
      break 
     } 
    } 

    if *stop { 
     return bestVal, bestMove 
    } 

    var nodeType uint8 
    if bestVal <= alpha0 { 
     nodeType = transtable.UpperBound 
    } else if bestVal >= beta { 
     nodeType = transtable.LowerBound 
    } else { 
     nodeType = transtable.Exact 
    } 
    transtable.Put(b, bestMove, bestVal, depth, nodeType) 
    return bestVal, bestMove 
} 

func quiesce(b *dragontoothmg.Board, alpha int16, beta int16, stop *bool) int16 { 
    nodeCount++ 
    if *stop { 
     return alpha 
    } 
    var standPat int16 
    found, _, evalresult, _, ntype := transtable.Get(b) 
    if found && ntype == transtable.Exact { 
     standPat = evalresult 
    } else { 
     standPat = eval.Evaluate(b) 
     transtable.Put(b, 0, standPat, 0, transtable.Exact) 
    } 
    if standPat >= beta { 
     return beta 
    } 
    if alpha < standPat { 
     alpha = standPat 
    } 
    moves := b.GenerateLegalMoves() 
    if len(moves) == 0 { // TODO(dylhunn): What about stalemate? 
     return negInf 
    } 
    for _, move := range moves { 
     if !isCapture(move, b) { 
      continue 
     } 
     unapply := b.Apply(move) 
     score := -quiesce(b, -beta, -alpha, stop) 
     unapply() 
     if score >= beta { 
      return beta 
     } 
     if score > alpha { 
      alpha = score 
     } 
    } 
    return alpha 
} 

func isCapture(m dragontoothmg.Move, b *dragontoothmg.Board) bool { 
    toBitboard := (uint64(1) << m.To()) 
    return (toBitboard&b.White.All != 0) || (toBitboard&b.Black.All != 0) 
} 

답변

1

코드를 올바르게 읽으면 모든 캡처가 검색됩니다. 당신이 일을 구하기 위해 할 수있는 일은 희망이없는 포로 이동을 잘라내는 것입니다. 움직임이 너무 나빠서 건너 뛰어도 안전하다는 것이 일반적이므로 기술은 매우 안전합니다.

예를 들어,이 위치에서 살펴 :

enter image description here

FEN을 :

  • Rxa7
  • Qxd7 +
  • Rxh7 : rnbqkbnr/pppppppp/8/8/8/8/1PP1PPP1/RNBQKBNR w KQkq - 0 1

    세 캡처 있습니다

엔진이 먼저 여왕과 함께 캡처 이동을 시도한다고 가정 해 봅시다. 블랙은 4 가지 포착 방법을 가지고 있지만, 이러한 움직임들 중 어느 것이 컷오프를 초래할 것입니다.

예를 들어 검은 색은 Bxd7을 재생합니다. 이제 흰색은 결과 위치 인 Rxa7 또는 Rxh7에서 두 개의 캡처를 갖습니다.

여기에서 대부분의 엔진은 폰이 캡처하는 것조차 도움이되지 않는 베타와 비교하여 흰색이 이미 재료로 떨어진 것을 인식합니다. 따라서이 루크 캡처는 둘 다 잘릴 것 같지 않습니다.

여기에서 현재 검색은 여전히 ​​이러한 움직임을 계속 검색합니다. 그러한 경우를 감지하고 이러한 동작을 건너 뛰면 많은 작업을 줄일 수 있습니다.

추가 최적화가 있습니다. 예를 들어, static exchange evaluation의 강력한 엔진은 Qxd7이 하나의 폰을 얻지 만 여왕을 잃어 버리게됩니다. 이것이 나쁜 거래이기 때문에 엔진은 즉시이 움직임을 건너 뛸 수 있습니다. 다른 두 개의 루크 캡처에 대해서도 마찬가지입니다.

항상 그렇듯이 절충안이 있습니다. 너가 너무 적극적으로 치부하면, 너는 결국 좋은 움직임을 치부 할 것이다. 일반적으로 정상 검색에서는 시간을 많이 사용하는 것이 좋습니다. 침입 검색에서는 그렇지 않으므로 적극적인 가지 치기가 좋습니다.