我刚刚改写了几个月前制作的简单游戏,并将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_state
和self.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