示例#1
0
class Bot(LiacBot):
	name = 'Bot'

	def __init__(self, depth):
		super(Bot, self).__init__()
		self.transpositionTable = TranspositionTable()
		self._depth = depth
		self.last_move = None
		
	# Retorna board de maior value da lista
	def __maxTake(self, moves):
		(move, board) = moves.head()
		maxi = (move, board, board.value)
		
		for (m, board) in moves:
			val = board.value
			if val > maxi[2]:
				maxi = (m, board, val)
		newMoves = moves.remove(maxi[0:2])
		return (maxi, newMoves)		
	
	def __sortMoves(self, moves):
		for _ in range(5):
			(maxi, newMoves) = self.__maxTake(moves)
		for (_, board) in moves:
			val = board.value
	
	def __negaScout(self, board, depth, alpha, beta):
		if board.value == POS_INF: # WIN
			return (None, POS_INF)
		if board.value == NEG_INF: # LOSE
			return (None, NEG_INF)
		if depth == 0:
			return (None, board.value)
		
		# busca na tabela de transposicao
		ttEntry = self.transpositionTable.look_up(board.string, depth)
		if ttEntry != None:
			move, value = ttEntry
			return (move, value)
		
		moves = board.generate()
		if moves == []: # DRAW
			return (None, 0)
		sorted(moves, key=itemgetter(1), reverse=True)
		
		firstChild = True
		for moveValue in moves:
			(move, _) = moveValue
			board.move(move)
			if firstChild:
				firstChild = False
				bestValue = -(self.__negaScout(board, depth - 1, -beta, -alpha)[1])
				bestMove = move
			else:
				score = -(self.__negaScout(board, depth - 1, -alpha -1, -alpha)[1])
				if alpha < score and score < beta:
					score = -(self.__negaScout(board, depth - 1, -beta, -alpha)[1])
				if bestValue < score:
					bestValue = score
					bestMove = move
			board.unmove(move)
			alpha = max(alpha, bestValue)
			if alpha >= beta:
				break
				
		# armazena na tabela de transposicao
		self.transpositionTable.store(board.string, depth, bestMove, bestValue)
		
		return (bestMove, bestValue)
		
	# gera o proximo movimento a partir da poda alfa e beta
	def __alphaBeta(self, board, depth, alpha, beta):
		if board.value == POS_INF: # WIN
			return (None, POS_INF)
		if board.value == NEG_INF: # LOSE
			return (None, NEG_INF)
		if depth == 0:
			return (None, board.value)
		
		# busca na tabela de transposicao
		ttEntry = self.transpositionTable.look_up(board.string, depth)
		if ttEntry != None:
			move, value = ttEntry
			return move, value
		
		moves = board.generate()
		if moves == []: # DRAW
			return (None, 0)
		moves = sorted(moves, key=itemgetter(1), reverse=True)
		
		bestValue = NEG_INF
		(bestMove, _) = moves[0]
		for moveValue in moves:
			(move, _) = moveValue
			
			board.move(move)
			val = -(self.__alphaBeta(board, depth - 1, -beta, -alpha)[1])
			board.unmove(move)
			
			if bestValue < val:
				bestValue = val
				bestMove = move
			alpha = max(alpha, val)
			if alpha >= beta:
				break
		
		# armazena na tabela de transposicao
		self.transpositionTable.store(board.string, depth, bestMove, bestValue)
		
		return (bestMove, bestValue)

	def on_move(self, state):
		print 'Generating a move...\n',
		board = Board(state)

		if state['bad_move']:
			print state['board']
			raw_input()

		t0 = time.time()
		move, value = self.__negaScout(board, self._depth, NEG_INF, POS_INF)
		t = time.time()
		print 'Time:', t - t0

		self.last_move = move
		print move, ' value: ', value
		self.send_move(move[0], move[1])
		self.color = state["who_moves"]

	def on_game_over(self, state):
		if state['draw']:
			print 'Draw!'
		elif state['winner'] == self.color:
			print 'We won!'
		else:
			print 'We lost!'
