Body: <p>I have 2 problems with my chess engine I can't seem to figure out/understand. The first is with easy endgames. I was using <a href="https://www.youtube.com/watch?v=U4ogK0MIzqk&amp;t=1326s&amp;ab_channel=SebastianLague" rel="nofollow noreferrer">this</a> video as a reference (18:30 for the easy endgames), and implemented the same code he did, but adapted to how I designed the engine. Instead of always maximizing the score for both sides, I want to take the maximum score for white and the minimum for black.</p>
  <p>Code for my endgame evaluation:</p>
  <pre><code>def endGameEval(self, endgameWeight: float, isWhite: bool) -&gt; int:
          &quot;&quot;&quot;
          endGameEval gives a higher score to moves that force the enemy 
          king to the backRanks when the game is closer to the end.
          &quot;&quot;&quot;
          eval = 0
          blackKingSquare = ch.square_name(self.pythonBoard.king(ch.BLACK))
          blackKingRank = int(blackKingSquare[1])
          blackKingFile = ord(blackKingSquare[0]) - ord('a') + 1
  
          whiteKingSquare = ch.square_name(self.pythonBoard.king(ch.WHITE))
          whiteKingRank = int(whiteKingSquare[1])
          whiteKingFile = ord(whiteKingSquare[0]) - ord('a') + 1
          if (isWhite == False):
              EnemyDistToCentreFile = max(3 - whiteKingFile, whiteKingFile - 4)
              EnemyDistToCentreRank = max(3 - whiteKingRank, whiteKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          else:
              EnemyDistToCentreFile = max(3 - blackKingFile, blackKingFile - 4)
              EnemyDistToCentreRank = max(3 - blackKingRank, blackKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          eval += EnemyDistFromCentre
  
          fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
          # if (isWhite == False):
          #     eval = -eval
          return int(eval * endgameWeight)
  </code></pre>
  <p>I commented</p>
  <pre><code>if (isWhite == False):
         eval = -eval
  </code></pre>
  <p>out because the engine was making random moves in some endgame positions like this one<a href="https://i.sstatic.net/JCzgBe2C.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/JCzgBe2C.png" alt="enter image description here" /></a>.</p>
  <p>I tried switching up the values to extremes for this part:</p>
  <pre><code>fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
  </code></pre>
  <p>but nothing changed.</p>
  <p>Here's my engine class,</p>
  <pre><code>class Engine():
      &quot;&quot;&quot;
      Class that controls the engine with the moves
      &quot;&quot;&quot;
      def __init__(self, board: dict[str: str], pythonBoard: ch.Board, whitePieces: int, blackPieces: int, transpositions: defaultdict) -&gt; None:
          self.board = board
          self.pythonBoard = pythonBoard
          self.materialValue = 0
          self.move = None
          self.transpositions = transpositions
          self.whitePieces = whitePieces
          self.blackPieces = blackPieces
  
  
      def attackedByPawn(self, start: str, color: str) -&gt; bool:
          &quot;&quot;&quot;
          attackedByPawn detects if a piece at position start with the color color is attacked by a pawn.
          A pinned pawn will count as an attacker.
          &quot;&quot;&quot;
          if (color == &quot;white&quot;):
              left = &quot;&quot;
              right = &quot;&quot;
              # check row
              if (start[1] != '8'):
                  # check column
                  if (start[0] != 'a'):
                      left = chr(ord(start[0]) - 1) + str(int(start[1]) + 1)
                      if (self.board[left] == 'p' and self.pythonBoard.is_pinned(ch.BLACK, ch.parse_square(left)) == False):
                          return True
                  if (start[0] != 'h'):
                      right = chr(ord(start[0]) + 1) + str(int(start[1]) + 1)
                      if (self.board[right] == 'p' and self.pythonBoard.is_pinned(ch.BLACK, ch.parse_square(right)) == False):
                          return True
                  return False
          else:
              left = &quot;&quot;
              right = &quot;&quot;
              # check row
              if (start[1] != '1'):
                  # check column
                  if (start[0] != 'a'):
                      right = chr(ord(start[0]) - 1) + str(int(start[1]) - 1)
                      if (self.board[right] == 'P' and self.pythonBoard.is_pinned(ch.WHITE, ch.parse_square(right)) == False):
                          return True
                  if (start[0] != 'h'):
                      left = chr(ord(start[0]) + 1) + str(int(start[1]) - 1)
                      if (self.board[left] == 'P' and self.pythonBoard.is_pinned(ch.WHITE, ch.parse_square(left)) == False):
                          return True
                  return False
  
      def orderMoves(self, moves: set[str], start: str) -&gt; list[str]:
          &quot;&quot;&quot;
          orderMoves sorts the moves in an order with estimates to how good the
          move is. There are 4 criterias: 
          Move to capture a piece, 
          Move to promote,
          Move that gets a piece attacked by a pawn
          Checkmate
          &quot;&quot;&quot;
          valList = []
          moveName = self.board[start]
          moveColor = findColor(moveName)
          movePiece = Piece(moveName, moveColor, 0, set())
          for move in moves:
              move = move[:2]
              targetName = self.board[move]
              # if there's a piece on the square, check the captured piece
              if (targetName != '0'):
                  targetColor = findColor(targetName)
                  targetPiece = Piece(targetName, targetColor, 0, set())
                  val = abs(targetPiece.value)
                  val = 10 * val - abs(movePiece.value)
              else:
                  val = 0
              self.pythonBoard.push(ch.Move.from_uci(start + move))
              if (self.pythonBoard.is_checkmate):
                  val += 10000000
              self.pythonBoard.pop()
              # check for promotion
              if ((move[1] == '8' and moveName == 'P') or (move[1] == '1' or moveName == 'p')):
                  val += 9
              # check for pawn attacks
              if (self.attackedByPawn(move, moveColor)):
                  val -= movePiece.value
              valList.append(val)
          moves = list(moves)
          combined = list(zip(moves, valList))
          return [move[0] for move in sorted(combined, key=lambda x: x[1], reverse=True)]
  
      def endGameEval(self, endgameWeight: float, isWhite: bool) -&gt; int:
          &quot;&quot;&quot;
          endGameEval gives a higher score to moves that force the enemy 
          king to the backRanks when the game is closer to the end.
          &quot;&quot;&quot;
          eval = 0
          blackKingSquare = ch.square_name(self.pythonBoard.king(ch.BLACK))
          blackKingRank = int(blackKingSquare[1])
          blackKingFile = ord(blackKingSquare[0]) - ord('a') + 1
  
          whiteKingSquare = ch.square_name(self.pythonBoard.king(ch.WHITE))
          whiteKingRank = int(whiteKingSquare[1])
          whiteKingFile = ord(whiteKingSquare[0]) - ord('a') + 1
          if (isWhite == False):
              EnemyDistToCentreFile = max(3 - whiteKingFile, whiteKingFile - 4)
              EnemyDistToCentreRank = max(3 - whiteKingRank, whiteKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          else:
              EnemyDistToCentreFile = max(3 - blackKingFile, blackKingFile - 4)
              EnemyDistToCentreRank = max(3 - blackKingRank, blackKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          eval += EnemyDistFromCentre
  
          fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
          # if (isWhite == False):
          #     eval = -eval
          return int(eval * endgameWeight)
  
      def evaluate(self, isWhite: bool) -&gt; float:
          &quot;&quot;&quot;
          evaluate evaluates the position
          &quot;&quot;&quot;
          materialValue = 0
          squares = list(self.board.keys())
          if (self.pythonBoard.is_stalemate()):
              return 0
          if (self.pythonBoard.outcome() != None):
              if (self.pythonBoard.outcome().winner == True):
                      return float('inf')
              elif (self.pythonBoard.outcome().winner == False):
                  return float('-inf')
          for square in squares:
              # if there's a piece on the square
              if (self.board[square] != '0'):
                  name = self.board[square]
                  if (name.islower()):
                      color = &quot;black&quot;
                  else:
                      color = &quot;white&quot;
                  moves = set()
                  piece = Piece(name, color, 0, moves)
                  materialValue += piece.value * 10
          if (isWhite):
              endGameBonus = self.endGameEval(16 - self.whitePieces, isWhite)
          else:
              endGameBonus = self.endGameEval(16 - self.blackPieces, isWhite)
          return materialValue + endGameBonus
  
      def isCapture(self, square: str) -&gt; bool:
          if (self.board[square] != '0'):
              return True
          else:
              return False
  
      def findCaptureMoves(self, allMoves: list[ch.Move], currPos: str) -&gt; set[str]:
          &quot;&quot;&quot;
          Finds all capture moves of the piece at currPos which is a string representing 
          a square on the board, such as a8 using allMoves which is a list containing 
          every legal move of every piece
          &quot;&quot;&quot;
          legalMoves = set()
          for location in allMoves:
              pos = str(location)[2:4]
              try:
                  # add move if it's legal
                  if (ch.Move.from_uci(currPos + pos) in allMoves and self.board[pos] != '0'):
                      legalMoves.add(pos)
              except ch.InvalidMoveError:
                  pass
          return legalMoves
  
      def searchCaptures(self, whiteTurn: bool, alpha: int, beta: int) -&gt; float:
          &quot;&quot;&quot;
          Search captures looks at sequences of immediate captures to ensure that
          the score given to the position is accurate even after the depth has ran out.
          It inherits alpha and beta from the search function as well as whiteTurn.
          &quot;&quot;&quot;
          evaluation = self.evaluate(whiteTurn)
          squares = list(self.board.keys())
          possibleMoves = {}
          end = False
          if (whiteTurn):
              bestEvaluation = float('-inf')
              alpha = max(alpha, evaluation)
              if (alpha &gt;= beta):
                  return alpha
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(self.findCaptureMoves(self.pythonBoard.legal_moves, square), square)
              whiteSquares = list(possibleMoves.keys())
              for whiteSquare in whiteSquares:
                  for move in possibleMoves[whiteSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(whiteSquare + move))
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      evaluation = self.searchCaptures(not whiteTurn, alpha, beta)
                      if (evaluation &gt; bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      alpha = max(alpha, evaluation)
                      if (beta &lt;= alpha):
                          end = True
                          break
                  if (end):
                      break
          else:
              bestEvaluation = float('inf')
              beta = min(beta, evaluation)
              if (alpha &gt;= beta):
                  return beta
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(self.findCaptureMoves(self.pythonBoard.legal_moves, square), square)
              blackSquares = list(possibleMoves.keys())
              for blackSquare in blackSquares:
                  for move in possibleMoves[blackSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(blackSquare + move))
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      evaluation = self.searchCaptures(not whiteTurn, alpha, beta)
                      if (evaluation &lt; bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      beta = min(beta, evaluation)
                      if (beta &lt;= alpha):
                          end = True
                          break
                  if (end):
                      break
          return evaluation
  
  
      def search(self, depth: int, whiteTurn: bool, alpha: int, beta: int) -&gt; float:
          &quot;&quot;&quot;
          search function will give scores to position after searching with a
          depth of depth.
          &quot;&quot;&quot;
          possibleMoves = {}
          end = False
          squares = list(self.board.keys())
          # end of depth
          if (depth == 0):
              return self.searchCaptures(whiteTurn, alpha, beta)
          # trying to maximize the score
          if (whiteTurn):
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
              bestEvaluation = float('-inf')
              whiteSquares = list(possibleMoves.keys())
              # go through all possible moves of each white piece
              for whiteSquare in whiteSquares:
                  for move in possibleMoves[whiteSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(whiteSquare + move))
                      fenBoard = self.pythonBoard.board_fen()
                      self.board = fenConverter(fenBoard)
  
                      if (self.transpositions[(fenBoard, whiteTurn)][0] != None and self.transpositions[(fenBoard, whiteTurn)][1] &gt; depth):
                          evaluation = self.transpositions[(fenBoard, whiteTurn)][0]
                          if (evaluation &gt; bestEvaluation):
                              bestEvaluation = evaluation
                          self.pythonBoard.pop()
                          self.board = fenConverter(self.pythonBoard.board_fen())
                          alpha = max(alpha, evaluation)
                          if (beta &lt;= alpha):
                              end = True
                              break
                          continue
  
                      evaluation = self.search(depth - 1, not whiteTurn, alpha, beta) # alpha best for white, beta best for black
                      if (self.transpositions[(fenBoard, whiteTurn)][0] == None):
                          self.transpositions[(fenBoard, whiteTurn)] = [evaluation, depth]
                      if (evaluation &gt; bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      alpha = max(alpha, evaluation)
                      # Pruning
                      if (beta &lt;= alpha):
                          end = True
                          break
                  if (end):
                      break
              return bestEvaluation
          # trying to minimize the score
          else:
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].islower()):
                      possibleMoves[square] = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
              bestEvaluation = float('inf')
              blackSquares = list(possibleMoves.keys())
              # go through all possible moves of each black piece
              for blackSquare in blackSquares:
                  for move in possibleMoves[blackSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(blackSquare + move))
                      fenBoard = self.pythonBoard.board_fen()
                      self.board = fenConverter(fenBoard)
                      if (self.pythonBoard.is_checkmate() and depth == DEPTH):
                          self.move = ch.Move.from_uci(blackSquare + move)
                          self.pythonBoard.pop()
                          return float('-inf')
  
                      if (self.transpositions[(fenBoard, whiteTurn)][0] != None and self.transpositions[(fenBoard, whiteTurn)][1] &gt; depth):
                          evaluation = self.transpositions[(fenBoard, whiteTurn)][0]
                          if (evaluation &lt; bestEvaluation):
                              bestEvaluation = evaluation
                              if (depth == DEPTH):
                                  self.move = ch.Move.from_uci(blackSquare + move)
                                  self.materialValue = bestEvaluation
  
                          self.pythonBoard.pop()
                          self.board = fenConverter(self.pythonBoard.board_fen())
                          beta = min(beta, evaluation)
                          if (beta &lt;= alpha):
                              end = True
                              break
                          continue
  
                      evaluation = self.search(depth - 1, not whiteTurn, alpha, beta)
  
                      if (depth == DEPTH):
                          pass
                          # print(self.pythonBoard, '\n', evaluation)
  
                      if (self.transpositions[(fenBoard, whiteTurn)] == None):
                          self.transpositions[(fenBoard, whiteTurn)] = [evaluation, depth]
                      # if we get better score
                      if (evaluation &lt; bestEvaluation):
                          bestEvaluation = evaluation
                          # Choose the move. Only legal moves for the next turn will be at depth DEPTH
                          if (depth == DEPTH):
                              self.move = ch.Move.from_uci(blackSquare + move)
                              self.materialValue = bestEvaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      beta = min(beta, evaluation)
                      if (beta &lt; alpha):
                          end = True
                          break
                  if (end):
                      break
              return bestEvaluation
  
  </code></pre>
  <p>As for the transposition tables, I have tried to run the engine with and without them at depth 5, but the runtime was basically the same. From my understanding, I just need to store the position, which points to the score of the position and what depth is was found at, and if I can search with a higher depth, I update it.</p>
  <p>I'm using the minimax algorithm with alpha beta pruning.</p>
  <p>I will gladly add more information if it's needed.</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2024-07-28T17:46:21.953Z
Comments.Text: Are you sure that the indentation at the end is how it's supposed to be?
Comments.UserDisplayName: Rishav Raj
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2024-07-28T17:51:47.783Z
Comments.Text: Did this fix it?
Comments.UserDisplayName: Deltag0ny
Tags: <engines><endgame><transpositions>
Title: Chess AI: Easy endgames and transposition tables
ThreadText: # Chess AI: Easy endgames and transposition tables
  
  (Asked by: username_0 on 2024-07-28)
  
  I have 2 problems with my chess engine I can't seem to figure out/understand. The first is with easy endgames. I was using [this](https://www.youtube.com/watch?v=U4ogK0MIzqk&t=1326s&ab_channel=SebastianLague) video as a reference (18:30 for the easy endgames), and implemented the same code he did, but adapted to how I designed the engine. Instead of always maximizing the score for both sides, I want to take the maximum score for white and the minimum for black.
  
  Code for my endgame evaluation:
  
  ```
  def endGameEval(self, endgameWeight: float, isWhite: bool) -> int:
          """
          endGameEval gives a higher score to moves that force the enemy 
          king to the backRanks when the game is closer to the end.
          """
          eval = 0
          blackKingSquare = ch.square_name(self.pythonBoard.king(ch.BLACK))
          blackKingRank = int(blackKingSquare[1])
          blackKingFile = ord(blackKingSquare[0]) - ord('a') + 1
  
          whiteKingSquare = ch.square_name(self.pythonBoard.king(ch.WHITE))
          whiteKingRank = int(whiteKingSquare[1])
          whiteKingFile = ord(whiteKingSquare[0]) - ord('a') + 1
          if (isWhite == False):
              EnemyDistToCentreFile = max(3 - whiteKingFile, whiteKingFile - 4)
              EnemyDistToCentreRank = max(3 - whiteKingRank, whiteKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          else:
              EnemyDistToCentreFile = max(3 - blackKingFile, blackKingFile - 4)
              EnemyDistToCentreRank = max(3 - blackKingRank, blackKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          eval += EnemyDistFromCentre
  
          fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
          # if (isWhite == False):
          #     eval = -eval
          return int(eval * endgameWeight)
  
  ```
  
  I commented
  
  ```
  if (isWhite == False):
         eval = -eval
  
  ```
  
  out because the engine was making random moves in some endgame positions like this one[![enter image description here](https://i.sstatic.net/JCzgBe2C.png)](https://i.sstatic.net/JCzgBe2C.png).
  
  I tried switching up the values to extremes for this part:
  
  ```
  fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
  
  ```
  
  but nothing changed.
  
  Here's my engine class,
  
  ```
  class Engine():
      """
      Class that controls the engine with the moves
      """
      def __init__(self, board: dict[str: str], pythonBoard: ch.Board, whitePieces: int, blackPieces: int, transpositions: defaultdict) -> None:
          self.board = board
          self.pythonBoard = pythonBoard
          self.materialValue = 0
          self.move = None
          self.transpositions = transpositions
          self.whitePieces = whitePieces
          self.blackPieces = blackPieces
  
      def attackedByPawn(self, start: str, color: str) -> bool:
          """
          attackedByPawn detects if a piece at position start with the color color is attacked by a pawn.
          A pinned pawn will count as an attacker.
          """
          if (color == "white"):
              left = ""
              right = ""
              # check row
              if (start[1] != '8'):
                  # check column
                  if (start[0] != 'a'):
                      left = chr(ord(start[0]) - 1) + str(int(start[1]) + 1)
                      if (self.board[left] == 'p' and self.pythonBoard.is_pinned(ch.BLACK, ch.parse_square(left)) == False):
                          return True
                  if (start[0] != 'h'):
                      right = chr(ord(start[0]) + 1) + str(int(start[1]) + 1)
                      if (self.board[right] == 'p' and self.pythonBoard.is_pinned(ch.BLACK, ch.parse_square(right)) == False):
                          return True
                  return False
          else:
              left = ""
              right = ""
              # check row
              if (start[1] != '1'):
                  # check column
                  if (start[0] != 'a'):
                      right = chr(ord(start[0]) - 1) + str(int(start[1]) - 1)
                      if (self.board[right] == 'P' and self.pythonBoard.is_pinned(ch.WHITE, ch.parse_square(right)) == False):
                          return True
                  if (start[0] != 'h'):
                      left = chr(ord(start[0]) + 1) + str(int(start[1]) - 1)
                      if (self.board[left] == 'P' and self.pythonBoard.is_pinned(ch.WHITE, ch.parse_square(left)) == False):
                          return True
                  return False
  
      def orderMoves(self, moves: set[str], start: str) -> list[str]:
          """
          orderMoves sorts the moves in an order with estimates to how good the
          move is. There are 4 criterias: 
          Move to capture a piece, 
          Move to promote,
          Move that gets a piece attacked by a pawn
          Checkmate
          """
          valList = []
          moveName = self.board[start]
          moveColor = findColor(moveName)
          movePiece = Piece(moveName, moveColor, 0, set())
          for move in moves:
              move = move[:2]
              targetName = self.board[move]
              # if there's a piece on the square, check the captured piece
              if (targetName != '0'):
                  targetColor = findColor(targetName)
                  targetPiece = Piece(targetName, targetColor, 0, set())
                  val = abs(targetPiece.value)
                  val = 10 * val - abs(movePiece.value)
              else:
                  val = 0
              self.pythonBoard.push(ch.Move.from_uci(start + move))
              if (self.pythonBoard.is_checkmate):
                  val += 10000000
              self.pythonBoard.pop()
              # check for promotion
              if ((move[1] == '8' and moveName == 'P') or (move[1] == '1' or moveName == 'p')):
                  val += 9
              # check for pawn attacks
              if (self.attackedByPawn(move, moveColor)):
                  val -= movePiece.value
              valList.append(val)
          moves = list(moves)
          combined = list(zip(moves, valList))
          return [move[0] for move in sorted(combined, key=lambda x: x[1], reverse=True)]
  
      def endGameEval(self, endgameWeight: float, isWhite: bool) -> int:
          """
          endGameEval gives a higher score to moves that force the enemy 
          king to the backRanks when the game is closer to the end.
          """
          eval = 0
          blackKingSquare = ch.square_name(self.pythonBoard.king(ch.BLACK))
          blackKingRank = int(blackKingSquare[1])
          blackKingFile = ord(blackKingSquare[0]) - ord('a') + 1
  
          whiteKingSquare = ch.square_name(self.pythonBoard.king(ch.WHITE))
          whiteKingRank = int(whiteKingSquare[1])
          whiteKingFile = ord(whiteKingSquare[0]) - ord('a') + 1
          if (isWhite == False):
              EnemyDistToCentreFile = max(3 - whiteKingFile, whiteKingFile - 4)
              EnemyDistToCentreRank = max(3 - whiteKingRank, whiteKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          else:
              EnemyDistToCentreFile = max(3 - blackKingFile, blackKingFile - 4)
              EnemyDistToCentreRank = max(3 - blackKingRank, blackKingRank - 4)
              EnemyDistFromCentre = EnemyDistToCentreFile + EnemyDistToCentreRank
          eval += EnemyDistFromCentre
  
          fileDist = abs(blackKingFile - whiteKingFile)
          rankDist = abs(blackKingRank - whiteKingRank)
          kingDist = fileDist + rankDist
          eval += 14 - kingDist
          # if (isWhite == False):
          #     eval = -eval
          return int(eval * endgameWeight)
  
      def evaluate(self, isWhite: bool) -> float:
          """
          evaluate evaluates the position
          """
          materialValue = 0
          squares = list(self.board.keys())
          if (self.pythonBoard.is_stalemate()):
              return 0
          if (self.pythonBoard.outcome() != None):
              if (self.pythonBoard.outcome().winner == True):
                      return float('inf')
              elif (self.pythonBoard.outcome().winner == False):
                  return float('-inf')
          for square in squares:
              # if there's a piece on the square
              if (self.board[square] != '0'):
                  name = self.board[square]
                  if (name.islower()):
                      color = "black"
                  else:
                      color = "white"
                  moves = set()
                  piece = Piece(name, color, 0, moves)
                  materialValue += piece.value * 10
          if (isWhite):
              endGameBonus = self.endGameEval(16 - self.whitePieces, isWhite)
          else:
              endGameBonus = self.endGameEval(16 - self.blackPieces, isWhite)
          return materialValue + endGameBonus
  
      def isCapture(self, square: str) -> bool:
          if (self.board[square] != '0'):
              return True
          else:
              return False
  
      def findCaptureMoves(self, allMoves: list[ch.Move], currPos: str) -> set[str]:
          """
          Finds all capture moves of the piece at currPos which is a string representing 
          a square on the board, such as a8 using allMoves which is a list containing 
          every legal move of every piece
          """
          legalMoves = set()
          for location in allMoves:
              pos = str(location)[2:4]
              try:
                  # add move if it's legal
                  if (ch.Move.from_uci(currPos + pos) in allMoves and self.board[pos] != '0'):
                      legalMoves.add(pos)
              except ch.InvalidMoveError:
                  pass
          return legalMoves
  
      def searchCaptures(self, whiteTurn: bool, alpha: int, beta: int) -> float:
          """
          Search captures looks at sequences of immediate captures to ensure that
          the score given to the position is accurate even after the depth has ran out.
          It inherits alpha and beta from the search function as well as whiteTurn.
          """
          evaluation = self.evaluate(whiteTurn)
          squares = list(self.board.keys())
          possibleMoves = {}
          end = False
          if (whiteTurn):
              bestEvaluation = float('-inf')
              alpha = max(alpha, evaluation)
              if (alpha >= beta):
                  return alpha
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(self.findCaptureMoves(self.pythonBoard.legal_moves, square), square)
              whiteSquares = list(possibleMoves.keys())
              for whiteSquare in whiteSquares:
                  for move in possibleMoves[whiteSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(whiteSquare + move))
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      evaluation = self.searchCaptures(not whiteTurn, alpha, beta)
                      if (evaluation > bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      alpha = max(alpha, evaluation)
                      if (beta <= alpha):
                          end = True
                          break
                  if (end):
                      break
          else:
              bestEvaluation = float('inf')
              beta = min(beta, evaluation)
              if (alpha >= beta):
                  return beta
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(self.findCaptureMoves(self.pythonBoard.legal_moves, square), square)
              blackSquares = list(possibleMoves.keys())
              for blackSquare in blackSquares:
                  for move in possibleMoves[blackSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(blackSquare + move))
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      evaluation = self.searchCaptures(not whiteTurn, alpha, beta)
                      if (evaluation < bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      beta = min(beta, evaluation)
                      if (beta <= alpha):
                          end = True
                          break
                  if (end):
                      break
          return evaluation
  
      def search(self, depth: int, whiteTurn: bool, alpha: int, beta: int) -> float:
          """
          search function will give scores to position after searching with a
          depth of depth.
          """
          possibleMoves = {}
          end = False
          squares = list(self.board.keys())
          # end of depth
          if (depth == 0):
              return self.searchCaptures(whiteTurn, alpha, beta)
          # trying to maximize the score
          if (whiteTurn):
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].isupper()):
                      possibleMoves[square] = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
              bestEvaluation = float('-inf')
              whiteSquares = list(possibleMoves.keys())
              # go through all possible moves of each white piece
              for whiteSquare in whiteSquares:
                  for move in possibleMoves[whiteSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(whiteSquare + move))
                      fenBoard = self.pythonBoard.board_fen()
                      self.board = fenConverter(fenBoard)
  
                      if (self.transpositions[(fenBoard, whiteTurn)][0] != None and self.transpositions[(fenBoard, whiteTurn)][1] > depth):
                          evaluation = self.transpositions[(fenBoard, whiteTurn)][0]
                          if (evaluation > bestEvaluation):
                              bestEvaluation = evaluation
                          self.pythonBoard.pop()
                          self.board = fenConverter(self.pythonBoard.board_fen())
                          alpha = max(alpha, evaluation)
                          if (beta <= alpha):
                              end = True
                              break
                          continue
  
                      evaluation = self.search(depth - 1, not whiteTurn, alpha, beta) # alpha best for white, beta best for black
                      if (self.transpositions[(fenBoard, whiteTurn)][0] == None):
                          self.transpositions[(fenBoard, whiteTurn)] = [evaluation, depth]
                      if (evaluation > bestEvaluation):
                          bestEvaluation = evaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      alpha = max(alpha, evaluation)
                      # Pruning
                      if (beta <= alpha):
                          end = True
                          break
                  if (end):
                      break
              return bestEvaluation
          # trying to minimize the score
          else:
              for square in squares:
                  if (self.board[square] != '0' and self.board[square].islower()):
                      possibleMoves[square] = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
              bestEvaluation = float('inf')
              blackSquares = list(possibleMoves.keys())
              # go through all possible moves of each black piece
              for blackSquare in blackSquares:
                  for move in possibleMoves[blackSquare]:
                      self.pythonBoard.push(ch.Move.from_uci(blackSquare + move))
                      fenBoard = self.pythonBoard.board_fen()
                      self.board = fenConverter(fenBoard)
                      if (self.pythonBoard.is_checkmate() and depth == DEPTH):
                          self.move = ch.Move.from_uci(blackSquare + move)
                          self.pythonBoard.pop()
                          return float('-inf')
  
                      if (self.transpositions[(fenBoard, whiteTurn)][0] != None and self.transpositions[(fenBoard, whiteTurn)][1] > depth):
                          evaluation = self.transpositions[(fenBoard, whiteTurn)][0]
                          if (evaluation < bestEvaluation):
                              bestEvaluation = evaluation
                              if (depth == DEPTH):
                                  self.move = ch.Move.from_uci(blackSquare + move)
                                  self.materialValue = bestEvaluation
  
                          self.pythonBoard.pop()
                          self.board = fenConverter(self.pythonBoard.board_fen())
                          beta = min(beta, evaluation)
                          if (beta <= alpha):
                              end = True
                              break
                          continue
  
                      evaluation = self.search(depth - 1, not whiteTurn, alpha, beta)
  
                      if (depth == DEPTH):
                          pass
                          # print(self.pythonBoard, '\n', evaluation)
  
                      if (self.transpositions[(fenBoard, whiteTurn)] == None):
                          self.transpositions[(fenBoard, whiteTurn)] = [evaluation, depth]
                      # if we get better score
                      if (evaluation < bestEvaluation):
                          bestEvaluation = evaluation
                          # Choose the move. Only legal moves for the next turn will be at depth DEPTH
                          if (depth == DEPTH):
                              self.move = ch.Move.from_uci(blackSquare + move)
                              self.materialValue = bestEvaluation
                      self.pythonBoard.pop()
                      self.board = fenConverter(self.pythonBoard.board_fen())
                      beta = min(beta, evaluation)
                      if (beta < alpha):
                          end = True
                          break
                  if (end):
                      break
              return bestEvaluation
  
  ```
  
  As for the transposition tables, I have tried to run the engine with and without them at depth 5, but the runtime was basically the same. From my understanding, I just need to store the position, which points to the score of the position and what depth is was found at, and if I can search with a higher depth, I update it.
  
  I'm using the minimax algorithm with alpha beta pruning.
  
  I will gladly add more information if it's needed.
  
  **Tags:** engines, endgame, transpositions
  
  > **Comments:**
  > **username_1**: Are you sure that the indentation at the end is how it's supposed to be?  
  > **username_0**: Did this fix it?  
  
  ---
  
  ## 0 answers
  
  