diff --git a/.gitignore b/.gitignore index 6955cf9..2ab5adf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -__pycache__/ -idea.km +__pycache__/ +idea.km idea.png \ No newline at end of file diff --git a/ai.py b/ai.py new file mode 100644 index 0000000..8e8d8ac --- /dev/null +++ b/ai.py @@ -0,0 +1,84 @@ +from data_structure import Tree + +class Ai: + def __init__(self): + self.path = [] + + def start(self,snake,apple,settings): + openlist = [] + closelist = [] + starting_point = snake.coords[0] + ending_point = apple.coords + + + def calculate_f(): + """计算openlist各格子的f值""" + for cell in openlist: + g = abs(cell['x'] - starting_point['x']) + abs(cell['y'] - starting_point['y']) + h = abs(cell['x'] - ending_point['x']) + abs(cell['y'] - ending_point['y']) + f = g + h + cell['f'] = f + + + def is_wall(): + # 碰到左右墙壁 + if (cell['x'] == -1 or cell['x'] == settings.cell_w): + return True + # 碰到上下墙壁 + if (cell['y'] == -1 or cell['y'] == settings.cell_h): + return True + # 碰到自己 + if (cell in snake.coords[1:]): + return True + return False + + + #记得做openlist用尽、苹果在蛇里面的解决办法 + while ending_point not in openlist: + if not openlist: + openlist.append(starting_point) + path_tree = Tree(starting_point) + #查找Openlist中F最小的格子并弹入Closelist + #《这里也有问题 假设下面出现的列表同步更改的错误解决了》 + #《那么本来就被计算过的格子不需要再次计算 但这里依然算了》 + calculate_f() + min_f = min(f['f'] for f in openlist) + for cell in openlist: + if cell['f'] == min_f: + closelist.append(cell) + openlist.remove(cell) + break + #《这里有问题 不应该for 应该只检查刚丢进closelist的那一个》 + #《检查过的格子又检查一遍 也是openlist有重复格子的根本原因》 + for close_cell in closelist: + near_cells = [{'x': close_cell['x'], 'y': close_cell['y'] + 1}, #获取旁边的格子上右下左 + {'x': close_cell['x'] + 1, 'y': close_cell['y']}, + {'x': close_cell['x'], 'y': close_cell['y'] - 1}, + {'x': close_cell['x'] - 1, 'y': close_cell['y']}] + #不考虑蛇的身体或墙壁 + for cell in near_cells: + if is_wall(): + near_cells.remove(cell) + #排除F键值对的影响 + #《?为什么使用切片后copy的更改还是能影响到原openlist?》 + #《搞得我openlist的f值全弹出了》 + openlist_copy = openlist[:] + for cell_copy in openlist_copy: + cell_copy.pop('f',None) + #找出不在openlist的格子并加进openlist + #《filter疑似用错 没有筛选功能 openlist依然有重复格子》 + not_in_list = filter(lambda x: x != (cell_copy for cell_copy in openlist_copy), near_cells) + openlist.extend(not_in_list) + #《这里再用一次filter查教程后是因为filter只能迭代一次》 + #《filter好jb难用 我是因为看它性能消耗少才用的QAQ 换一个吧》 + not_in_list = filter(lambda x: x != (cell_copy for cell_copy in openlist_copy), near_cells) + + #《这里的添加节点能运行但是加不进去 只有一开始的父节点加得进去》 + for not_in_cell in not_in_list: + path_tree.add_child(path_tree.find(close_cell),not_in_cell) + + + ending_nodes = path_tree.find(ending_point) + for node in ending_nodes.get_ancestors()[::-1]: + self.path.append(node) + self.iterator = snake.create_iterator(self.path) \ No newline at end of file diff --git a/apple.py b/apple.py index a0a9473..60a17e8 100644 --- a/apple.py +++ b/apple.py @@ -1,11 +1,11 @@ -import random - - -class Apple(): - def __init__(self,settings): - self.x = random.randint(1, settings.cell_w - 1) - self.y = random.randint(1, settings.cell_h - 1) - - def spawn(self,settings): - self.x = random.randint(1, settings.cell_w - 1) +import random + + +class Apple(): + def __init__(self,settings): + self.x = 7#random.randint(1, settings.cell_w - 1) + self.y = 5#random.randint(1, settings.cell_h - 1) + self.coords = {'x':self.x,'y':self.y} + def spawn(self,settings): + self.x = random.randint(1, settings.cell_w - 1) self.y = random.randint(1, settings.cell_h - 1) \ No newline at end of file diff --git a/data_structure.py b/data_structure.py new file mode 100644 index 0000000..25f3eb5 --- /dev/null +++ b/data_structure.py @@ -0,0 +1,53 @@ +class TreeNode: + def __init__(self, value, parent=None): + self.value = value + self.parent = parent + self.children = [] + + def add_child(self, child_node): + child_node.parent = self + self.children.append(child_node) + + def remove_child(self, child_node): + self.children = [child for child in self.children if child is not child_node] + child_node.parent = None + + def __repr__(self, level=0): + ret = "\t" * level + repr(self.value) + "\n" + for child in self.children: + ret += child.__repr__(level + 1) + return ret + + def get_ancestors(self): + """返回当前节点及其所有祖先节点""" + ancestors = [] + node = self + ancestors.append(node) + while node.parent: + ancestors.append(node.parent) + node = node.parent + return ancestors + +class Tree: + def __init__(self, root_value): + self.root = TreeNode(root_value) + + def add_child(self, parent_node, child_value): + if not parent_node: + parent_node = self.root + parent_node.add_child(TreeNode(child_value)) + + def find(self, value): + return self._find_in_node(self.root, value) + + def _find_in_node(self, node, value): + if node.value == value: + return node + for child in node.children: + found_node = self._find_in_node(child, value) + if found_node: + return found_node + return None + + def __repr__(self): + return repr(self.root) \ No newline at end of file diff --git a/game_functions.py b/game_functions.py index bf74099..46b75ff 100644 --- a/game_functions.py +++ b/game_functions.py @@ -1,168 +1,199 @@ -import pygame -import sys - - -def check_events(settings): - for event in pygame.event.get(): - # 当按关闭或者按ESC键退出游戏 - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - elif event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE: - pygame.quit() - sys.exit() - # 按1键选择手动模式 - elif event.key == pygame.K_1: - settings.game_mode = 1 - # 按2键选择自动模式 - elif event.key == pygame.K_2: - settings.game_mode = 2 - - -def check_play_events(snake): - """检测游戏过程中的按键""" - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - elif event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE: - pygame.quit() - sys.exit() - elif event.key == pygame.K_LEFT and not snake.direction == 'right': #防止反向移动 - snake.direction = 'left' - break - elif event.key == pygame.K_RIGHT and not snake.direction == 'left': - snake.direction = 'right' - break - elif event.key == pygame.K_UP and not snake.direction == 'down': - snake.direction = 'up' - break - elif event.key == pygame.K_DOWN and not snake.direction == 'up': - snake.direction = 'down' - break - - -def show_start(settings, screen): - """模式选择界面""" - title_Font = pygame.font.Font(settings.font_name, 80) # 设置标题字体 - title_image = title_Font.render("贪吃蛇", True, (255, 255, 255), (0, 0, 0)) - title_rect = title_image.get_rect() - title_rect.center = screen.get_rect().center - - presskey_font = pygame.font.Font(settings.font_name, 15) # 设置说明文字的字体 - presskey_image = presskey_font.render( - '按1为手动模式,按2为自动模式,按ESC可退出游戏', True, (255, 255, 255), (0, 0, 0)) - presskey_rect = presskey_image.get_rect() - presskey_rect.centerx = title_rect.centerx - presskey_rect.top = title_rect.bottom - - screen.fill(settings.bg_color) # 绘制屏幕 - screen.blit(title_image, title_rect) # 绘制标题 - screen.blit(presskey_image, presskey_rect) # 绘制说明文字 - while True: - check_events(settings) # 检测键盘 - if settings.game_mode != 0: # 说明按了1或2,退出循环 - break - pygame.display.flip() - - -def show_end(settings, screen): - """失败结算界面""" - title_font = pygame.font.Font(settings.font_name, 80) - game_image = title_font.render('Game', True, (233, 150, 122)) - over_image = title_font.render('Over', True, (233, 150, 122)) - game_rect = game_image.get_rect() - over_rect = over_image.get_rect() - screen_rect = screen.get_rect() - game_rect.midtop = (settings.width / 2, screen_rect.top + 70) - over_rect.midtop = (settings.width / 2, game_rect.bottom + 50) - screen.blit(game_image, game_rect) - screen.blit(over_image, over_rect) - pygame.display.flip() - pygame.time.wait(1500) - settings.game_mode = 0 - - -def draw_grid(settings, screen): - # 绘制横向线 - for x in range(0, settings.width, settings.cell_size): - pygame.draw.line(screen, (40, 40, 40), (x, 0), (x, settings.height)) - # 绘制竖向线 - for y in range(0, settings.height, settings.cell_size): - pygame.draw.line(screen, (40, 40, 40), (0, y), (settings.width, y)) - - -def draw_snake(settings, screen, snake): - # 头部用白色 - head_index = snake.coords[0] - x = head_index['x'] * settings.cell_size - y = head_index['y'] * settings.cell_size - snake_head_rect = pygame.Rect( - x, y, settings.cell_size, settings.cell_size) - pygame.draw.rect(screen, (255, 255, 255), snake_head_rect) - # 蛇身内部用浅绿,外框用深绿 - for bodies_index in snake.coords[1: -1]: - x = bodies_index['x'] * settings.cell_size - y = bodies_index['y'] * settings.cell_size - snake_part_rect = pygame.Rect( - x, y, settings.cell_size, settings.cell_size) - pygame.draw.rect(screen, (0, 155, 0), snake_part_rect) - snake_part_inner_rect = pygame.Rect( - x + 4, y + 4, settings.cell_size - 8, settings.cell_size - 8) - pygame.draw.rect(screen, (0, 255, 0), snake_part_inner_rect) - # 蛇尾用浅绿 - tail_index = snake.coords[-1] - x = tail_index['x'] * settings.cell_size - y = tail_index['y'] * settings.cell_size - snake_tail_rect = pygame.Rect( - x, y, settings.cell_size, settings.cell_size) - pygame.draw.rect(screen, (0, 255, 0), snake_tail_rect) - - -def draw_apple(apple,settings,screen): - x = apple.x * settings.cell_size - y = apple.y * settings.cell_size - apple_rect = pygame.Rect( - x, y, settings.cell_size, settings.cell_size) - pygame.draw.rect(screen, (255, 0, 0), apple_rect) - - -# 绘制游戏界面 -def update_screen(settings, screen, snake,apple): - screen.fill(settings.bg_color) - draw_grid(settings, screen) - draw_apple(apple,settings,screen) - # 移动蛇 - snake.move_head() # 加蛇头 - if game_over(snake,settings): - settings.game_mode = -1 - elif eat_apple(snake,apple): - apple.spawn(settings) - draw_apple(apple,settings,screen) - else: - del snake.coords[-1] # 去蛇尾 - draw_snake(settings, screen, snake) - pygame.display.flip() - # 暂停一下 - settings.my_clock.tick(settings.clock_frq) - - -def game_over(snake,settings): - # 碰到左右墙壁 - if (snake.coords[snake.head_index]['x'] == -1 or snake.coords[snake.head_index]['x'] == settings.cell_w): - return True - # 碰到上下墙壁 - if (snake.coords[snake.head_index]['y'] == -1 or snake.coords[snake.head_index]['y'] == settings.cell_h): - return True - # 碰到自己 - if (snake.coords[snake.head_index] in snake.coords[1:]): - return True - return False - - -def eat_apple(snake,apple): - if (snake.coords[snake.head_index]['x'] == apple.x and snake.coords[snake.head_index]['y'] == apple.y): - return True +import pygame +import sys + +#函数名含义:check events for mode (selecting),下同 +def check_mode_events(settings): + for event in pygame.event.get(): + # 当按关闭或者按ESC键退出游戏 + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + pygame.quit() + sys.exit() + # 按1键选择手动模式 + elif event.key == pygame.K_1: + settings.game_mode = 1 + # 按2键选择自动模式 + elif event.key == pygame.K_2: + settings.game_mode = 2 + + +def check_play_events(snake): + """检测游戏过程中的按键""" + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + pygame.quit() + sys.exit() + elif event.key == pygame.K_LEFT and not snake.direction == 'right': #防止反向移动 + snake.direction = 'left' + break + elif event.key == pygame.K_RIGHT and not snake.direction == 'left': + snake.direction = 'right' + break + elif event.key == pygame.K_UP and not snake.direction == 'down': + snake.direction = 'up' + break + elif event.key == pygame.K_DOWN and not snake.direction == 'up': + snake.direction = 'down' + break + + +def autocheck_events(ai,snake,apple,settings): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + pygame.quit() + sys.exit() + if eat_apple(snake,apple): + ai.start(snake,apple,settings) + +def show_start(settings, screen): + """模式选择界面""" + title_Font = pygame.font.Font(settings.font_name, 80) # 设置标题字体 + title_image = title_Font.render("贪吃蛇", True, (255, 255, 255), (0, 0, 0)) + title_rect = title_image.get_rect() + title_rect.center = screen.get_rect().center + + presskey_font = pygame.font.Font(settings.font_name, 15) # 设置说明文字的字体 + presskey_image = presskey_font.render( + '按1为手动模式,按2为自动模式,按ESC可退出游戏', True, (255, 255, 255), (0, 0, 0)) + presskey_rect = presskey_image.get_rect() + presskey_rect.centerx = title_rect.centerx + presskey_rect.top = title_rect.bottom + + screen.fill(settings.bg_color) # 绘制屏幕 + screen.blit(title_image, title_rect) # 绘制标题 + screen.blit(presskey_image, presskey_rect) # 绘制说明文字 + while True: + check_mode_events(settings) # 检测键盘 + if settings.game_mode != 0: # 说明按了1或2,退出循环 + break + pygame.display.flip() + + +def show_end(settings, screen): + """失败结算界面""" + title_font = pygame.font.Font(settings.font_name, 80) + game_image = title_font.render('Game', True, (233, 150, 122)) + over_image = title_font.render('Over', True, (233, 150, 122)) + game_rect = game_image.get_rect() + over_rect = over_image.get_rect() + screen_rect = screen.get_rect() + game_rect.midtop = (settings.width / 2, screen_rect.top + 70) + over_rect.midtop = (settings.width / 2, game_rect.bottom + 50) + screen.blit(game_image, game_rect) + screen.blit(over_image, over_rect) + pygame.display.flip() + pygame.time.wait(1500) + settings.game_mode = 0 + + +def draw_grid(settings, screen): + # 绘制横向线 + for x in range(0, settings.width, settings.cell_size): + pygame.draw.line(screen, (40, 40, 40), (x, 0), (x, settings.height)) + # 绘制竖向线 + for y in range(0, settings.height, settings.cell_size): + pygame.draw.line(screen, (40, 40, 40), (0, y), (settings.width, y)) + + +def draw_snake(settings, screen, snake): + # 头部用白色 + head_index = snake.coords[0] + x = head_index['x'] * settings.cell_size + y = head_index['y'] * settings.cell_size + snake_head_rect = pygame.Rect( + x, y, settings.cell_size, settings.cell_size) + pygame.draw.rect(screen, (255, 255, 255), snake_head_rect) + # 蛇身内部用浅绿,外框用深绿 + for bodies_index in snake.coords[1: -1]: + x = bodies_index['x'] * settings.cell_size + y = bodies_index['y'] * settings.cell_size + snake_part_rect = pygame.Rect( + x, y, settings.cell_size, settings.cell_size) + pygame.draw.rect(screen, (0, 155, 0), snake_part_rect) + snake_part_inner_rect = pygame.Rect( + x + 4, y + 4, settings.cell_size - 8, settings.cell_size - 8) + pygame.draw.rect(screen, (0, 255, 0), snake_part_inner_rect) + # 蛇尾用浅绿 + tail_index = snake.coords[-1] + x = tail_index['x'] * settings.cell_size + y = tail_index['y'] * settings.cell_size + snake_tail_rect = pygame.Rect( + x, y, settings.cell_size, settings.cell_size) + pygame.draw.rect(screen, (0, 255, 0), snake_tail_rect) + + +def draw_apple(apple,settings,screen): + x = apple.x * settings.cell_size + y = apple.y * settings.cell_size + apple_rect = pygame.Rect( + x, y, settings.cell_size, settings.cell_size) + pygame.draw.rect(screen, (255, 0, 0), apple_rect) + + +# 绘制游戏界面 +def update_screen(settings, screen, snake,apple): + screen.fill(settings.bg_color) + draw_grid(settings, screen) + draw_apple(apple,settings,screen) + # 移动蛇 + snake.move_head() # 加蛇头 + if game_over(snake,settings): + settings.game_mode = -1 + elif eat_apple(snake,apple): + apple.spawn(settings) + draw_apple(apple,settings,screen) + else: + del snake.coords[-1] # 去蛇尾 + draw_snake(settings, screen, snake) + pygame.display.flip() + # 暂停一下 + settings.my_clock.tick(settings.clock_frq) + + +def autoupdate_screen(settings, screen, snake,apple,ai): + screen.fill(settings.bg_color) + draw_grid(settings, screen) + draw_apple(apple,settings,screen) + # 移动蛇 + snake.automove_head(ai) # 加蛇头 + if game_over(snake,settings): + settings.game_mode = -1 + elif eat_apple(snake,apple): + apple.spawn(settings) + draw_apple(apple,settings,screen) + else: + del snake.coords[-1] # 去蛇尾 + draw_snake(settings, screen, snake) + pygame.display.flip() + # 暂停一下 + settings.my_clock.tick(settings.clock_frq) + + +def game_over(snake,settings): + # 碰到左右墙壁 + if (snake.coords[snake.head_index]['x'] == -1 or snake.coords[snake.head_index]['x'] == settings.cell_w): + return True + # 碰到上下墙壁 + if (snake.coords[snake.head_index]['y'] == -1 or snake.coords[snake.head_index]['y'] == settings.cell_h): + return True + # 碰到自己 + if (snake.coords[snake.head_index] in snake.coords[1:]): + return True + return False + + +def eat_apple(snake,apple): + if (snake.coords[snake.head_index] == apple.coords): + return True return False \ No newline at end of file