示例#2
0
class Bot(LiacBot):
    name = 'BotTESTE'

    def __init__(self, depth):
        super(Bot, self).__init__()
        self.transpositionTable = TranspositionTable()
        self._depth = depth
        self.last_move = None

    # Retorna board de maior value da lista
    def __maxTake(self, moves):
        (move, board) = moves.head()
        maxi = (move, board, board.value)

        for (m, board) in moves:
            val = board.value
            if val > maxi[2]:
                maxi = (m, board, val)
        newMoves = moves.remove(maxi[0:2])
        return (maxi, newMoves)

    def __sortMoves(self, moves):
        for _ in range(5):
            (maxi, newMoves) = self.__maxTake(moves)
        for (_, board) in moves:
            val = board.value

    def __negaScout(self, board, depth, alpha, beta):
        if board.value == POS_INF:  # WIN
            return (None, POS_INF)
        if board.value == NEG_INF:  # LOSE
            return (None, NEG_INF)
        if depth == 0:
            return (None, board.value)

        # busca na tabela de transposicao
        ttEntry = self.transpositionTable.look_up(board.string, depth)
        if ttEntry != None:
            move, value = ttEntry
            return (move, value)

        moves = board.generate()
        if moves == []:  # DRAW
            return (None, 0)
        sorted(moves, key=itemgetter(1), reverse=False)

        firstChild = True
        for moveValue in moves:
            (move, _) = moveValue
            board.move(move)
            if firstChild:
                firstChild = False
                bestValue = -(self.__negaScout(board, depth - 1, -beta,
                                               -alpha)[1])
                bestMove = move
            else:
                score = -(self.__negaScout(board, depth - 1, -alpha - 1,
                                           -alpha)[1])
                if alpha < score and score < beta:
                    score = -(self.__negaScout(board, depth - 1, -beta,
                                               -alpha)[1])
                if bestValue < score:
                    bestValue = score
                    bestMove = move
            board.unmove(move)
            alpha = max(alpha, bestValue)
            if alpha >= beta:
                break

        # armazena na tabela de transposicao
        self.transpositionTable.store(board.string, depth, bestMove, bestValue)

        return (bestMove, bestValue)

    # gera o proximo movimento a partir da poda alfa e beta
    def __alphaBeta(self, board, depth, alpha, beta):
        if board.value == POS_INF:  # WIN
            return (None, POS_INF)
        if board.value == NEG_INF:  # LOSE
            return (None, NEG_INF)
        if depth == 0:
            return (None, board.value)

        # busca na tabela de transposicao
        ttEntry = self.transpositionTable.look_up(board.string, depth)
        if ttEntry != None:
            move, value = ttEntry
            return move, value

        moves = board.generate()
        if moves == []:  # DRAW
            return (None, 0)
        moves = sorted(moves, key=itemgetter(1), reverse=True)

        bestValue = NEG_INF
        (bestMove, _) = moves[0]
        for moveValue in moves:
            (move, _) = moveValue

            board.move(move)
            val = -(self.__alphaBeta(board, depth - 1, -beta, -alpha)[1])
            board.unmove(move)

            if bestValue < val:
                bestValue = val
                bestMove = move
            alpha = max(alpha, val)
            if alpha >= beta:
                break

        # armazena na tabela de transposicao
        self.transpositionTable.store(board.string, depth, bestMove, bestValue)

        return (bestMove, bestValue)

    def on_move(self, state):
        print 'Generating a move...\n',
        board = Board(state)

        if state['bad_move']:
            print state['board']
            raw_input()

        t0 = time.time()
        move, value = self.__negaScout(board, self._depth, NEG_INF, POS_INF)
        t = time.time()
        print 'Time:', t - t0

        self.last_move = move
        print move, ' value: ', value
        self.send_move(move[0], move[1])
        self.color = state["who_moves"]

    def on_game_over(self, state):
        if state['draw']:
            print 'Draw!'
        elif state['winner'] == self.color:
            print 'We won!'
        else:
            print 'We lost!'
