"""
CS4701 Practicum in AI
Project: Checkers AI with minimax algorithm
Team members: Shunqi Mao, Pu Zhao
"""

import copy
import math
import random
import datetime
import tkinter
import _thread

class AIPlayer():
    def __init__(self, game, difficulty):
        self.game = game
        self.difficulty = difficulty

    def getNextMove(self):
        if self.difficulty == 1:
            return self.getNextMoveEasy()
        elif self.difficulty == 2:
            return self.getNextMoveMedium()
        elif self.difficulty == 3:
            return self.getNextMoveHard()
        else:
            return self.getNextMoveInfernal()

    # Simple AI, returns a random legal move
    def getNextMoveEasy(self):
        state = AIGameState(self.game)
        moves = state.getActions(False)
        index = random.randrange(len(moves))
        chosenMove = moves[index]
        return chosenMove[0], chosenMove[1], chosenMove[2], chosenMove[3]

    # Hard AI, returns the move found by alpha-beta search with depth limit 5
    def getNextMoveMedium(self):
        state = AIGameState(self.game)
        nextMove = self.alphaBetaSearch(state, 5)
        return nextMove[0], nextMove[1], nextMove[2], nextMove[3]

    # Hard AI, returns the move found by alpha-beta search with depth limit 10
    def getNextMoveHard(self):
        state = AIGameState(self.game)
        nextMove = self.alphaBetaSearch(state, 10)
        return nextMove[0], nextMove[1], nextMove[2], nextMove[3]

    # Infernal AI, returns the best move found by alpha-beta search
    def getNextMoveInfernal(self):
        state = AIGameState(self.game)
        depthLimit = self.computeDepthLimit(state)
        nextMove = self.alphaBetaSearch(state, depthLimit)
        return nextMove[0], nextMove[1], nextMove[2], nextMove[3]

    # Dynamically compute depth limit
    # Fewer checkers we have, deeper level we can search
    def computeDepthLimit(self, state):
        numcheckers = len(state.AIpieces) + len(state.humanPieces)
        return 26 - numcheckers

    def alphaBetaSearch(self, state, depthLimit):
        # collect statistics for the search
        self.currDepth = 0
        self.maxDepth = 0
        self.numNodes = 0
        self.maxPruning = 0
        self.minPruning = 0

        self.bestMove = []
        self.depthLimit = depthLimit

        starttime = datetime.datetime.now()
        v = self.maxValue(state, -1000, 1000, self.depthLimit)

        # print statistics for the search

        print("Time = " + str(datetime.datetime.now() - starttime))
        print("selected value " + str(v))
        print("(1) max depth of the tree = {0:d}".format(self.maxDepth))
        print("(2) total number of nodes generated = {0:d}".format(self.numNodes))
        print("(3) number of times pruning occurred in the MAX-VALUE() = {0:d}".format(self.maxPruning))
        print("(4) number of times pruning occurred in the MIN-VALUE() = {0:d}".format(self.minPruning))

        return self.bestMove

    # For AI player (MAX AGENT)
    def maxValue(self, state, alpha, beta, depthLimit):
        if state.terminalTest():
            return state.computeUtilityValue()
        if depthLimit == 0:
            return state.computeHeuristic()

        # update statistics for the search
        self.currDepth += 1
        self.maxDepth = max(self.maxDepth, self.currDepth)
        self.numNodes += 1

        v = -math.inf
        for a in state.getActions(False):
            # return captured checker if it is a capture move
            captured = state.applyAction(a)
            # state.printBoard()
            if state.humanCanContinue():
                next = self.minValue(state, alpha, beta, depthLimit - 1)
            else:  # human cannot move, AI gets one more move
                next = self.maxValue(state, alpha, beta, depthLimit - 1)
            if next > v:
                v = next
                # Keep track of the best move so far at the top level
                if depthLimit == self.depthLimit:
                    self.bestMove = a
            state.revoke(a, captured)

            # alpha-beta max pruning
            if v >= beta:
                self.maxPruning += 1
                self.currDepth -= 1
                return v
            alpha = max(alpha, v)

        self.currDepth -= 1

        return v

    # For human player (MIN AGENT)
    def minValue(self, state, alpha, beta, depthLimit):
        if state.terminalTest():
            return state.computeUtilityValue()
        if depthLimit == 0:
            return state.computeHeuristic()

        # update statistics for the search
        self.currDepth += 1
        self.maxDepth = max(self.maxDepth, self.currDepth)
        self.numNodes += 1

        v = math.inf
        for a in state.getActions(True):
            captured = state.applyAction(a)
            if state.AICanContinue():
                next = self.maxValue(state, alpha, beta, depthLimit - 1)
            else:  # AI cannot move, human gets one more move
                next = self.minValue(state, alpha, beta, depthLimit - 1)
            if next < v:
                v = next
            state.revoke(a, captured)

            #alpha-beta min pruning
            if v <= alpha:
                self.minPruning += 1
                self.currDepth -= 1
                return v
            beta = min(beta, v)

        self.currDepth -= 1
        return v

