线程类运行一个长函数,如果需要完全停止

时间:2016-02-27 18:00:26

标签: python multithreading pygame

我刚刚改写了几个月前制作的简单游戏,并将AI处理放在后台线程中,这样它就不会冻结游戏。但是,我注意到如果在AI处理期间启动新游戏,则第一个线程与第一个线程一起启动(如果另一个新游戏启动,则启动第三个线程),如果有一个问题,计算需要很长时间。

我查了一些关于如何结束线程的例子,但是如果它运行无限循环,它们似乎都是如何在循环结束时干净地停止它。如果我可以设置代码来完全停止执行并忘记所有内容,我更喜欢,但如果不可能,我想听听最好的方法是什么。

以下是我使用的基本课程:

class ThreadHelper(Thread):
    """Run a function in a background thread."""
    def __init__(self, function, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        Thread.__init__(self)
        self.function = function

    def run(self):
        self.function(*self.args, **self.kwargs)

这是游戏中用来运行线程的函数,self.game._ai_stateself.game._ai_move在完成后会发生变化:

def run_ai(self):
    self.game._ai_move = self.game._ai_state = None
    ThreadHelper(self.game.ai.calculate_move, 
                 self.game._player, 
                 difficulty=self.game._player_types[self.game._player - 1], 
                 player_range=self.game._range_players).start()

所以基本上它就像键入函数一样简单,然后是任何args / kwargs给函数。

以下内容是AI类中的功能,我不想通过在整个过程中添加十几个检查来使其更加混乱。我认为大多数处理都在find_far_neighbour完成,但我不确定它是否就是这样。

def check_cell(self, cell_id, grid=None, player=None):
    """Check how many points a cell has in the grid."""
    if grid is not None:
        self._temp_core.grid = grid
        total, calculations = self._temp_core._point_score(cell_id, player, quick=True)
    else:
        total, calculations = self.game.core._point_score(cell_id, player, quick=True)
    try:
        self.calculations += calculations
    except AttributeError:
        pass
    return total


def find_best_cell(self, player):
    """Get maximum number of points that can be gained for a player
    from each empty cell.
    """
    max_points = defaultdict(int)
    filled_grid = bytearray(i if i else player for i in self.game.core.grid)
    for cell_id in self.game.core._range_lg:
        if filled_grid[cell_id] == player and not self.game.core.grid[cell_id]:
            max_points[cell_id] = self.check_cell(cell_id, filled_grid)

    return get_max_keys(max_points)

def find_close_neighbour(self, player_range, grid=None):
    """Find all places where anyone has n-1 points in a row, by 
    substituting in a point for each player in every cell.
    """
    new_grid = bytearray(self.game.core.grid if grid is None else grid)

    matches = defaultdict(list)
    for cell_id in self.game.core._range_lg:
        if not new_grid[cell_id]:
            for player in player_range:
                if self.check_cell(cell_id, grid, player):
                    matches[player].append(cell_id)

    return matches

def find_far_neighbour(self, player_range):
    """Look two moves ahead to detect if someone could complete a row.
    Uses the find_close_neighbour function from within a loop.
    """

    #Initial check
    initial_match = self.find_close_neighbour(player_range=player_range)
    match_cells = []

    #Make list of all cells so far to avoid duplicates
    for k, v in initial_match.iteritems():
        match_cells += v
    match_cells = set(match_cells)

    #For every grid cell, substitute a player into it, then do the check again
    grid = bytearray(self.game.core.grid)
    matches = defaultdict(list)
    for i in self.game.core._range_lg:
        if not self.game.core.grid[i]:
            old_value = grid[i]
            for player in player_range:
                grid[i] = player
                #match = self.find_close_neighbour(player_range=player_range, grid=grid)
                match = self.find_close_neighbour(player_range=[player], grid=grid)
                if match:
                    for k, v in match.iteritems():
                        matches[k] += [cell for cell in v if cell not in match_cells or v.count(cell) > 1]

            grid[i] = old_value

    return initial_match, matches


def calculate_move(self, player, difficulty=None, player_range=None):
    """Uses the possible moves to calculate an actual move to make.
    This is the part that can be changed for different behaviour.

    The outcome depends a lot on chance, but taking the 'extreme'
    AI as an example since it has no chance, it only depends on 2
    things: the n-1 possible moves, and n-2 possible moves.

    Current order of priorities:
    1. Block any n-1 rows
    2. Block any n-2 advanced moves
    3. Complete any n-1 rows
    4. Continue any n-2 advanced moves
    5. Continue any n-2 rows
    6. Block any n-2 rows
    7. Make a predictive placement
    8. Make a random placement

    An n-1 row is where the row is one cell of being completed.
    An n-2 row is where a row is two cells from being completed.
    An advanced move is where once cell results in two n-2 rows
    becoming n-1 rows, which is impossible to block.

    If the grid is full, an error will be thrown, since this should
    not be running
    """
    self.game._ai_running = True

    #Default to 2 players
    if player_range is None:
        player_range = (1, 2)

    chance_tactic, chance_ignore_near, chance_ignore_far = self.difficulty(difficulty)

    total_moves = len([i for i in self.game.core.grid if i])
    self.calculations = 0
    next_moves = []

    self.game._ai_text = ['AI Objective: Calculating']
    self.game._ai_state = None
    self.game._ai_move = None
    ai_text = self.game._ai_text.append

    #It is possible the first few moves since they need the most calculations
    #This is disabled for now though as the AI runs a lot faster
    non_dangerous_skip = total_moves >= (self.game.core.size - 2) * len(player_range)

    if True:

        #Calculate move
        close_matches, far_matches = self.find_far_neighbour(player_range=player_range)
        del self.game._ai_text[0]
        ai_text('Urgent: {}'.format(bool(close_matches)))


        #Chance of things happening
        chance_ignore_near **= pow(total_moves / self.game.core._size_cubed, 0.25)
        chance_notice_basic = random.uniform(0, 100) > chance_ignore_near
        chance_notice_far = random.uniform(0, 100) > chance_ignore_far
        chance_notice_advanced = min(random.uniform(0, 100), random.uniform(0, 100)) > chance_ignore_far

        #Count occurances, and store the overall total in move_count_player[0]
        move_count_player = defaultdict(dict)
        move_count_player[0] = defaultdict(int)
        move_count_advanced = defaultdict(list)
        for k, v in far_matches.iteritems():
            if k not in move_count_player:
                move_count_player[k] = defaultdict(int)
            for i in v:
                move_count_player[0][i] += 1
                move_count_player[k][i] += 1
            for k2, v2 in move_count_player[k].iteritems():
                if v2 > 1:
                    move_count_advanced[k] += [k2] * (v2 - 1)

        #Check if there actually are any advanced moves to make
        advanced_move = any(v > 1 for k, v in move_count_player[0].iteritems())

        #First try block an enemy advanced move, then do own
        #Then do the move that would block an enemy and gain a point at the same time
        advanced_move_type = 0
        if advanced_move and chance_notice_advanced:
            next_moves_total = move_count_advanced[0]
            next_moves_player = move_count_advanced[player]
            del move_count_advanced[player]
            del move_count_advanced[0]

            #Enemy moves
            for k, v in move_count_advanced.iteritems():
                if k:
                    next_moves = move_count_advanced[k]
                    self.game._ai_state = 'Forward Thinking (Blocking Opposition)'
                    advanced_move_type = 1

            #Own moves
            if next_moves_player:
                if not next_moves or random.uniform(0, 100) < chance_tactic:
                    next_moves = next_moves_player
                    self.game._ai_state = 'Forward Thinking (Gaining Points)'
                    advanced_move_type = 2

            #Leftover moves
            if next_moves_total and not next_moves:
                next_moves = next_moves_player
                self.game._ai_state = 'Forward Thinking'
                advanced_move_type = 3


        #Check for any n-1 points
        #Block enemy first then gain points
        basic_move_type = 0
        if close_matches and chance_notice_basic:

            already_moved = bool(next_moves)

            if close_matches[player]:
                if advanced_move_type != 1 or not next_moves:
                    next_moves = close_matches[player]
                    self.game._ai_state = 'Gaining Points'
                    basic_move_type = 1

            enemy_moves = []
            for k, v in close_matches.iteritems():
                if k != player:
                    enemy_moves += v

            if enemy_moves:
                if not next_moves or not random.uniform(0, 100) < chance_tactic:

                    #If there is a move to block and gain at the same time
                    if basic_move_type == 1:
                        mixed_moves = [i for i in enemy_moves if i in close_matches[player]]
                        if mixed_moves:
                            enemy_moves = mixed_moves

                    next_moves = enemy_moves
                    self.game._ai_state = 'Blocking Opposition'


        #Check for any n-2 points
        #Gain points first then block enemy
        elif not next_moves and chance_notice_far:
            next_moves = far_matches[player]
            self.game._ai_state = 'Looking Ahead (Gaining Points)'

            enemy_moves = []
            for k, v in far_matches.iteritems():
                if k != player:
                    enemy_moves += v

            if enemy_moves:
                if not next_moves or random.uniform(0, 100) < chance_tactic:
                    next_moves = []
                    for k, v in far_matches.iteritems():
                        next_moves += v
                    self.game._ai_state = 'Looking Ahead (Blocking Opposition)'


        if not self.game._ai_state:
            if not chance_notice_basic and not chance_notice_advanced:
                ai_text("AI missed something.")
            self.game._ai_state = False


    #Make a semi random placement
    if (not next_moves or not self.game._ai_state) and random.uniform(0, 100) > chance_ignore_far:
        next_moves = self.find_best_cell(player)
        self.game._ai_state = 'Predictive placement'

    #Make a totally random move
    else:
        next_moves = [i for i in self.game.core._range_lg if not self.game.core.grid[i]]
        self.game._ai_state = 'Random placement'

    ai_text('AI Objective: {}.'.format(self.game._ai_state))
    n = random.choice(next_moves)

    potential_moves = 'Potential Moves: {}'.format(next_moves)
    if len(potential_moves) > 40:
        potential_moves = potential_moves[:37] + '...'
    ai_text(potential_moves)

    self.game._ai_move = random.choice(next_moves)

    ai_text('Chosen Move: {}'.format(self.game._ai_move))
    ai_text('Calculations: {}'.format(self.calculations + 1))

    #print state, self.game._ai_text
    self.game._ai_running = False
    return self.game._ai_move

0 个答案:

没有答案