示例#3
0
class Evaluator():
    def __init__(self,
                 max_iterations=config.MAX_ITER_MTD,
                 max_search_depth=config.MAX_DEPTH,
                 max_score=config.MAX_SCORE,
                 evaluator="negamax"):
        self.max_iterations = max_iterations
        self.max_search_depth = max_search_depth
        self.max_score = max_score
        self.tt = TranspositionTable()

        self.square_values = {
            # Pawn
            1:
            np.array([
                0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 10, -20, -20, 10, 10, 5, 5, -5,
                -10, 0, 0, -10, -5, 5, 0, 0, 0, 20, 20, 0, 0, 0, 5, 5, 10, 25,
                25, 10, 5, 5, 10, 10, 20, 30, 30, 20, 10, 10, 50, 50, 50, 50,
                50, 50, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0
            ]),
            # Knight
            2:
            np.array([
                -50, -40, -30, -30, -30, -30, -40, -50, -40, -20, 0, 5, 5, 0,
                -20, -40, -30, 5, 10, 15, 15, 10, 5, -30, -30, 0, 15, 20, 20,
                15, 0, -30, -30, 5, 15, 20, 20, 15, 5, -30, -30, 0, 10, 15, 15,
                10, 0, -30, -40, -20, 0, 0, 0, 0, -20, -40, -50, -40, -30, -30,
                -30, -30, -40, -50
            ]),
            # Bishop
            3:
            np.array([
                -20, -10, -10, -10, -10, -10, -10, -20, -10, 5, 0, 0, 0, 0, 5,
                -10, -10, 10, 10, 10, 10, 10, 10, -10, -10, 0, 10, 10, 10, 10,
                0, -10, -10, 5, 5, 10, 10, 5, 5, -10, -10, 0, 5, 10, 10, 5, 0,
                -10, -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -10, -10, -10,
                -10, -20
            ]),
            # Rook
            4:
            np.array([
                0, 0, 0, 5, 5, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0,
                0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0,
                -5, -5, 0, 0, 0, 0, 0, 0, -5, 5, 10, 10, 10, 10, 10, 10, 5, 0,
                0, 0, 0, 0, 0, 0, 0
            ]),
            # Queen
            5:
            np.array([
                -20, -10, -10, -5, -5, -10, -10, -20, -10, 0, 5, 0, 0, 0, 0,
                -10, -10, 5, 5, 5, 5, 5, 0, -10, 0, 0, 5, 5, 5, 5, 0, -5, -5,
                0, 5, 5, 5, 5, 0, -5, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0,
                0, 0, 0, -10, -20, -10, -10, -5, -5, -10, -10, -20
            ]),
            # King
            6:
            np.array([
                20, 30, 10, 0, 0, 10, 30, 20, 20, 20, 0, 0, 0, 0, 20, 20, -10,
                -20, -20, -20, -20, -20, -20, -10, -20, -30, -30, -40, -40,
                -30, -30, -20, -30, -40, -40, -50, -50, -40, -40, -30, -30,
                -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50,
                -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30
            ]),
            # King End Game
            7:
            np.array([
                -50, -30, -30, -30, -30, -30, -30, -50, -30, -30, 0, 0, 0, 0,
                -30, -30, -30, -10, 20, 30, 30, 20, -10, -30, -30, -10, 30, 40,
                40, 30, -10, -30, -30, -10, 30, 40, 40, 30, -10, -30, -30, -10,
                20, 30, 30, 20, -10, -30, -30, -20, -10, 0, 0, -10, -20, -30,
                -50, -40, -30, -20, -20, -30, -40, -50
            ])
        }

    # Negamax with alpha beta pruning, transposition table, move ordering and iterative deepening.
    def negamax(self, board, depth, alpha, beta, move, evaluator):
        old_alpha = alpha
        # Past moves.
        moves = []
        # Get player number according to the library.
        player = 1 if board.turn else -1
        if depth == 0 or board.is_game_over():
            # entry.score = evaluator(board, entry.result)
            moves.append(move)
            if evaluator == "negamax":
                return moves, self.negamax_evaluate(board) * player, board
            else:
                return moves, self.nn_evaluate(board), board
        # Transposition Table Entry
        ttEntry = self.tt.lookup(board)

        # Transposition Table usage.
        if ttEntry and ttEntry.depth >= depth:
            if ttEntry.flag == 0:
                if not move:
                    move = ttEntry.move
                moves.append(move)
                return moves, ttEntry.value, board
            elif ttEntry.flag == -1:
                alpha = max(alpha, ttEntry.value)
            elif ttEntry.flag == 1:
                beta = min(beta, ttEntry.value)

            # alpha-beta cut-off.
            if alpha >= beta:
                if not move:
                    move = ttEntry.move
                moves.append(move)
                return moves, ttEntry.value, board

        value = float("-inf")
        best_move = None
        legal_moves = board.legal_moves

        # For each legal move in the current board state.
        for i in legal_moves:
            # Push the current move to the board.
            board.push(i)

            # Generate new moves and a value (recursive).
            new_moves, current_value, b = self.negamax(board, depth - 1, -beta,
                                                       -alpha, i, evaluator)
            current_value = -current_value

            # Pop so the current displayed board does not change.
            board.pop()

            # Get the maximum value and set best move.
            if current_value > value:
                value = current_value
                moves = new_moves
                best_move = i

            # Redefine alpha.
            alpha = max(alpha, value)

            # alpha-beta cut-off.
            if alpha >= beta:
                break

        # If there is no best move, get current move.
        if not best_move:
            best_move = move

        if value <= old_alpha:
            flag = 1
        elif value >= beta:
            flag = -1
        else:
            flag = 0

        # Store the current board state with other parameters in the transposition table.
        self.tt.store(board, value, flag, depth, best_move)
        moves.append(best_move)
        return moves, value, board

    def _mtd(self, board, depth, firstGuess):
        guess = firstGuess
        finalBoard = None
        upperBound = self.max_score
        lowerBound = -self.max_score
        i = 0

        while lowerBound < upperBound and i < self.max_iterations:
            if guess == lowerBound:
                gamma = guess + 1
            else:
                gamma = guess
            move, guess, finalBoard = self.negamax(board, depth, gamma - 1,
                                                   gamma, None, "neural")
            if guess < gamma:
                upperBound = guess
            else:
                lowerBound = guess
                i = i + 1
        return move, guess, finalBoard

    # MTDf
    def selectMove(self, board):
        guess1 = 1 << 64
        guess2 = 1 << 64
        finalBoard1 = None
        finalBoard2 = None

        for depth in range(2, self.max_search_depth + 1):
            if depth % 2 == 0:
                move, guess1, finalBoard1 = self._mtd(board, depth, guess1)
            else:
                move, guess2, finalBoard2 = self._mtd(board, depth, guess2)

        if self.max_search_depth % 2 == 0:
            return (move, guess1, finalBoard1)
        else:
            return (move, guess2, finalBoard2)

    def clearTranspositionTable(self):
        self.tt = TranspositionTable()

    # Normal Evaluation Function
    def negamax_evaluate(self, board):
        piece_values = {
            chess.PAWN: 1,
            chess.KNIGHT: 3,
            chess.BISHOP: 3,
            chess.ROOK: 5,
            chess.QUEEN: 9,
            chess.KING: 100
        }

        white_value, black_value = 0, 0
        for p in range(1, 6):
            # Get white pieces.
            white_piece = board.pieces(p, True)
            # Get black pieces.
            black_piece = board.pieces(p, False)
            whites, blacks = 0, 0
            # Get the square value for that piece.
            piece_sq_values = self.square_values[p]
            # Get the piece value.
            piece_value = piece_values[p] * 100

            if len(white_piece) > 0:
                # Get the points from the squares with the existing pieces on that square for white.
                whites = piece_sq_values[np.array(list(white_piece))]

            if len(black_piece) > 0:
                # Get the points from the squares with the existing pieces on that square for black.
                blacks = piece_sq_values[-(np.array(list(black_piece)) + 1)]

            # Get white values
            white_value += (np.sum(whites) + len(white_piece) * piece_value)
            # Get black values
            black_value -= (np.sum(blacks) + len(black_piece) * piece_value)

        # Get final value
        final_value = white_value + black_value
        value = final_value / 3500

        # Set the value -1.0, 1.0 or 0.
        value = 1.0 if value > 1.0 else -1.0 if value < -1.0 else value
        return value

    # Neural Network Evaluation Function
    def nn_evaluate(self, board):
        pawnScore = config.PAWN_SCORE
        materialPnts = [
            pawnScore, 4 * pawnScore,
            int(round(4.1 * pawnScore)), 6 * pawnScore, 12 * pawnScore, 0
        ]
        phasePoints = [0, 1, 1, 2, 4, 0]
        points = 0

        if board.is_game_over():
            if board.result() == "1-0":
                points = 100
            if board.result() == "1/2-1/2":
                points = 0
            else:
                points = -100
            if not board.turn:
                points = -points
            return points

        materialPoints = initialPoints = endPoints = 0
        phase = totalPhase = 24

        for i in range(0, 64):
            piece = board.piece_at(i)
            if piece:
                phase -= phasePoints[piece.piece_type - 1]
                piece_value = materialPnts[piece.piece_type - 1]
                initial = weights.initPosPnts[piece.piece_type - 1]
                final = weights.finalPosPnts[piece.piece_type - 1]

                if piece.color:
                    materialPoints += piece_value
                    initialPoints += initial[i]
                    endPoints += final[i]
                else:
                    materialPoints -= piece_value
                    initialPoints -= initial[63 - i]
                    endPoints -= final[63 - i]

        phase = (phase * 256 + (totalPhase / 2)) / totalPhase
        points = materialPoints + \
            ((initialPoints * (256 - phase)) + (endPoints * phase)) / 256

        if (board.turn == False):
            points = -points
        return points

    def findFeatures(self, board, color):
        phasePoints = [0, 1, 1, 2, 4, 0]
        featuresRawInit = [[0 for i in range(0, 64)] for j in range(0, 6)]
        featuresRawFin = [[0 for i in range(0, 64)] for j in range(0, 6)]
        featuresInit = []
        featuresFin = []
        colorType = 1 if color else -1
        phase = totalPhase = 24

        for i in range(0, 64):
            piece = board.piece_at(i)
            if piece:
                phase -= phasePoints[piece.piece_type - 1]

        phase = (phase * 256 + (totalPhase / 2)) / totalPhase

        for i in range(0, 64):
            piece = board.piece_at(i)
            if piece:
                initial_features = featuresRawInit[piece.piece_type - 1]
                final_features = featuresRawFin[piece.piece_type - 1]

                if piece.color:
                    initial_features[i] += (256.0 - phase) / 256.0 * colorType
                    final_features[i] += phase / 256.0 * colorType
                else:
                    initial_features[63 -
                                     i] -= (256.0 - phase) / 256.0 * colorType
                    final_features[63 - i] -= phase / 256.0 * colorType

        for j in range(6):
            for i in range(64):
                featuresInit.append(featuresRawInit[j][i])
                featuresFin.append(featuresRawFin[j][i])

        return (featuresInit, featuresFin)