# a class for AI to simulate game state
class AIGameState():
    def __init__(self, game):
        self.board = copy.deepcopy(game.getBoard())

        self.AIpieces = set()
        for checker in game.opponentPieces:
            self.AIpieces.add(checker)
        self.humanPieces = set()
        for checker in game.playerPieces:
            self.humanPieces.add(checker)
        self.checkerPositions = {}
        for checker, position in game.checkerPositions.items():
            self.checkerPositions[checker] = position

    # Check if the human player can cantinue.
    def humanCanContinue(self):
        directions = [[-1, -1], [-1, 1], [-2, -2], [-2, 2]]
        for checker in self.humanPieces:
            position = self.checkerPositions[checker]
            row = position[0]
            col = position[1]
            for dir in directions:
                if self.isValidMove(row, col, row + dir[0], col + dir[1], True):
                    return True
        return False

    # Check if the AI player can cantinue.
    def AICanContinue(self):
        directions = [[1, -1], [1, 1], [2, -2], [2, 2]]
        for checker in self.AIpieces:
            position = self.checkerPositions[checker]
            row = position[0]
            col = position[1]
            for dir in directions:
                if self.isValidMove(row, col, row + dir[0], col + dir[1], False):
                    return True
        return False

    # Neither player can can continue, thus game over
    def terminalTest(self):
        if len(self.humanPieces) == 0 or len(self.AIpieces) == 0:
            return True
        else:
            return (not self.AICanContinue()) and (not self.humanCanContinue())

    # Check if current move is valid
    def isValidMove(self, oldrow, oldcol, row, col, humanTurn):
        # invalid index
        if oldrow < 0 or oldrow > 7 or oldcol < 0 or oldcol > 7 \
                or row < 0 or row > 7 or col < 0 or col > 7:
            return False
        # No checker exists in original position
        if self.board[oldrow][oldcol] == 0:
            return False
        # Another checker exists in destination position
        if self.board[row][col] != 0:
            return False

        # human player's turn
        if humanTurn:
            if row - oldrow == -1:   # regular move
                return abs(col - oldcol) == 1
            elif row - oldrow == -2:  # capture move
                return (col - oldcol == -2 and self.board[row+1][col+1] < 0) \
                       or (col - oldcol == 2 and self.board[row+1][col-1] < 0)
            else:
                return False
        # opponent's turn
        else:
            if row - oldrow == 1:   # regular move
                return abs(col - oldcol) == 1
            elif row - oldrow == 2: # capture move
                # / direction or \ direction
                return (col - oldcol == -2 and self.board[row-1][col+1] > 0) \
                       or (col - oldcol == 2 and self.board[row-1][col-1] > 0)
            else:
                return False

    # compute utility value of terminal state
    # utility value = difference in # of checkers * 500 + # of AI checkers * 50
    # utility value has larger weights so that is it preferred over heuristic values
    def computeUtilityValue(self):
        utility = (len(self.AIpieces) - len(self.humanPieces)) * 500 \
                  + len(self.AIpieces) * 50
        return utility

    # compute heuristic value of a non-terminal state
    # heuristic value = diff in # of checkers * 50 + # of safe checkers * 10 + # of AI checkers
    def computeHeuristic(self):
        heurisitc = (len(self.AIpieces) - len(self.humanPieces)) * 50 \
                    + self.countSafeAIpieces() * 10 + len(self.AIpieces)
        return heurisitc

    # Count the number of safe AI checker.
    # A safe AI checker is one checker that no opponent can capture.
    def countSafeAIpieces(self):
        count = 0
        for AIchecker in self.AIpieces:
            AIrow = self.checkerPositions[AIchecker][0]
            AIcol = self.checkerPositions[AIchecker][1]
            safe = True
            if not (AIcol == 0 or AIcol == len(self.board[0])):
                # checkers near the boundaries are safe
                for humanchecker in self.humanPieces:
                    if AIrow < self.checkerPositions[humanchecker][0]:
                        safe = False
                        break
            if safe:
                count += 1
        return count

    # get all possible actions for the current player
    def getActions(self, humanTurn):
        if humanTurn:
            checkers = self.humanPieces
            regularDirs = [[-1, -1], [-1, 1]]
            captureDirs = [[-2, -2], [-2, 2]]
        else:
            checkers = self.AIpieces
            regularDirs = [[1, -1], [1, 1]]
            captureDirs = [[2, -2], [2, 2]]

        regularMoves = []
        captureMoves = []
        for checker in checkers:
            oldrow = self.checkerPositions[checker][0]
            oldcol = self.checkerPositions[checker][1]
            for dir in regularDirs:
                if self.isValidMove(oldrow, oldcol, oldrow+dir[0], oldcol+dir[1], humanTurn):
                    regularMoves.append([oldrow, oldcol, oldrow+dir[0], oldcol+dir[1]])
            for dir in captureDirs:
                if self.isValidMove(oldrow, oldcol, oldrow+dir[0], oldcol+dir[1], humanTurn):
                    captureMoves.append([oldrow, oldcol, oldrow+dir[0], oldcol+dir[1]])

        # must take capture move if possible
        if captureMoves:
            return captureMoves
        else:
            return regularMoves

    # Apply given action to the game board.
    def applyAction(self, action):
        oldrow = action[0]
        oldcol = action[1]
        row = action[2]
        col = action[3]
        captured = 0

        # move the checker
        toMove = self.board[oldrow][oldcol]
        self.checkerPositions[toMove] = (row, col)
        self.board[row][col] = toMove
        self.board[oldrow][oldcol] = 0

        # capture move, remove captured checker
        if abs(oldrow - row) == 2:
            captured = self.board[(oldrow + row) // 2][(oldcol + col) // 2]
            if captured > 0:
                self.humanPieces.remove(captured)
            else:
                self.AIpieces.remove(captured)
            self.board[(oldrow + row) // 2][(oldcol + col) // 2] = 0
            self.checkerPositions.pop(captured, None)

        return captured

    # Reset given action to the game board. Restored captured checker if any.
    def revoke(self, action, captured):
        oldrow = action[0]
        oldcol = action[1]
        row = action[2]
        col = action[3]

        # move the checker
        toMove = self.board[row][col]
        self.checkerPositions[toMove] = (oldrow, oldcol)
        self.board[oldrow][oldcol] = toMove
        self.board[row][col] = 0

        # capture move, remove captured checker
        if abs(oldrow - row) == 2:
            if captured > 0:
                self.humanPieces.add(captured)
            else:
                self.AIpieces.add(captured)
            self.board[(oldrow + row) // 2][(oldcol + col) // 2] = captured
            self.checkerPositions[captured] = ((oldrow + row) // 2, (oldcol + col) // 2)

    def printBoard(self):
        for i in range(len(self.board)):
            for j in range(len(self.board[i])):
                check = self.board[i][j]
                if (check < 0):
                    print(check,end=' ')
                else:
                    print(' ' + str(check),end=' ')

            print()
        print('------------------------')

class CheckerGame():
    def __init__(self):
        self.lock = _thread.allocate_lock()
        self.board = self.initBoard()
        self.playerTurn = self.whoGoFirst()
        self.difficulty = self.getDifficulty()
        self.AIPlayer = AIPlayer(self, self.difficulty)
        self.GUI = BoardGUI(self)

        # AI goes first
        if not self.isPlayerTurn():
            _thread.start_new_thread(self.AIMakeMove, ())

        self.GUI.startGUI()

    # Let player decide to go first or second
    def whoGoFirst(self):
        ans = input("Do you want to go first? (Y/N) ")
        return ans == "Y" or ans == "y"

    # Let player decide level of difficulty
    def getDifficulty(self):
        ans = eval(input("What level of difficulty? (1 Easy, 2 Medium, 3 Hard, 4 Infernal) "))
        while not (ans == 1 or ans == 2 or ans == 3 or ans == 4):
            print("Invalid input, please enter a value between 1 and 4")
            ans = eval(input("What level of difficulty? (1 Easy, 2 Medium, 3 Hard) "))
        return ans

    # This function initializes the game board.
    # Each checker has a label. Positive checkers for the player,
    # and negative checkers for the opponent.
    def initBoard(self):
        board = [[0]*8 for _ in range(8)]
        self.playerPieces = set()
        self.opponentPieces = set()
        self.checkerPositions = {}
        for i in range(8):
            self.playerPieces.add(i + 1)
            self.opponentPieces.add(-(i + 1))
            if i % 2 == 0:
                board[1][i] = -(i + 1)
                board[7][i] = i + 1
                self.checkerPositions[-(i + 1)] = (1, i)
                self.checkerPositions[i + 1] = (7, i)
            else:
                board[0][i] = -(i + 1)
                board[6][i] = i + 1
                self.checkerPositions[-(i + 1)] = (0, i)
                self.checkerPositions[i + 1] = (6, i)

        self.boardUpdated = True

        return board

    def getBoard(self):
        return self.board

    def printBoard(self):
        for i in range(len(self.board)):
            for j in range(len(self.board[i])):
                check = self.board[i][j]
                if (check < 0):
                    print(check,end=' ')
                else:
                    print(' ' + str(check),end=' ')

            print()

    def isBoardUpdated(self):
        return self.boardUpdated

    def setBoardUpdated(self):
        self.lock.acquire()
        self.boardUpdated = True
        self.lock.release()

    def completeBoardUpdate(self):
        self.lock.acquire()
        self.boardUpdated = False
        self.lock.release()

    def isPlayerTurn(self):
        return self.playerTurn

    # Switch turns between player and opponent.
    # If one of them has no legal moves, the other can keep playing
    def changePlayerTurn(self):
        if self.playerTurn and self.opponentCanContinue():
            self.playerTurn = False
        elif not self.playerTurn and self.playerCanContinue():
            self.playerTurn = True

    # apply the given move in the game
    def move(self, oldrow, oldcol, row, col):
        if not self.isValidMove(oldrow, oldcol, row, col, self.playerTurn):
            return

        # human player can only choose from the possible actions
        if self.playerTurn and not ([oldrow, oldcol, row, col] in self.getPossiblePlayerActions()):
            return

        self.makeMove(oldrow, oldcol, row, col)
        _thread.start_new_thread(self.next, ())

    # update game state
    def next(self):
        if self.isGameOver():
            self.getGameSummary()
            return
        self.changePlayerTurn()
        if self.playerTurn:     # let player keep going
            return
        else:                   # AI's turn
            self.AIMakeMove()

    # Temporarily Pause GUI and ask AI player to make next move.
    def AIMakeMove(self):
        self.GUI.pauseGUI()
        oldrow, oldcol, row, col = self.AIPlayer.getNextMove()
        self.move(oldrow, oldcol, row, col)
        self.GUI.resumeGUI()

    # update checker position
    def makeMove(self, oldrow, oldcol, row, col):
        toMove = self.board[oldrow][oldcol]
        self.checkerPositions[toMove] = (row, col)

        # move the checker
        self.board[row][col] = self.board[oldrow][oldcol]
        self.board[oldrow][oldcol] = 0

        # capture move, remove captured checker
        if abs(oldrow - row) == 2:
            toRemove = self.board[(oldrow + row) // 2][(oldcol + col) // 2]
            if toRemove > 0:
                self.playerPieces.remove(toRemove)
            else:
                self.opponentPieces.remove(toRemove)
            self.board[(oldrow + row) // 2][(oldcol + col) // 2] = 0
            self.checkerPositions.pop(toRemove, None)

        self.setBoardUpdated()

    # Get all possible moves for the current player
    def getPossiblePlayerActions(self):
        checkers = self.playerPieces
        regularDirs = [[-1, -1], [-1, 1]]
        captureDirs = [[-2, -2], [-2, 2]]

        regularMoves = []
        captureMoves = []
        for checker in checkers:
            oldrow = self.checkerPositions[checker][0]
            oldcol = self.checkerPositions[checker][1]
            for dir in regularDirs:
                if self.isValidMove(oldrow, oldcol, oldrow+dir[0], oldcol+dir[1], True):
                    regularMoves.append([oldrow, oldcol, oldrow+dir[0], oldcol+dir[1]])
            for dir in captureDirs:
                if self.isValidMove(oldrow, oldcol, oldrow+dir[0], oldcol+dir[1], True):
                    captureMoves.append([oldrow, oldcol, oldrow+dir[0], oldcol+dir[1]])

        # must take capture move if possible
        if captureMoves:
            return captureMoves
        else:
            return regularMoves

    # check if the given move is valid for the current player
    def isValidMove(self, oldrow, oldcol, row, col, playerTurn):
        # invalid index
        if oldrow < 0 or oldrow > 7 or oldcol < 0 or oldcol > 7 \
                or row < 0 or row > 7 or col < 0 or col > 7:
            return False
        # No checker exists in original position
        if self.board[oldrow][oldcol] == 0:
            return False
        # Another checker exists in destination position
        if self.board[row][col] != 0:
            return False

        # player's turn
        if playerTurn:
            if row - oldrow == -1:   # regular move
                return abs(col - oldcol) == 1
            elif row - oldrow == -2:  # capture move
                #  \ direction or / direction
                return (col - oldcol == -2 and self.board[row+1][col+1] < 0) \
                       or (col - oldcol == 2 and self.board[row+1][col-1] < 0)
            else:
                return False
        # opponent's turn
        else:
            if row - oldrow == 1:   # regular move
                return abs(col - oldcol) == 1
            elif row - oldrow == 2: # capture move
                # / direction or \ direction
                return (col - oldcol == -2 and self.board[row-1][col+1] > 0) \
                       or (col - oldcol == 2 and self.board[row-1][col-1] > 0)
            else:
                return False

    # Check if the player can cantinue
    def playerCanContinue(self):
        directions = [[-1, -1], [-1, 1], [-2, -2], [-2, 2]]
        for checker in self.playerPieces:
            position = self.checkerPositions[checker]
            row = position[0]
            col = position[1]
            for dir in directions:
                if self.isValidMove(row, col, row + dir[0], col + dir[1], True):
                    return True
        return False

    # Check if the opponent can cantinue
    def opponentCanContinue(self):
        directions = [[1, -1], [1, 1], [2, -2], [2, 2]]
        for checker in self.opponentPieces:
            position = self.checkerPositions[checker]
            row = position[0]
            col = position[1]
            for dir in directions:
                if self.isValidMove(row, col, row + dir[0], col + dir[1], False):
                    return True
        return False

    # Neither player can can continue, thus game over
    def isGameOver(self):
        if len(self.playerPieces) == 0 or len(self.opponentPieces) == 0:
            return True
        else:
            return (not self.playerCanContinue()) and (not self.opponentCanContinue())

    def getGameSummary(self):
        self.GUI.pauseGUI()
        print("Game Over!")
        playerNum = len(self.playerPieces)
        opponentNum = len(self.opponentPieces)
        if (playerNum > opponentNum):
            print("Player won by {0:d} checkers! Congratulation!".format(playerNum - opponentNum))
        elif (playerNum < opponentNum):
            print("Computer won by {0:d} checkers! Try again!".format(opponentNum - playerNum))
        else:
            print("It is a draw! Try again!")

class BoardGUI():
    def __init__(self, game):
        # Initialize parameters
        self.game = game
        self.ROWS = 8
        self.COLS = 8
        self.WINDOW_WIDTH = 800
        self.WINDOW_HEIGHT = 800
        self.col_width = self.WINDOW_WIDTH / self.COLS
        self.row_height = self.WINDOW_HEIGHT / self.ROWS

        # Initialize GUI
        self.initBoard()

    def initBoard(self):
        self.root = tkinter.Tk()
        self.c = tkinter.Canvas(self.root, width=self.WINDOW_WIDTH, height=self.WINDOW_HEIGHT,
                                borderwidth=5, background='white')
        self.c.pack()
        self.board = [[0]*self.COLS for _ in range(self.ROWS)]
        self.tiles = [[None for _ in range(self.COLS)] for _ in range(self.ROWS)]

        # Print dark square
        for i in range(8):
            for j in range(8):
                if (i + j) % 2 == 1:
                    self.c.create_rectangle(i * self.row_height, j * self.col_width,
                                        (i+1) * self.row_height, (j+1) * self.col_width, fill="gray", outline="gray")

        # Print grid lines
        for i in range(8):
            self.c.create_line(0, self.row_height * i, self.WINDOW_WIDTH, self.row_height * i, width=2)
            self.c.create_line(self.col_width * i, 0, self.col_width * i, self.WINDOW_HEIGHT, width=2)

        # Place checks on the board
        self.updateBoard()

        # Initialize parameters
        self.checkerSelected = False
        self.clickData = {"row": 0, "col": 0, "checker": None}

        # Register callback function for mouse clicks
        self.c.bind("<Button-1>", self.processClick)

        # make GUI updates board every second
        self.root.after(1000, self.updateBoard)

    def startGUI(self):
        self.root.mainloop()

    def pauseGUI(self):
        self.c.bind("<Button-1>", '')

    def resumeGUI(self):
        self.c.bind("<Button-1>", self.processClick)

    # Update the positions of checkers
    def updateBoard(self):
        if self.game.isBoardUpdated():
            newBoard = self.game.getBoard()
            for i in range(len(self.board)):
                for j in range(len(self.board[0])):
                    if self.board[i][j] != newBoard[i][j]:
                        self.board[i][j] = newBoard[i][j]
                        self.c.delete(self.tiles[i][j])
                        self.tiles[i][j] = None

                        # choose different color for different player's checkers
                        if newBoard[i][j] < 0:
                            self.tiles[i][j] = self.c.create_oval(j*self.col_width+10, i*self.row_height+10,
                                                              (j+1)*self.col_width-10, (i+1)*self.row_height-10,
                                                              fill="black")
                        elif newBoard[i][j] > 0:
                            self.tiles[i][j] = self.c.create_oval(j*self.col_width+10, i*self.row_height+10,
                                                                  (j+1)*self.col_width-10, (i+1)*self.row_height-10,
                                                                  fill="red")
                        else:  # no checker
                            continue

                        # raise the tiles to highest layer
                        self.c.tag_raise(self.tiles[i][j])

            # tell game logic that GUI has updated the board
            self.game.completeBoardUpdate()

        # make GUI updates board every second
        self.root.after(1000, self.updateBoard)

    # this function checks if the checker belongs to the current player
    def isCurrentPlayerChecker(self, row, col):
        return self.game.isPlayerTurn() == (self.board[row][col] > 0)

    # callback function that process user's mouse clicks
    def processClick(self, event):
        col = int(event.x // self.col_width)
        row = int(event.y // self.row_height)

        # If there is no checker being selected
        if not self.checkerSelected:
            # there exists a checker at the clicked position
            # and the checker belongs to the current player
            if self.board[row][col] != 0 and self.isCurrentPlayerChecker(row, col):
                self.clickData["row"] = row
                self.clickData["col"] = col
                self.clickData["color"] = self.c.itemcget(self.tiles[row][col], 'fill')

                # replace clicked checker with a temporary checker
                self.c.delete(self.tiles[row][col])
                self.tiles[row][col] = self.c.create_oval(col*self.col_width+10, row*self.row_height+10,
                                                         (col+1)*self.col_width-10, (row+1)*self.row_height-10,
                                                          fill="green")
                self.checkerSelected = True

            else: # no checker at the clicked postion
                return

        else: # There is a checker being selected
            # First reset the board
            oldrow = self.clickData["row"]
            oldcol = self.clickData["col"]
            self.c.delete(self.tiles[oldrow][oldcol])
            self.tiles[oldrow][oldcol] = self.c.create_oval(oldcol*self.col_width+10, oldrow*self.row_height+10,
                                                            (oldcol+1)*self.col_width-10, (oldrow+1)*self.row_height-10,
                                                            fill=self.clickData["color"])

            # If the destination leads to a legal move
            self.game.move(self.clickData["row"], self.clickData["col"],row, col)
            self.checkerSelected = False

game = CheckerGame()


def solve(s):
    if (s == None or len(s) < 1):
        return ""
    res = ""
    for i in range(len(s)):
        len1 = helper(s, i, i)
        if len(len1) > len(res):
            res = len1
        len2 = helper(s, i, i+1)
        if len(len2) > len(res):
            res = len2
        #res = max(len1, len2)

    return res

def helper(s, l, r):
    if (s == None or l > r):
        return 0

    while (l >= 0 and r < len(s) and s[l] == s[r]):
        l -= 1
        r += 1

    return s[l+1:r]

s = "aaaabbaa"
print(solve(s))


def solution(s):
    words = s.split(" ")  # 문자열을 공백을 기준으로 단어로 분리
    answer = []

    for word in words:
        converted = ""
        for i in range(len(word)):
            if i % 2 == 0:
                converted += word[i].upper()  # 짝수 인덱스는 대문자로 변환
            else:
                converted += word[i].lower()  # 홀수 인덱스는 소문자로 변환
        answer.append(converted)

    return " ".join(answer)  # 변환된 단어들을 공백으로 연결하여 문자열로 반환

프로그래머스/lv1/12930. 이상한 문자 만들기/이상한 문자 만들기.py

이상한 문자 만들기.py


import asyncio
import socketio
from services.redis import redis_m

sio = socketio.AsyncClient()  # instance of socket io server

@sio.event
async def send_message(message):
    '''Send message to the room from connected client.'''
    print(f'Sending: {message}')
    await sio.send(message)

@sio.event
async def connect():
    '''Connect to the selected room and print "connection established" message
    to client.'''
    print('connection established')
    await sio.emit('rooms', {'room': 'Redis_Room'})

@sio.event
async def disconnect():
    '''Disconnect message when server disconnect client'''
    print('disconnected from server')

async def main():
    await sio.connect('http://localhost:8080')
    while True:
        try:
            await send_message(redis_m.decode())
            await sio.sleep(60)
        except KeyboardInterrupt:
            break
    await sio.wait()

if __name__ == '__main__':
    asyncio.run(main())


def send_mail(settings, infos):
	settings['sender']
	settings['port']
	settings['smtp']
	settings['sender']
	infos
	infos = [['cpu', 95], ['ram', 100]]
	body = message
	for info in infos:
		body += "\n{} : {}".format(info[0], info[1])
	# envoi

def send_mails(infos):
	configfile = ...
	config = ConfigParser()
	config.read(configfile)
	settings['message'] = config.get('Mail','message')
	for conf in config.sections():
		for key, value in conf.items(conf):
			settings[key] = value
		send_mail(settings, infos)


# coding:utf-8
from PyQt5.QtWidgets import (QWidget, QLabel, QApplication, QDesktopWidget,
                             QMessageBox)
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtCore import QTimer, Qt
import sys
import cv2
import threading
import MqttSubClient
import GlobalRes
import HeatMap

class HeatMapWindow(QWidget):

    def __init__(self):
        super().__init__()
        self.title = "车站人员热力检测系统"
        GlobalRes.init_heat_map()  # 初始化热力图，避免残留数据的影响
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setWindowIcon(QIcon(GlobalRes.icon_path))
        self.setFixedSize(1794, 695)
        self.center()
        self.label = QLabel(self)
        timer = QTimer(self)
        timer.timeout.connect(self.update_image)
        timer.start(1000)  # 定时器 1s刷新一次数据
        self.update_image()

    # 主窗口居中显示
    def center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) / 2,
                  (screen.height() - size.height()) / 2)

    # 更新图片
    def update_image(self):
        pixmap = QPixmap(GlobalRes.image_heat_map_path)
        if not pixmap.isNull():
            self.label.setPixmap(pixmap)
            self.label.adjustSize()
            self.resize(pixmap.size())

# mqtt client to connect broker
def loop_mqtt_client():
    client = MqttSubClient.connect_mqtt()
    MqttSubClient.subscribe(client)
    client.loop_forever()

# mqtt thread to accept message from broker and update datas and heat map
def mqtt_thread(func):
    mqtt_thread = threading.Thread(target=func, name='LoopThread')
    mqtt_thread.setDaemon(True)  # 设置守护进程, 在窗口关闭时，断开 Mqtt连接
    mqtt_thread.start()

if __name__ == '__main__':
    '''开启一个新线程去做连接服务器'''
    mqtt_thread(loop_mqtt_client)
    '''展示热力图'''
    app = QApplication(sys.argv)
    heat_map_ex = HeatMapWindow()
    heat_map_ex.show()
    sys.exit(app.exec_())


import disnake.ext.commands
from disnake.ext import commands
from variables.staticData import token
from variables.dynamicData import version

intents = disnake.Intents.all()
bot = commands.Bot(command_prefix=commands.when_mentioned_or(","), test_guilds=[913922450205585439], intents=intents)

@bot.event
async def on_ready():
	bot.load_extension(f'base.cogManager')
	print(f"Started with version: {version}")
	await bot.change_presence(activity=disnake.Activity(type=disnake.ActivityType.watching, name=''))

@bot.event
async def on_message(message):
	await bot.process_commands(message)

@bot.event
async def on_message_edit(message_before, message_after):
	await bot.process_commands(message_after)

bot.run(token)


#AA1846

numberOfcolums = 4
numberOfRounds = 10
numberOfWordsInKey = 4

def main():

    print()
    pText = input('Enter your message to be encrypted : ').encode()
    print("\nYou can add both characters and numbers as the key.\n")
    key = input('Enter key : ')
    print()

    if len(key) > 16:
        print('Invalid Key.')
        return

    for symbol in key:
        if ord(symbol) > 0xff:
            print('Invalid Key')
            return

    encryptedBytes = []
    temp = []
    for byte in pText:
        temp.append(byte)
        if len(temp) == 16:
            Encrypted16Bytes = AES128Encryption(temp, key)
            encryptedBytes.extend(Encrypted16Bytes)
            temp = []

    emptySpaces = None
    if 0 < len(temp) < 16:
            emptySpaces = 16 - len(temp)
            for i in range(emptySpaces - 1):
                temp.append(0)
            temp.append(1)
            Encrypted16Bytes = AES128Encryption(temp, key)
            encryptedBytes.extend(Encrypted16Bytes)

    encryptedMessage = bytes(encryptedBytes)

    print("Encrypted ciphertext is (in bytes) : ", encryptedMessage)

    decryptedBytes = []
    temp = []
    for byte in encryptedMessage:
        temp.append(byte)
        if len(temp) == 16:
            Decrypted16Bytes = AES128Decryption(temp, key)
            decryptedBytes.extend(Decrypted16Bytes)
            temp = []

    if emptySpaces != None:
        decryptedMessage = bytes(decryptedBytes[ : -emptySpaces])
    else:
        decryptedMessage = bytes(decryptedBytes)

    print("Decrypted pText is (in bytes) : ", decryptedMessage)
    print()

def AES128Encryption(pTextBytes, key):

    state = [[] for j in range(4)]

    for row in range(4):
        for column in range(numberOfcolums):
            state[row].append(pTextBytes[row + 4 * column])

    keySchedule = keyExpansion(key)

    round = 0
    state = addRoundKey(state, keySchedule, round)

    for round in range(1, numberOfRounds):
        state = subBytes(state, False)
        state = shiftRows(state, False)
        state = mixColumns(state, False)
        state = addRoundKey(state, keySchedule, round)

    state = subBytes(state, False)
    state = shiftRows(state, False)
    state = addRoundKey(state, keySchedule, round + 1)

    output = [None for i in range(4 * numberOfcolums)]
    for row in range(4):
        for column in range(numberOfcolums):
            output[row + 4 * column] = state[row][column]

    return output

def AES128Decryption(ciphertextBytes, key):

    state = [[] for i in range(numberOfcolums)]

    for row in range(4):
        for column in range(numberOfcolums):
            state[row].append(ciphertextBytes[row + 4 * column])

    keySchedule = keyExpansion(key)

    state = addRoundKey(state, keySchedule, numberOfRounds)

    round = numberOfRounds - 1
    for round in range(round, 0, -1):
        state = shiftRows(state, True)
        state = subBytes(state, True)
        state = addRoundKey(state, keySchedule, round)
        state = mixColumns(state, True)

    round -= 1

    state = shiftRows(state, True)
    state = subBytes(state, True)
    state = addRoundKey(state, keySchedule, round)

    output = [None for i in range(4 * numberOfcolums)]
    for row in range(4):
        for column in range(numberOfcolums):
            output[row + 4 * column] = state[row][column]

    return output

Sbox = [
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
]

inverseSbox = [
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
]

def subBytes(state, inverse):

    box = Sbox
    if inverse == True:
        box = inverseSbox

    for i in range(len(state)):
        for j in range(len(state[i])):

            row = state[i][j] // 0x10
            column = state[i][j] % 0x10

            boxElement = box[16 * row + column]
            state[i][j] = boxElement

    return state

def shiftRows(state, inverse):

    count = 1

    if inverse == True:
        for i in range(1, numberOfcolums):
            state[i] = rightShift(state[i], count)
            count += 1
    else:
        for i in range(1, numberOfcolums):
            state[i] = leftShift(state[i], count)
            count += 1

    return state

def leftShift(array, count):

    return array[count:] + array[:count]

def rightShift(array, count):

    return array[-count:] + array[:-count]

def mixColumns(state, inverse):

    for i in range(numberOfcolums):

        if inverse == True:
            s0 = multiplyBy0x0e(state[0][i]) ^ multiplyBy0x0b(state[1][i]) ^ multiplyBy0x0d(state[2][i]) ^ multiplyBy0x09(state[3][i])
            s1 = multiplyBy0x09(state[0][i]) ^ multiplyBy0x0e(state[1][i]) ^ multiplyBy0x0b(state[2][i]) ^ multiplyBy0x0d(state[3][i])
            s2 = multiplyBy0x0d(state[0][i]) ^ multiplyBy0x09(state[1][i]) ^ multiplyBy0x0e(state[2][i]) ^ multiplyBy0x0b(state[3][i])
            s3 = multiplyBy0x0b(state[0][i]) ^ multiplyBy0x0d(state[1][i]) ^ multiplyBy0x09(state[2][i]) ^ multiplyBy0x0e(state[3][i])
        else:
            s0 = multiplyBy0x02(state[0][i]) ^ multiplyBy0x03(state[1][i]) ^ state[2][i] ^ state[3][i]
            s1 = state[0][i] ^ multiplyBy0x02(state[1][i]) ^ multiplyBy0x03(state[2][i]) ^ state[3][i]
            s2 = state[0][i] ^ state[1][i] ^ multiplyBy0x02(state[2][i]) ^ multiplyBy0x03(state[3][i])
            s3 = multiplyBy0x03(state[0][i]) ^ state[1][i] ^ state[2][i] ^ multiplyBy0x02(state[3][i])

        state[0][i] = s0
        state[1][i] = s1
        state[2][i] = s2
        state[3][i] = s3

    return state

RCON = [
    [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36],
    [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
]

def keyExpansion(key):

    keySymbols = [ord(symbol) for symbol in key]

    if len(keySymbols) < 4 * numberOfWordsInKey:
        for i in range(4 * numberOfWordsInKey - len(keySymbols)):
            keySymbols.append(0x01)

    keySchedule = [[] for i in range(4)]
    for row in range(4):
        for column in range(numberOfWordsInKey):
            keySchedule[row].append(keySymbols[row + 4 * column])

    for column in range(numberOfWordsInKey, numberOfcolums * (numberOfRounds + 1)):

        if column % numberOfWordsInKey == 0:
            temp = [keySchedule[row][column - 1] for row in range(1, 4)]
            temp.append(keySchedule[0][column - 1])

            for j in range(len(temp)):

                SboxRow = temp[j] // 0x10
                SboxColumn = temp[j] % 0x10
                SboxElement = Sbox[16 * SboxRow + SboxColumn]
                temp[j] = SboxElement

            for row in range(4):
                s = (keySchedule[row][column - 4]) ^ (temp[row]) ^ (RCON[row][int(column / numberOfWordsInKey - 1)])
                keySchedule[row].append(s)
        else:
            for row in range(4):
                s = keySchedule[row][column - 4] ^ keySchedule[row][column - 1]
                keySchedule[row].append(s)

    return keySchedule

def addRoundKey(state, keySchedule, round):

    for column in range(numberOfWordsInKey):

        s0 = state[0][column] ^ keySchedule[0][numberOfcolums * round + column]
        s1 = state[1][column] ^ keySchedule[1][numberOfcolums * round + column]
        s2 = state[2][column] ^ keySchedule[2][numberOfcolums * round + column]
        s3 = state[3][column] ^ keySchedule[3][numberOfcolums * round + column]

        state[0][column] = s0
        state[1][column] = s1
        state[2][column] = s2
        state[3][column] = s3

    return state

def multiplyBy0x02(number):
    if number < 0x80:
        result = (number << 1)
    else:
        result = (number << 1) ^ 0x1b

    result = result % 0x100

    return result

def multiplyBy0x03(number):
    return (multiplyBy0x02(number) ^ number)

def multiplyBy0x09(number):
    return multiplyBy0x02(multiplyBy0x02(multiplyBy0x02(number))) ^ number

def multiplyBy0x0b(number):
    return multiplyBy0x02(multiplyBy0x02(multiplyBy0x02(number))) ^ multiplyBy0x02(number) ^ number

def multiplyBy0x0d(number):
    return multiplyBy0x02(multiplyBy0x02(multiplyBy0x02(number))) ^ multiplyBy0x02(multiplyBy0x02(number)) ^ number

def multiplyBy0x0e(number):
    return multiplyBy0x02(multiplyBy0x02(multiplyBy0x02(number))) ^ multiplyBy0x02(multiplyBy0x02(number)) ^ multiplyBy0x02(number)

if __name__ == '__main__':
    main()


def triNumber(number):
    """ (int) -> int

    Return the numberth of triangular number.

    >>> triNumber(5)
    15
    >>> triNumber(7)
    28
    """

    sum = 0

    for i in range(number + 1):
        sum = sum + i

    return sum

def divisorFinder(number):
    """ (int) -> [list, int]

    Return a list of the positive divisors of number.

    >>> divisorFinder(10)
    [[1, 2, 5, 10], 4]
    >>> divisorFinder(28)
    [[1, 2, 4, 7, 14, 28], 6]
    """

    result = 0
    result_list = []
    counter = 0

    for i in range(1, number + 1):
        if number % i == 0:
            result = i
            result_list.append(result)
            counter = counter + 1

    return [result_list, counter]

def leastFinder(num):
    """ (int) -> int

    Return the first number to have num divisors.

    >>> leastFinder(6)
    12
    """

    checker = False
    i = 0

    while checker == False:
        i = i + 1
        if divisorFinder(i)[1] >= num:
            return i

def solution(num):
    checker = False
    i = 0

    while checker == False:
        i = i + 1

        if divisorFinder(triNumber(i))[1] >=num:
            return triNumber(i)

##
##    while checker == False:
##        i = i + 1
##        if divisorFinder(triNumber(i))[1] > num:
##            checker = True
##        if i == 100:
##            print(i)
##        if i == 500:
##            print(i)
##        if i == 1000:
##            print(i)
##        if i == 5000:
##            print(i)
##        if i == 10000:
##            print(i)
##        if i == 50000:
##            print(i)
##        if i == 100000:
##            print(i)
##        if i == 500000:
##            print(i)
##        if i == 1000000:
##            print(i)
##        if i == 5000000:
##            print(i)
##        if i == 10000000:
##            print(i)
##        if i == 50000000:
##            print(i)
##        if i == 100000000:
##            print(i)
##        if i == 500000000:
##            print(i)
##        if i == 1000000000:
##            print(i)
##        if i == 5000000000:
##            print(i)
##        if i == 10000000000:
##            print(i)

    return i


from pyaudio import PyAudio, paInt16
from datetime import datetime
import wave
# from tkinter import *

# define of params
NUM_SAMPLES = 2000
framerate = 8000
channels = 1
sampwidth = 2
# the longest record time
TIME = 30  # 最长录制时间

FILENAME = ''  # 录制文件名
NOW = ''  # 存储录制时间
SAVE = ''
JUDGE = True  # 进行录制结束的判断

def save_wave_file(filename, data):
    '''save the date to the wav file'''
    wf = wave.open(filename, 'wb')
    wf.setnchannels(channels)
    wf.setsampwidth(sampwidth)
    wf.setframerate(framerate)
    wf.writeframes("".join(data))
    wf.close()

def record_wave():
    # open the input of wave
    pa = PyAudio()
    stream = pa.open(format=paInt16, channels=1, rate=framerate, input=True, frames_per_buffer=NUM_SAMPLES)
    save_buffer = []
    count = 0
    global JUDGE
    while JUDGE and count < TIME * 4:
        # read NUM_SAMPLES sampling data
        string_audio_data = stream.read(NUM_SAMPLES)
        save_buffer.append(string_audio_data)
        count += 1
        print(".")

    now = datetime.now().strftime("%Y-%m-%d_%H_%M_%S")
    filename = now + ".wav"
    SAVE = save_wave_file(filename, save_buffer)
    save_buffer = []
    print(filename, "saved")
    global FILENAME, NOW, JUDGE
    FILENAME = filename
    NOW = now

if __name__ == '__main__':
    """先调用recorder中的函数record_wave，然后在录制结束时将recorder中的全局变量JUDGE置为False，即可完成录制。
    """
    record_wave()


# /usr/bin/env python
# -*- encoding: utf-8 -*-
"""
【少前13-4自动刷图】
"""
# 打包命令pyinstaller main.spec
import logging.config
import os
import subprocess
import sys
import threading
import time
from functools import partial

import aircv
import win32api
import win32con
import yaml

# 全局覆盖print函数参数
print = partial(print, flush=True)

class Dnconsole:
    """
    【雷电控制台类】
    version: 9.0
    import该文件会自动实例化为 Dc
    """

    def __init__(self, installation_path: str):
        """
        【构造方法】
        """
        # if 模拟器安装路径存在性检测
        if os.path.exists(installation_path) is False:
            print('模拟器安装路径不存在！')
        # 获取模拟器安装路径
        self.ins_path = installation_path
        # Dnconsole程序路径
        self.console_path = self.ins_path + r'\ldconsole.exe '
        # ld程序路径
        self.ld_path = self.ins_path + r'\ld.exe '
        # if Dnconsole程序路径检测
        if os.path.exists(self.console_path) is False:
            print('Dnconsole程序路径不存在！\n请确认模拟器安装文件是否完整或模拟器版本是否不符！')
        # adb程序路径
        self.adb_path = self.ins_path + r'\adb.exe '
        # if adb程序路径检测
        if os.path.exists(self.adb_path) is False:
            print('Dnconsole程序路径不存在！\n请确认模拟器安装文件是否完整！')
        # 模拟器截屏程序路径
        self.screencap_path = r'/system/bin/screencap'
        # 模拟器截图保存路径
        self.devicess_path = r'/sdcard/Pictures/Screenshots/screenshot_tmp.png'
        # 用户环境
        self.userprofile = os.environ['USERPROFILE']
        self.documents_path = os.path.join(self.userprofile, 'Documents')
        # 本地图片保存路径
        self.images_path = self.documents_path + r'\leidian9\Pictures\Screenshots\\'
        # if 模拟器的截图保存路径
        if os.path.exists(self.images_path) is False:
            print('模拟器截图保存路径不存在！')
        # 获取当前工作目录路径
        self.workspace_path = os.getcwd()
        # 本地图片样本保存路径
        # self.target_path = self.workspace_path + r'\target\1080p_dpi280\\'
        self.target_path = self.workspace_path + r'\target\720p_dpi240\\'
        # 读取作战参数信息保存路径
        self.action_path = self.workspace_path + r'\script\\'
        # 读取作战参数
        # 构造完成
        logging.debug('Class-Dnconsole is ready.(%s)' % self.ins_path)
        # print('Class-Dnconsole is ready.(%s)' % self.ins_path)

    def YAML(self, script_name: str):
        """
        【读取Yaml文件】
        :param script_name:
        :return:
        """
        # 读取YAML参数
        yaml_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                                 self.action_path + script_name)
        logging.debug('yaml_path: %s' % yaml_path)
        try:
            # 打开文件
            with open(yaml_path, "r", encoding="utf-8") as f:
                data = yaml.load(f, Loader=yaml.FullLoader)
                return data
        except:
            return None

    def CMD(self, cmd: str):
        """
        【执行控制台命令语句】
        :param cmd: 命令
        :return: 控制台调试内容
        """
        CMD = self.console_path + cmd  # 控制台命令
        process = os.popen(CMD)
        result = process.read()
        process.close()
        return result

    def ldCMD(self, cmd: str):
        """
        【通过ld.exe执行代替adb的命令语句】
        :param cmd: 命令
        :return: 控制台调试内容
        """
        ldCMD = self.ld_path + cmd  # 控制台命令
        process = os.popen(ldCMD)
        result = process.read()
        process.close()
        return result

    def ADB(self, cmd: str):
        """
        【执行ADB命令语句】
        :param cmd: 命令
        :return: 控制台调试内容
        """
        CMD = self.adb_path + cmd  # adb命令
        # os.system('chcp 65001')
        p = subprocess.Popen(['C:\\leidian\\LDPlayer9\\adb', '-s', '127.0.0.1:5555', 'exec-out', 'screencap', '-p'],
                             stdout=subprocess.PIPE)
        output, _ = p.communicate()
        return output

    def launch(self, index: int = 0):
        """
        【启动模拟器】
        :param index: 模拟器序号
        :return: True=已启动 / False=不存在
        """
        cmd = 'launch --index %d' % index
        if self.CMD(cmd) == '':
            return True
        else:
            return False

    def runninglist(self):
        """
        【获取正在运行的模拟器列表（仅标题）】
        :return: 控制台调试内容
        """
        cmd = 'runninglist'
        return self.CMD(cmd)

    def isrunning(self, index: int = 0):
        """
        【检测模拟器是否启动】
        :param index: 模拟器序号
        :return: True=已启动 / False=未启动
        """
        cmd = 'isrunning --index %d' % index
        if self.CMD(cmd) == 'running':
            return True
        else:
            return False

    def runApp(self, index: int, packagename: str):
        """
        【运行App】
        :param index: 模拟器序号
        :param packagename: 包名
        :return: 控制台调试内容
        """
        cmd = 'runapp --index %d --packagename %s' % (index, packagename)
        return self.CMD(cmd)

    def isrunningAPP(self, index: int, packagename: str):
        """
        【检测应用是否启动】
        :param packagename: App包名
        :param index: 模拟器序号
        :return: True=已启动 / False=未启动
        """
        cmd = 'adb --index %d --command "shell dumpsys window | grep mCurrentFocus"' % index
        if packagename in self.CMD(cmd):
            return True
        else:
            return False

    def appIsrunning(self, index: int, packagename: str):
        """
        【获取App运行状态】
        :param index: 模拟器序号
        :param packagename: 包名
        :return: 控制台调试内容
        """
        cmd = 'adb --index %d --command "shell pidof %s"' % (index, packagename)
        return self.CMD(cmd)

    def killApp(self, index: int, packagename: str):
        """
        【终止App】
        :param index: 模拟器序号
        :param packagename: 包名
        :return: 控制台调试内容
        """
        cmd = 'killapp --index %d --packagename %s' % (index, packagename)
        return self.CMD(cmd)

    def screenShot(self, index: int):
        """
        【截屏并保存到本地】
        :param index: 模拟器序号
        """
        cmd1 = 'adb --index %d --command "shell %s -p %s"' % (index, self.screencap_path, self.devicess_path)
        cmd2 = 'adb --index %d --command "pull %s %s"' % (index, self.devicess_path, self.images_path)
        self.CMD(cmd1)
        self.CMD(cmd2)

    def screenShotnew(self, index: int):
        """
        【截屏并保存到本地】
        :param index: 模拟器序号
        """
        cmd = 'adb --index %d --command "shell %s -p %s"' % (index, self.screencap_path, self.devicess_path)
        result = self.CMD(cmd)
        return result

    def screenShotnewLd(self, index: int):
        """
        【截屏并保存到本地ld调用方式】
        :param index: 模拟器序号
        """
        cmd = '-s %d " %s -p %s"' % (index, self.screencap_path, self.devicess_path)
        result = self.ldCMD(cmd)
        scp_time = time.time() - start_time
        scp_time = time.strftime("%H:%M:%S", time.gmtime(scp_time))
        logging.info("%s 已截图" % scp_time)
        return result

    def actionOfTap(self, index: int, x: int, y: int):
        """
        【点击操作】
        :param index: 模拟器序号
        :param x: x
        :param y: y
        :return: 控制台调试内容
        """
        cmd = 'adb --index %d --command "shell input tap %d %d"' % (index, x, y)
        return self.CMD(cmd)

    def inputTap(self, index: int, x: int, y: int):
        """
        【点击操作】
        :param index: 模拟器序号
        :param x: x
        :param y: y
        :return: 控制台调试内容
        """
        cmd = '-s %d input tap %d %d' % (index, x, y)
        return self.ldCMD(cmd)

    def inputSwipe(self, index: int, x0: int, y0: int, x1: int, y1, ms: int = 200):
        """
        【滑动操作】
        :param index:
        :param x0:
        :param y0:
        :param x1:
        :param y1:
        :param ms:
        :return:
        """
        cmd = '-s %d input swipe %d %d %d %d %d' % (index, x0, y0, x1, y1, ms)
        return self.ldCMD(cmd)

class Ldaction(object):
    """
    【雷电操作类】
    import该文件需先实例化 Dconsole
    """

    def __init__(self, index: int, console, screenshot_img_name: str):
        """
        【构造方法】
        :param index: 模拟器号
        :param console: 传入的console对象
        :param screenshot_img_name:
        :return:
        """
        self.screenshot_img_file = console.images_path + screenshot_img_name
        # self.screenshot_img = aircv.imread(self.screenshot_img_file)
        self.index = index
        self.ld = console
        self.tap_info = self.ld.YAML

    def isExistV2(self, target_name: str, target_img_num: int = 0):
        """
        【判断指定图片是否包含在截图中】
        :param target_name:
        :param target_img_num:
        :return: 图片查询结果和包含详情
        """
        target_yaml = target_yaml_data[target_name]
        target_img_name = target_yaml['target_img_name_list'][target_img_num]
        rgb = target_yaml['rgb']
        threshold = target_yaml['threshold']
        screenshot_img = aircv.imread(self.screenshot_img_file)
        target_img_name_file = self.ld.target_path + target_img_name
        target_img = aircv.imread(target_img_name_file)
        result = aircv.find_template(screenshot_img, target_img, rgb=rgb, threshold=threshold)
        if result is None:
            return False, result
        else:
            return True, result

    def find_target_imgV4(self, target_name: str, tap_info: dict):
        """
        【改进后的目标图片搜索方法】
        :param tap_info:
        :param target_name:
        :return:
        """
        logging.debug('target: %s, 配置信息: %s' % (target_name, tap_info))
        target_img_name_list = tap_info['target_img_name_list']
        need_screenShot = tap_info['need_screenShot']
        moveX = tap_info['moveX']
        moveY = tap_info['moveY']
        tap_times = tap_info['tap_times']
        tap_interval = tap_info['tap_interval']
        search_again_times = tap_info['search_again_times']
        search_again_sleep_time = tap_info['search_again_sleep_time']
        for target_img_name in target_img_name_list:
            for search in range(search_again_times):
                if need_screenShot:
                    self.ld.ScreenShot_Ld(self.index)
                target_img_num = target_img_name_list.index(target_img_name)
                result = self.isExistV2(target_name, target_img_num)
                find_result = result[1]
                if find_result is None and need_screenShot is False and search_again_times == 1:
                    running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                    # print('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                    logging.error('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                    break
                elif find_result is None and search_again_times == 1:
                    running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                    # print('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                    logging.error('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                elif find_result is None and search_again_times > 1:
                    running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                    # print('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name), end="")
                    logging.warning('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                    search_times = 1
                    search_times += search
                    if search_again_sleep_time != 0:
                        search_again_sleep_time_double = int(search_again_sleep_time * 2)
                        for s in range(search_again_sleep_time_double + 1):
                            running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                            half_second = s
                            s_10, d = divmod(half_second, 10)
                            print("\r%s %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                            if s == search_again_sleep_time_double:
                                pass
                            else:
                                time.sleep(0.5)
                        print('\n', end='')
                    else:
                        pass
                    running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                    # print('%s 重新搜索 %s 按钮 %d 次' % (running_time, target_img_name, search_times))
                    logging.info('%s 重新搜索 %s 按钮 %d 次' % (running_time, target_img_name, search_times))
                    # 判断当不用截图的target节点当search_again_times大于1时重试时触发截图
                    if need_screenShot is False:
                        self.ld.ScreenShot_Ld(self.index)
                    if search_times == search_again_times:
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        # print('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                        logging.error('%s 未能找到需要的 %s 按钮！' % (running_time, target_img_name))
                else:
                    # 按钮坐标
                    x = find_result['result'][0]
                    y = find_result['result'][1]
                    # 点击坐标
                    tap_x = x - moveX
                    tap_y = y - moveY
                    for t in range(tap_times):
                        tap = t + 1
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        print('%s 找到按钮: %s [%d , %d]  ' % (running_time, target_img_name, x, y))
                        if tap_interval != 0:
                            tap_interval_time = int(tap_interval * 2)
                            for s in range(tap_interval_time + 1):
                                running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                                half_second = s
                                s_10, d = divmod(half_second, 10)
                                print("\r%s %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                                if s == tap_interval_time:
                                    pass
                                else:
                                    time.sleep(0.5)
                            print('\n', end='')
                        else:
                            pass
                        self.ld.inputTap(Console_Action.index, tap_x, tap_y)
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        print('%s 已点击按钮 %s [%d , %d] %d次' % (running_time, target_img_name, tap_x, tap_y, tap))
                    break
            if result[0]:
                break
        return result

    def LdactionTapV4(self, target_name: str, tap_info: dict):
        """
        【查找对应按钮】
        :param tap_info:
        :param target_name: 点击事件查询的按钮target
        """
        running_time = time.time() - start_time
        running_time = time.strftime("%H:%M:%S", time.gmtime(running_time))
        tap_result_check_name = tap_info['tap_result_check_name']
        before_tap_wait_time = tap_info['before_tap_wait_time']
        after_tap_wait_time = tap_info['after_tap_wait_time']
        beginning_content = tap_info['beginning_content']
        end_content = tap_info['end_content']
        error_content = tap_info['error_content']
        print('%s +++++START+++++ [目标%s: %s %s]' % (running_time, target_name, beginning_content, running_script))
        if before_tap_wait_time != 0:
            do_before_tap_wait_time = int(before_tap_wait_time * 2)
            for s in range(do_before_tap_wait_time + 1):
                running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                half_second = s
                s_10, d = divmod(half_second, 10)
                print("\r%s %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                if s == do_before_tap_wait_time:
                    pass
                else:
                    time.sleep(0.5)
            print('\n', end='')
        else:
            pass
        result = True
        while result:
            # 点击target
            find_result = self.find_target_imgV4(target_name, tap_info)
            # 判断点击方法处理结果
            if find_result[0]:
                # 处理点击后等待时间
                if after_tap_wait_time != 0:
                    do_after_tap_wait_time = int(after_tap_wait_time * 2)
                    for s in range(do_after_tap_wait_time + 1):
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        half_second = s
                        s_10, d = divmod(half_second, 10)
                        print("\r%s %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                        if s == do_after_tap_wait_time:
                            pass
                        else:
                            time.sleep(0.5)
                    print('\n', end='')
                else:
                    pass
                # 判断是否有校验对象
                if tap_result_check_name is not None:
                    # 赋值未校验标记
                    is_not_Exist_result = True
                    # 当有校验对象后执行循环校验
                    while is_not_Exist_result:
                        # 校验前截图
                        self.ld.ScreenShot_Ld(self.index)
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        # print('%s 开始校验 %s' % (running_time, tap_result_check_name))
                        logging.info('%s 开始校验 %s' % (running_time, tap_result_check_name))
                        # 执行校验
                        tap_result = self.isExistV2(tap_result_check_name)
                        # 判断校验结果，通过后结束校验
                        if tap_result[0]:
                            running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                            # print('%s 校验通过' % running_time)
                            logging.info('%s 校验通过' % running_time)
                            is_not_Exist_result = False
                            break
                        # 不通过重新点击target
                        elif tap_result[0] is False:
                            running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                            # print('%s 点击后结果校验不通过，重新点击！' % running_time)
                            logging.error('%s 点击后结果校验不通过，重新点击！' % running_time)
                            # 为了校验执行后重新点击target，需启动截图
                            need_screenShot_tap_info = tap_info.copy()
                            need_screenShot_tap_info.update({'need_screenShot': True})
                            find_result = self.find_target_imgV4(target_name, need_screenShot_tap_info)
                            # 判断重新点击target后的结果
                            if find_result[0]:
                                # 点击target成功后将会重新执行校验
                                # print('重新点击 %s 成功' % target_name)
                                logging.info('重新点击 %s 成功' % target_name)
                                if after_tap_wait_time != 0:
                                    do_after_tap_wait_time = int(after_tap_wait_time * 2)
                                    for s in range(do_after_tap_wait_time + 1):
                                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                                        half_second = s
                                        s_10, d = divmod(half_second, 10)
                                        print("\r%s %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                                        if s == do_after_tap_wait_time:
                                            pass
                                        else:
                                            time.sleep(0.5)
                                    print('\n', end='')
                                else:
                                    pass
                            # 点击target失败也将重新执行校验
                            else:
                                # print('当前界面未能找到 %s ，需重新校验 %s' % (target_name, tap_result_check_name))
                                logging.error(
                                    '当前界面未能找到 %s ，需重新校验 %s' % (target_name, tap_result_check_name))
                    if is_not_Exist_result is False:
                        break
                    else:
                        pass
                # 无校验的直接结束点击处理
                else:
                    break
            else:
                print(error_content)
                # 暂停后手动操作
                result_action = win32api.MessageBox(0, "自动执行异常请检查！请手动调整！是再次重试/否跳过此步骤:", "提醒",
                                                    win32con.MB_TOPMOST | win32con.MB_YESNO)
                if result_action == win32con.IDNO:
                    logging.info('选择了跳过 %s 步骤' % target_name)
                    break
                elif result_action == win32con.IDYES:
                    tap_info.update({'need_screenShot': True})
                    logging.info('重置截图触发为启动截图')
        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
        print('%s +++++ END +++++ [%s]\n' % (running_time, end_content))

    def is_all_ExistV2(self, target_name: str):
        """
        【判断多个指定图片是否包含在截图中】
        :param target_name:
        :return: 图片查询结果和包含详情
        """
        target_yaml = target_yaml_data[target_name]
        target_img_name = target_yaml['target_img_name_list'][0]
        rgb = target_yaml['rgb']
        threshold = target_yaml['threshold']
        screenshot_img = aircv.imread(self.screenshot_img_file)
        target_img_name_file = self.ld.target_path + target_img_name
        target_img = aircv.imread(target_img_name_file)
        result = aircv.find_all_template(screenshot_img, target_img, rgb=rgb, threshold=threshold)
        if result is None:
            return False, result
        else:
            return True, result

    def List_selectV2(self, target_name: str):
        """
        【从人形列表选择人形】
        :param self:
        :param target_name:
        """
        list_result = self.is_all_ExistV2(target_name)[1]
        # print(list_result)
        # print(len(list_result))
        if list_result is not False:
            for result in list_result:
                coordinate = result['result']
                x = coordinate[0] - 100
                y = coordinate[1] + 200
                print('选中人形 X: %s  Y: %s' % (x, y))
                self.ld.inputTap(Console_Action.index, x, y)
                time.sleep(0.5)

def tap_dict(data_dict: dict):
    """

    :param data_dict:
    """
    # print("dict:")
    for i in data_dict:
        if 'get' in i:
            get_def = data_dict[i]
            def_info_dict(i)
            drive_yaml(get_def)
            continue

def screenShot_dict(data_dict: dict):
    """

    :param data_dict:
    """
    isExist_target_key_result = False
    run_yaml = ''
    for key in data_dict:
        isExist_target_key = key
        isExist_target_value = data_dict[key]
        # print(isExist_target_key)
        if isExist_target_key == 'else':
            running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
            # print("%s 截图查询[ %s ]元素不存在！" % (running_time, data_dict.keys()))
            dict_keys = list(data_dict.keys())
            logging.warning("%s 截图查询 %s 元素不存在！" % (running_time, dict_keys))
            if isExist_target_value == 'exit':
                logging.debug('执行exit方法')
                return True
                # sys.exit()
            elif isExist_target_value == 'break':
                logging.debug('执行break方法')
                break
            else:
                running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                # print("%s 执行else方法" % running_time)
                logging.debug("执行else方法")
                tap_list(isExist_target_value)
        elif Console_Action.isExistV2(isExist_target_key)[0]:
            running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
            # print("%s 包含: %s" % (running_time, isExist_target_key))
            logging.debug("%s 包含: %s" % (running_time, isExist_target_key))
            # print(isExist_target_key, isExist_target_value)
            isExist_target_key_result = True
            run_yaml = isExist_target_value
            break
    if isExist_target_key_result:
        # 截图对象查询到后，判断后续处理对象还是
        if type(run_yaml) == dict:
            screenShot_dict(run_yaml)
        else:
            if drive_yaml(run_yaml):
                return True

def tap_info_dict(target: str, data_dict: dict):
    """

    :param target:
    :param data_dict:
    :return:
    """
    # 获取target中的默认的beginning_content与end_content值
    target_yaml = target_yaml_data[target]
    # 默认初始化的tap_info
    default_tap_info: dict = battle_yaml_data['default_tap_info']
    tap_info = default_tap_info.copy()
    tap_info.update(target_yaml)
    if data_dict is None:
        return tap_info
    else:
        tap_info.update(data_dict)
        return tap_info

def def_info_dict(data_dict: dict):
    """

    :param data_dict:
    :return:
    """
    fun: bool = True
    adv_retire: bool = True
    def_info: dict = {}
    def_info.update(data_dict)
    return def_info

def tap_list(data_target_list: list, data_info: dict = None):
    """

    :param data_info:
    :param data_target_list:
    """
    # print("list:")
    for data_target in data_target_list:
        for target in data_target:
            # 是否截图判断target存在
            if target == 'screenShot':
                if data_target[target] is None:
                    Console.ScreenShot_Ld(Console_Action.index)
                    break
                else:
                    Console.ScreenShot_Ld(Console_Action.index)
                    screenShot_dict_info = data_target[target]
                    # print('%s: %s' % (key, isExist_target[key]))
                    if screenShot_dict(screenShot_dict_info):
                        return True
            # 是否是驱动方法
            elif 'get_' in target:
                get_def_info = data_target[target]
                def_info = def_info_dict(get_def_info)
                get_def_yaml = battle_yaml_data[target]
                drive_yaml_result = drive_yaml(get_def_yaml, def_info)
                if drive_yaml_result:
                    return True
                elif drive_yaml_result is False:
                    return False
            elif 'round_' in target:
                round_times = data_target[target]
                round_def_yaml = battle_yaml_data[target]
                for i in range(round_times):
                    round_result = drive_yaml(round_def_yaml)
                    if round_result:
                        return True
                    elif round_result is False:
                        return False
                    else:
                        pass
            elif target == 'adv_retire':
                if data_info['adv_retire']:
                    tap_list(data_target[target])
            elif target == 'sleep':
                sleep_time = data_target[target]
                if sleep_time != 0:
                    do_sleep_time = int(sleep_time * 2)
                    for s in range(do_sleep_time + 1):
                        running_time = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
                        half_second = s
                        s_10, d = divmod(half_second, 10)
                        print("\r%s 请稍等 %-30s" % (running_time, 'o' * s_10 + '.' * d), end="")
                        if s == do_sleep_time:
                            pass
                        else:
                            time.sleep(0.5)
                    print('\n', end='')
                else:
                    pass
            elif target == 'swipe':
                swipe_info = data_target[target]
                x0 = swipe_info[0]
                y0 = swipe_info[1]
                x1 = swipe_info[2]
                y1 = swipe_info[3]
                ms = swipe_info[4]
                Console.inputSwipe(Console_Action.index, x0, y0, x1, y1, ms)
            elif target == 'loop':
                pass
            elif target == 'goto':
                pass
            else:
                if data_target[target] == 'list':
                    Console_Action.List_selectV2(target)
                else:
                    tap_info = tap_info_dict(target, data_target[target])
                    # if Ld.LdactionTapV4(target, tap_info):
                    #     return True
                    Console_Action.Action_Tap_V4(target, tap_info)

def drive_yaml(data, data_info: dict = {}, fun_return: bool = False):
    """

    :param fun_return:
    :param data_info:
    :param data:
    """
    if type(data) == dict:
        tap_dict(data)
    elif type(data) == list:
        if tap_list(data, data_info):
            return True
        if 'fun_return' in data_info.keys():
            data_info_result = data_info['fun_return']
            if data_info_result:
                return True
            else:
                return False
    else:
        pass

def test():
    """
    【调试测试类】
    :return:
    """
    # get_into_mission = battle_yaml_data['get_into_mission']
    # get_13_4 = battle_yaml_data['get_13_4']
    # drive_yaml(get_into_mission)
    # for i in range(2):
    #     drive_yaml(get_13_4)
    if Console_Action.isExistV2('end_fighting'):
        print("True")
    sys.exit()

class Job(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(Job, self).__init__(*args, **kwargs)
        self.__flag = threading.Event()  # 用于暂停线程的标识
        self.__flag.set()  # 设置为True
        self.__running = threading.Event()  # 用于停止线程的标识
        self.__running.set()  # 将running设置为True

    # def run(self):
    #     while self.__running.isSet():
    #         print("thread is run")
    #         self.__flag.wait()  # 为True时立即返回, 为False时阻塞直到self.__flag为True后返回
    #         print("run = ", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
    #         time.sleep(1)

    def pause(self):
        logging.debug("thread is pause")
        self.__flag.clear()  # 设置为False, 让线程阻塞

    def resume(self):
        logging.debug("thread is resume")
        self.__flag.set()  # 设置为True, 让线程停止阻塞

    def stop(self):
        logging.debug("thread is stop")
        self.__flag.set()  # 将线程从暂停状态恢复, 如何已经暂停的话
        self.__running.clear()  # 设置为False

def run_config():
    global Console, Console_Action, target_yaml_data, battle_yaml_data, get_into_mission, select_battle_name, battle_name_1, battle_name_2, \
        end_combat_1, end_combat_2
    Dc = Dnconsole(r'C:\leidian\LDPlayer9')
    Ld = Ldaction(0, Dc, 'screenshot_tmp.png')
    # print(Dc.workspace_path)
    logging.debug(Dc.workspace_path)
    target_yaml_data = Dc.YAML('target.yaml')
    # 载入关卡配置
    logging.info('开始加载关卡配置')
    battle_name = '13_4.yaml'
    battle_yaml_data = Dc.YAML(battle_name)
    get_into_mission = battle_yaml_data['get_into_mission']
    select_13_4 = battle_yaml_data['get_select_13_4']
    battle_13_4_1 = battle_yaml_data['battle_13_4_1']
    battle_13_4_2 = battle_yaml_data['battle_13_4_2']
    end_combat_1 = battle_yaml_data['end_combat_1']
    end_combat_2 = battle_yaml_data['end_combat_2']

def battle():
    global running_script, start_time
    user_action = True
    while user_action:
        try:
            runtimes = int(input("跑几圈:"))
        except ValueError:
            print("请正确输入圈数,未输入将默认执行1次")
            runtimes = 1
        try:
            runtime = float(input("跑多久(分钟）:"))
        except ValueError:
            print("请正确输入执行时间，未输入将默认执行999分钟限制")
            runtime = 999
        finally:
            runtime_min = runtime * 60
        running_script = '共 %d 次，开始执行' % runtimes
        start_time = time.time()
        if drive_yaml(get_into_mission):
            continue
        ran_time: float = 0
        if runtimes == 1:
            drive_yaml(select_battle_name)
            drive_yaml(battle_name_1)
            drive_yaml(end_combat_1)
        elif runtimes > 1:
            # 执行次数大于1，默认执行再次战斗流程
            fight_again: bool = True
            drive_yaml(select_battle_name)
            for is_runtimes in range(runtimes):
                is_runtimes_num = is_runtimes + 1
                running_script = '共 %d 次，正在执行第 %d 次' % (runtimes, is_runtimes_num)
                if is_runtimes_num > 1 and fight_again is True:
                    drive_yaml(battle_name_2)
                elif is_runtimes_num == 1 and fight_again is True:
                    drive_yaml(battle_name_1)
                elif is_runtimes_num > 1 and fight_again is False:
                    drive_yaml(battle_name_1)
                end_time = time.time()
                ran_time = end_time - start_time
                if ran_time < runtime_min and is_runtimes_num < runtimes:
                    fight_again = True
                    end_result = drive_yaml(end_combat_2)
                    # 【结束流程】校验执行了特殊方法后判断，是否是使用再次战斗执行的战斗流程
                    if end_result:
                        fight_again = False
                    end_time = time.time()
                    ran_time = end_time - start_time
                    ran_time_m, ran_time_s = divmod(ran_time, 60)
                    ran_time_h, ran_time_m = divmod(ran_time_m, 60)
                    logging.info('共 %d 次，已执行 %d 次, 已运行 %02d 时 %02d 分 %02d 秒\n' % (
                        runtimes, is_runtimes_num, ran_time_h, ran_time_m, ran_time_s))
                    # print('共 %d 次，已执行 %d 次, 已运行 %02d 时 %02d 分 %02d 秒\n' % (
                    #     runtimes, is_runtimes_num, ran_time_h, ran_time_m, ran_time_s))
                elif ran_time < runtime_min and is_runtimes_num == runtimes:
                    drive_yaml(end_combat_1)
                    end_time = time.time()
                    ran_time = end_time - start_time
                    ran_time_m, ran_time_s = divmod(ran_time, 60)
                    ran_time_h, ran_time_m = divmod(ran_time_m, 60)
                    logging.info('共 %d 次，已执行 %d 次, 已运行 %02d 时 %02d 分 %02d 秒\n' % (
                        runtimes, is_runtimes_num, ran_time_h, ran_time_m, ran_time_s))
                else:
                    drive_yaml(end_combat_1)
                    end_time = time.time()
                    ran_time = end_time - start_time
                    ran_time_m, ran_time_s = divmod(ran_time, 60)
                    ran_time_h, ran_time_m = divmod(ran_time_m, 60)
                    logging.info('共 %d 次，已执行 %d 次, 已运行 %02d 时 %02d 分 %02d 秒\n' % (
                        runtimes, is_runtimes_num, ran_time_h, ran_time_m, ran_time_s))
                    break
        result_action = win32api.MessageBox(0, "是否继续跑步机?", "提醒", win32con.MB_TOPMOST | win32con.MB_YESNO)
        # action = input('是否继续跑步机?enter后继续/输入exit退出: ')
        # if action == 'exit':
        #     break
        # else:
        #     pass
        if result_action == win32con.IDYES:
            logging.info('继续执行跑步机')
            pass
        else:
            logging.info('结束跑步机')
            break
        # os.system('pause')  # 按任意键继续

if __name__ == '__main__':
    CON_LOG = 'logger.conf'  # 配置文件路径
    logging.config.fileConfig(CON_LOG)  # '读取日志配置文件'
    logger = logging.getLogger('exampleLogger')  # 创建一个日志器logger
    workspace = os.getcwd()
    global Console, Console_Action, target_yaml_data, battle_yaml_data, get_into_mission, select_battle_name, battle_name_1, battle_name_2, \
        end_combat_1, end_combat_2, running_script, start_time
    # base_run = threading.Thread(target=run_config)
    # battle_run = threading.Thread(target=battle)
    # 基础数据和环境初始化
    run_config()
    # 启动战斗运行
    battle_run = Job(target=battle)
    battle_run.start()
    battle_run.join()

    # battle_run.pause()

    # AppPackage = 'com.sunborn.girlsfrontline.cn'
    # App = '少女前线'
    # if Dc.isrunning(0):
    #     print('模拟器已启动')
    #     time.sleep(5)
    # else:
    #     Dc.launch(0)
    #     print('模拟器启动中')
    #     time.sleep(15)
    #
    # if Dc.isruningAPP(0, AppPackage):
    #     print('%s is running' % AppPackage)
    # else:
    #     Dc.runApp(0, AppPackage)
    #     print('%s is ran' % AppPackage)
    #
    # time.sleep(10)
    # print('%s 已启动' % App)

    # # 登录（暂不支持需要输入密码）
    # Ld.LdactionTap(0, Dc, r'screenshout_tmp.png', r'sign_in.png', sleeptime=3)
    # # 开始游戏，进入主界面
    # Ld.LdactionTap(0, Dc, r'screenshout_tmp.png', r'start_game.png')

    # 调试方法
    # start_time = time.time()
    # running_script = '1'
    # test()


#! /usr/bin/env python
# coding=utf-8
"""
Author: Deean
Date: 2022-10-26 22:25
FileName: algorithm/P2379. 得到 K 个黑块的最少涂色次数.py
Description:
"""

class Solution:
    def minimumRecolors(self, blocks: str, k: int) -> int:
        minimum = len(blocks)
        for i in range(len(blocks) - k + 1):
            minimum = min(minimum, blocks[i:i + k].count("W"))
        return minimum

if __name__ == '__main__':
    print(Solution().minimumRecolors(blocks="WBBWWBBWBW", k=7))

P2379. 得到 K 个黑块的最少涂色次数.py