示例#4
0
class Bot(LiacBot):
    name = 'Terminator'

    def __init__(self, depth):
        super(Bot, self).__init__()
        self.transpositionTable = TranspositionTable()
        self._depth = depth
        self.last_move = None
        self.color = 0
        self.transposition_hits = 0
        self.nodes_explored = 0
        self.cuts = 0
        self.fails = 0

    # Retorna board de maior value da lista
    def _maxTake(self, moves):
        (move, board) = moves.head()
        maxi = (move, board, board.value)

        for (m, board) in moves:
            val = board.value
            if val > maxi[2]:
                maxi = (m, board, val)
        newMoves = moves.remove(maxi[0:2])
        return (maxi, newMoves)

    def _sortMoves(self, moves):
        for _ in range(5):
            (maxi, newMoves) = self._maxTake(moves)
        for (_, board) in moves:
            val = board.value

    def _nega_scout(self, board, depth, alpha, beta, color, scout=False):
        self.nodes_explored += 1
        if board.value == POS_INF:  # WIN
            self.cuts += 1
            return None, POS_INF * color
        if board.value == NEG_INF:  # LOSE
            self.cuts += 1
            return None, NEG_INF * color
        if depth == 0:
            return None, board.value * color

        # busca na tabela de transposicao
        tt_entry = self.transpositionTable.look_up((board.string, color), depth)
        if None != tt_entry:
            tt_move, tt_value = tt_entry
            self.transposition_hits += 1
            return tt_move, tt_value

        moves = board.generate(color)
        if not moves:  # DRAW
            self.cuts += 1
            return None, 0

        moves = sorted(moves, key=itemgetter(1), reverse=(color==WHITE))

        first_child = True
        for moveValue in moves:
            move, value = moveValue
            board.move(move, color, value)
            if first_child:
                first_child = False
                best_value = -(self._nega_scout(board, depth - 1, -beta, -alpha, -color, scout)[1])
                best_move = move
            else:
                score = -(self._nega_scout(board, depth - 1, -alpha - 1, -alpha, -color, True)[1])
                if alpha < score < beta:
                    self.fails += 1
                    score = -(self._nega_scout(board, depth - 1, -beta, -alpha, -color, scout)[1])
                if best_value < score:
                    best_value = score
                    best_move = move
            board.unmove(move, color)
            alpha = max(alpha, best_value)
            if alpha >= beta:
                self.cuts += 1
                break
            
        # armazena na tabela de transposicao se nao for o scout
        if not scout:
            self.transpositionTable.store((board.string, color), depth, best_move, best_value)

        return best_move, best_value

    # gera o proximo movimento a partir da poda alfa e beta
    def _alpha_beta(self, board, depth, alpha, beta, color):
        self.nodes_explored += 1
        if board.value == POS_INF:  # WIN
            self.cuts += 1
            return None, POS_INF * color
        if board.value == NEG_INF:  # LOSE
            self.cuts += 1
            return None, NEG_INF * color
        if depth == 0:
            return None, board.value * color

        # busca na tabela de transposicao
        tt_entry = self.transpositionTable.look_up((board.string, color), depth)
        if None != tt_entry:
            tt_move, tt_value = tt_entry
            self.transposition_hits += 1
            return tt_move, tt_value

        moves = board.generate(color)
        if not moves:  # DRAW
            self.cuts += 1
            return None, 0

        moves = sorted(moves, key=itemgetter(1), reverse=(color==WHITE))

        best_move, best_value = moves[0], NEG_INF
        for moveValue in moves:
            move, _ = moveValue

            board.move(move, color)
            val = -(self._alpha_beta(board, depth - 1, -beta, -alpha, -color)[1])
            board.unmove(move, color)

            if best_value < val:
                best_value = val
                best_move = move
            alpha = max(alpha, val)
            if alpha >= beta:
                self.cuts += 1
                break

        # armazena na tabela de transposicao
        self.transpositionTable.store((board.string, color), depth, best_move, best_value * color)

        return best_move, best_value

    def on_move(self, state):
        self.color = state["who_moves"]
        print '--------------------------------------'
        print 'Talk to the hand...\n',
        board = Board(state)

        if state['bad_move']:
            print state['board']
            raw_input()

        self.transposition_hits = 0
        self.nodes_explored = 0
        self.cuts = 0
        t0 = time.time()
        move, value = self._nega_scout(board, self._depth, NEG_INF, POS_INF, self.color)
        #value *= self.color
        t = time.time()
        print 'Time:', t - t0
        print 'TT Size: ', len(self.transpositionTable._table)
        print 'TT Hits: ', self.transposition_hits
        print 'Nodes Explored: ', self.nodes_explored
        print 'Cuts: ', self.cuts
        print 'Scout Fails: ', self.fails
        self.last_move = move
        print move, ' value: ', value
        self.send_move(move[0], move[1])

    def on_game_over(self, state):
        if state['draw']:
            print 'No problemo.'
        elif state['winner'] == self.color:
            print 'You have been terminated.'
        else:
            print 'I\'ll be back.'
示例#5
0
class ChessEngine:
    def __init__(self, fen=None, cache_size=1e7):
        self.p_encoder = Position()
        self.board = chess.Board(fen) if fen else chess.Board()
        self.ttable = TranspositionTable(cache_size)
        self.MOVE_VAL = []
        self.X = 0

    def reset(self, fen=None):
        self.board = chess.Board()

    def get_results(self):
        res = self.board.result()
        if res == '0-1':
            return -1.0
        elif res == '1-0':
            return 1.0
        return 0.0

    def nn_eval(self):
        # return random.uniform(-1.0, 1.0)
        return model.predict(
            np.asarray([self.p_encoder.binary_encode(self.board)]))[0, 0]

    def conventional_eval(self):

        # TODO enable 3fold rep/50 move rule by improving client-server interaction
        white_val, black_val = 0, 0
        for piece in range(1, 7):
            white_piece = self.board.pieces(piece, True)
            back_piece = self.board.pieces(piece, False)
            white_arr = piece_square_tables[piece][np.array(
                list(white_piece))] if len(white_piece) > 0 else 0
            black_arr = piece_square_tables[piece][-(
                np.array(list(back_piece)) + 1)] if len(back_piece) > 0 else 0
            white_val += (np.sum(white_arr) +
                          len(white_piece) * piece_values[piece])
            black_val -= (np.sum(black_arr) +
                          len(back_piece) * piece_values[piece])

        val = (white_val + black_val) / 3500  #normalize between -1, 1
        if val > 1.0:
            val = 1.0
        elif val < -1.0:
            val = -1.0
        return val

    def move(self, san):
        self.board.push_san(san)

    def _move(self, san):
        self.board.push(san)

    def reset_X(self):
        self.X = 0

    def clear_move_list(self):
        self.MOVE_VAL.clear()

    def take_back(self):
        self.board.pop()
        if not self.board.turn:
            self.board.pop()

    def move_order(self, root, depth):
        move_order = []
        node = root
        turn = self.board.turn
        for i in range(depth):
            if len(node.children) == 0:
                return move_order
            # node = sorted(node.children, key = lambda x: x.val, reverse = turn)[0]
            # o(n) time instead of sorting
            nodes = np.array([[n.val, n] for n in node.children])
            best = nodes[np.argmax(nodes[:, 0])] if turn else nodes[np.argmin(
                nodes[:, 0])]
            move_order.append((round(best[0], 4), best[1].name))
            turn = not turn
            node = best[1]
        return move_order

    def computer_move(self, depth=4, verbose='n', depth_to_sort=1, nn=False):
        if 't' in verbose:
            root = Node('root', depth=0, val=None, no=0)
            self.min_max_tree_alpa_beta_nodes2(-1.0, 1.0, depth, 1, depth,
                                               root)
            move_order = self.move_order(root, depth)
            print(move_order)
            if verbose == 't+':
                for row in RenderTree(
                        root,
                        childiter=lambda x: sorted(
                            x, key=lambda y: y.val, reverse=self.board.turn)):
                    print("%s%d %s %.3f" %
                          (row.pre, row.node.no, row.node.name, row.node.val))
            # print(RenderTree(root))
            # UniqueDotExporter(root, indent=4, nodeattrfunc=lambda node:
            # 'label="%s. %s = %.3f"' % (str(node.no), node.name, node.val)).to_picture("udo.pdf")
            if len(self.MOVE_VAL) == 1:
                val = self.MOVE_VAL[0]
                self.clear_move_list()
                data = {
                    'move': val[0],
                    'eval': val[1],
                    'moveOrder': move_order,
                    'num_pred': self.X
                }
                self.reset_X()
                self.move(val[0])
                return data
            return None
        else:
            moves, val = self.iterative_deepening_cache(
                depth, depth_to_sort, nn)
            if moves:
                print(moves)
                data = {
                    'move': self.board.san(moves[-1]),
                    'eval': -val,
                    'num_pred': self.X
                }
                self.reset_X()
                self._move(moves[-1])
                return data
            return None

    def attacked_by_inferior_piece(self, move, sqr):
        m = self.board.san(move)
        for square in self.board.attackers(not self.board.turn, sqr):
            piece = self.board.piece_type_at(square)
            if m[0] in 'BN' and piece == chess.PAWN:
                return True
            elif m[0] == 'R' and (piece == chess.PAWN or piece == chess.BISHOP
                                  or piece == chess.KNIGHT):
                return True
            elif m[0] == 'Q' and (piece == chess.PAWN or piece == chess.BISHOP
                                  or piece == chess.KNIGHT
                                  or piece == chess.ROOK):
                return True
        return False

    def num_defenders_to_square(self, move):
        return len(self.board.attackers(self.board.turn, move.to_square))

    def num_attackers_to_square(self, move):
        return len(self.board.attackers(not self.board.turn, move.to_square))

    def sort_moves(self):
        legals = []
        post_smart = 0
        for move in self.board.legal_moves:
            m = self.board.san(move)
            # checks if move is checkmate
            if m[-1] == '#':
                return [move]
            # check capture moves
            if 'x' in m:
                # checks if move is a pawn capture, probably a good move
                if m[0].islower():
                    legals.insert(0, move)
                    post_smart += 1
                    continue
                # takes undefended piece, probably a good move
                elif not self.board.is_attacked_by(not self.board.turn,
                                                   move.to_square):
                    legals.insert(0, move)
                    post_smart += 1
                    continue
                else:
                    legals.insert(post_smart, move)
                    continue

            # checks bad queen moves
            if m[0] == 'Q':
                # check if queen going to square controlled by other player, probably a bad move
                if self.board.is_attacked_by(not self.board.turn,
                                             move.to_square):
                    legals.append(move)
                    continue
                else:
                    legals.insert(post_smart, move)
                    continue

            # check if current piece being moved is being attacked by a piece inferior to it and if so,
            # is it being moved to a place where an inferior piece will attack it
            elif self.attacked_by_inferior_piece(move, move.from_square):
                # piece moves to square where different inferior piece is attacking it probably bad move
                if self.attacked_by_inferior_piece(move, move.to_square):
                    legals.append(move)
                    continue
                else:
                    # check square that piece is going to is defended more than attacked - probably a good move
                    if self.num_defenders_to_square(
                            move) >= self.num_attackers_to_square(move):
                        legals.insert(0, move)
                        post_smart += 1
                        continue
                    # if more attackers than defenders, probably a bad move
                    else:
                        legals.append(move)
                        continue
            # piece moves to square where inferior piece is attacking it probably bad move
            elif self.attacked_by_inferior_piece(move, move.to_square):
                legals.append(move)
                continue

            # TODO check bad moves of other pieces
            legals.insert(post_smart, move)

        return legals

    def store_in_table(self, val, flag, depth_, move):
        return self.ttable.store(self.board, val, flag, depth_, move)

    def get_from_table(self):
        res = self.ttable[self.board]
        return res if res else None

    def empty_table(self):
        self.ttable.empty_cache()

    def iterative_deepening_cache(self, depth, depth_to_sort, nn=False):
        moves, val_ = self.negamax_cache(-1.0, 1.0, 1, None, 1, depth_to_sort,
                                         None, nn)
        for i in range(2, depth + 1):
            moves, val_ = self.negamax_cache(-1.0, 1.0, i, None, i,
                                             depth_to_sort, moves, nn)
        return moves, val_

    def iterative_deepening(self, depth, depth_to_sort):
        moves, val_ = self.negamax_it_deep(-1.0, 1.0, 1, None, 1,
                                           depth_to_sort, None)
        for i in range(2, depth + 1):
            moves, val_ = self.negamax_it_deep(-1.0, 1.0, i, None, i,
                                               depth_to_sort, moves)
        return moves, val_

    def negamax_cache(self, alpha, beta, depth, move, original_depth,
                      depth_to_sort, prev_moves, nn):
        self.X += 1
        move_set = []
        alpha_orig = alpha
        color = 1 if self.board.turn else -1
        if self.board.is_game_over(claim_draw=False):
            move_set.append(move)
            return move_set, self.get_results() * color

        cached = self.get_from_table()
        if cached and cached.entry_depth >= depth:
            if cached.flag == EXACT:
                move = cached.move if not move else move
                move_set.append(move)
                return move_set, cached.val
            elif cached.flag == LOWER:
                alpha = max(alpha, cached.val)
            elif cached.flag == UPPER:
                beta = min(beta, cached.val)
            if alpha >= beta:
                move = cached.move if not move else move
                move_set.append(move)
                return move_set, cached.val

        if depth == 0:
            move_set.append(move)
            val = self.nn_eval() if nn else self.conventional_eval()
            return move_set, val * color
        best_move = None
        max_val = -1.0

        if original_depth - depth < depth_to_sort:
            legals = self.sort_moves()
        else:
            legals = list(self.board.legal_moves)

        if prev_moves and len(prev_moves) >= depth and prev_moves[depth -
                                                                  1] in legals:
            legals.insert(0, prev_moves[depth - 1])

        for m in legals:
            self.board.push(m)
            new_move_set, pred_eval = self.negamax_cache(
                -beta, -alpha, depth - 1, m, original_depth, depth_to_sort,
                prev_moves, nn)
            pred_eval = -pred_eval
            self.board.pop()
            if pred_eval > max_val:
                move_set = new_move_set
                max_val, best_move = pred_eval, m

            alpha = max(max_val, alpha)
            if alpha >= beta:
                break
        if max_val <= alpha_orig:
            flag = UPPER
        elif max_val >= beta:
            flag = LOWER
        else:
            flag = EXACT

        if not best_move: best_move = m
        self.store_in_table(max_val, flag, depth, best_move)
        move_set.append(best_move)
        return move_set, max_val

    def negamax_it_deep(self, alpha, beta, depth, move, original_depth,
                        depth_to_sort, prev_moves):
        self.X += 1
        move_set = []
        color = 1 if self.board.turn else -1
        if self.board.is_game_over(claim_draw=False):
            move_set.append(move)
            return move_set, self.get_results() * color
        if depth == 0:
            move_set.append(move)
            return move_set, self.conventional_eval() * color
        best_move = None
        max_val = -1.0

        if original_depth - depth < depth_to_sort:
            legals = self.sort_moves()
        else:
            legals = list(self.board.legal_moves)

        if prev_moves and len(prev_moves) >= depth and prev_moves[depth -
                                                                  1] in legals:
            legals.insert(0, prev_moves[depth - 1])

        for m in legals:
            self.board.push(m)
            new_move_set, pred_eval = self.negamax_it_deep(
                -beta, -alpha, depth - 1, m, original_depth, depth_to_sort,
                prev_moves)
            pred_eval = -pred_eval
            self.board.pop()
            if pred_eval > max_val:
                move_set = new_move_set
                max_val, best_move = pred_eval, m

            alpha = max(max_val, alpha)
            if alpha >= beta:
                break
        if not best_move: best_move = m
        move_set.append(best_move)
        return move_set, max_val

    def negamax(self, alpha, beta, depth, original_depth, depth_to_sort):
        self.X += 1
        alpha_orig = alpha

        color = 1 if self.board.turn else -1
        if self.board.is_game_over(claim_draw=False):
            return self.get_results() * color

        cached = self.get_from_table()
        if cached and cached.entry_depth >= depth:
            if cached.flag == EXACT:
                return cached.val
            elif cached.flag == LOWER:
                alpha = max(alpha, cached.val)
            elif cached.flag == UPPER:
                beta = min(beta, cached.val)
            if alpha >= beta:
                return cached.val

        if depth == 0:
            return self.conventional_eval() * color
        best_move = None
        max_val = -1.0
        if original_depth - depth < depth_to_sort:
            legals = self.sort_moves()
        else:
            legals = self.board.legal_moves
        for move in legals:
            self.board.push(move)
            pred_eval = -self.negamax(-beta, -alpha, depth - 1, original_depth,
                                      depth_to_sort)
            self.board.pop()
            if pred_eval > max_val:
                max_val = pred_eval
                best_move = move
            alpha = max(max_val, alpha)
            if alpha >= beta:
                break
        if depth == original_depth:
            if not best_move: best_move = move
            self.MOVE_VAL.append((self.board.san(best_move), max_val * color))
        if max_val <= alpha_orig:
            flag = UPPER
        elif max_val >= beta:
            flag = LOWER
        else:
            flag = EXACT

        self.store_in_table(max_val, flag, depth)
        return max_val

    def min_max_tree_alpa_beta_fast(self, alpha, beta, depth, original_depth,
                                    depth_to_sort):
        self.X += 1
        if self.board.is_game_over(claim_draw=False):
            return self.get_results()
        if depth == 0:
            return self.conventional_eval()
        best_move = None
        if original_depth - depth < depth_to_sort:
            legals = self.sort_moves()
        else:
            legals = self.board.legal_moves

        if self.board.turn:
            minmax = -1.0
            for move in legals:
                self.board.push(move)

                pred_eval = self.min_max_tree_alpa_beta_fast(
                    alpha, beta, depth - 1, original_depth, depth_to_sort)
                self.board.pop()

                if pred_eval > minmax:
                    minmax, best_move = pred_eval, move

                alpha = max(pred_eval, alpha)
                if alpha >= beta:
                    break
            if depth == original_depth:
                if not best_move: best_move = move
                self.MOVE_VAL.append((self.board.san(best_move), minmax))
        else:
            minmax = 1.0
            for move in legals:
                self.board.push(move)
                pred_eval = self.min_max_tree_alpa_beta_fast(
                    alpha, beta, depth - 1, original_depth, depth_to_sort)
                self.board.pop()
                if pred_eval < minmax:
                    minmax, best_move = pred_eval, move
                beta = min(pred_eval, beta)
                if alpha >= beta:
                    break
            if depth == original_depth:
                if not best_move: best_move = move
                self.MOVE_VAL.append((self.board.san(best_move), minmax))
        return minmax

    def min_max_tree_alpa_beta_nodes2(self, alpha, beta, depth, opst_depth,
                                      original_depth, node):
        self.X += 1
        if self.board.is_game_over():
            res = self.get_results()
            node.val = res
            return res
        if depth == 0:
            eval_ = self.conventional_eval()
            node.val = eval_
            return eval_
        best_move = None
        if depth == original_depth:
            leg = self.sort_moves()
        else:
            leg = self.board.legal_moves
        if self.board.turn:
            max_eval = -1.0
            for move in leg:

                new_node = Node(self.board.san(move),
                                parent=node,
                                depth=opst_depth,
                                val=None,
                                no=self.X)
                self.board.push(move)
                pred_eval = self.min_max_tree_alpa_beta_nodes2(
                    alpha, beta, depth - 1, opst_depth + 1, original_depth,
                    new_node)
                self.board.pop()

                if pred_eval > max_eval:
                    max_eval, best_move = pred_eval, move

                alpha = max(pred_eval, alpha)
                if alpha >= beta:
                    break
            if depth == original_depth:
                if not best_move: best_move = move
                self.MOVE_VAL.append((self.board.san(best_move), max_eval))
                node.name = 'root: best is ' + self.board.san(best_move)
            node.val = max_eval
            return max_eval
        else:
            min_eval = 1.0
            for move in leg:
                new_node = Node(self.board.san(move),
                                parent=node,
                                depth=opst_depth,
                                val=None,
                                no=self.X)
                self.board.push(move)
                pred_eval = self.min_max_tree_alpa_beta_nodes2(
                    alpha, beta, depth - 1, opst_depth + 1, original_depth,
                    new_node)
                self.board.pop()
                if pred_eval < min_eval:
                    min_eval, best_move = pred_eval, move
                beta = min(pred_eval, beta)
                if alpha >= beta:
                    break

            if depth == original_depth:
                if not best_move: best_move = move
                self.MOVE_VAL.append((self.board.san(best_move), min_eval))
                node.name = 'root: best is ' + self.board.san(best_move)
            node.val = min_eval
            return min_eval