所以这是我正在做的一个有趣的小项目。这是我目前在这个游戏上的进步:
import random
import time
import pygame
pygame.init()
pygame.font.init()
pygame.display.set_caption('Parsel Tongue')
MARGIN = 60
WINDOW_SIZE = (600, 600 + MARGIN)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
GREEN = (0, 255, 100)
WHITE = (255, 255, 255)
SNAKE_SIZE = 15
SNAKE_SPEED = 1
FONT_SIZE = 30
if WINDOW_SIZE[0] % SNAKE_SIZE != 0 or WINDOW_SIZE[1] % SNAKE_SIZE != 0 or MARGIN % SNAKE_SIZE != 0:
raise Exception('Size of grid should be divisible by SNAKE_SIZE')
class Food:
foods = []
def __init__(self):
self.add_to_list()
self.coords = None
self.count = 0
self.create_food()
def add_to_list(self):
self.foods.append(self)
def remove_from_list(self):
self.foods.remove(self)
def create_food(self):
self.count += 1
check = set(tuple(part[0]) for snake in Snake.snakes for part in snake.body)
self.coords = random.choice([(i, j) for i in range(0, WINDOW_SIZE[0], SNAKE_SIZE) for j in range(MARGIN, WINDOW_SIZE[1], SNAKE_SIZE) if (i, j) not in check])
def display(self, canvas):
pygame.draw.rect(canvas, RED, (self.coords[0] + 1, self.coords[1] + 1, SNAKE_SIZE - 2, SNAKE_SIZE - 2))
class Snake:
snakes = []
def __init__(self, initial_body):
self.add_to_list()
self.body = initial_body.copy()
self.lock_last = False
self.last_key_function = self.prev_key_function = -1
def add_to_list(self):
self.snakes.append(self)
def remove_from_list(self):
self.snakes.remove(self)
def update_key_function(self, keys):
for key in keys:
if key in (pygame.K_w, pygame.K_UP):
self.last_key_function = 0
if key in (pygame.K_s, pygame.K_DOWN):
self.last_key_function = 2
if key in (pygame.K_a, pygame.K_LEFT):
self.last_key_function = 1
if key in (pygame.K_d, pygame.K_RIGHT):
self.last_key_function = 3
def update(self):
if self.head_in_block():
self.lock_last = False
if self.last_key_function == 0:
if not self.move_up():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 1:
if not self.move_left():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 2:
if not self.move_down():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 3:
if not self.move_right():
self.last_key_function = self.prev_key_function
self.prev_key_function = self.last_key_function
for food in Food.foods:
if self.body[0][0] == food.coords:
self.add_part(food)
self.move()
def add_part(self, food):
self.lock_last = True
self.body.append(self.body[-1].copy())
food.create_food()
def move(self):
if self.last_key_function != -1:
for part_index, (part_coords, part_velocity) in enumerate(self.body if not self.lock_last else self.body[:-1]):
new_part_coords = (part_coords[0] + part_velocity[0], part_coords[1] + part_velocity[1])
self.body[part_index][0] = new_part_coords
if self.head_in_block():
for part_index in range(len(self.body) - 1 - int(self.lock_last), 0, -1):
for new_part_index in range(part_index - 1, -1, -1):
if self.body[new_part_index][0] != self.body[part_index][0]:
self.body[part_index][1] = self.body[new_part_index][1]
break
def move_up(self):
if len(self.body) <= 1 or self.body[0][0][1] <= self.body[1][0][1]:
self.body[0][1] = (0, -SNAKE_SPEED)
return True
return False
def move_left(self):
if len(self.body) <= 1 or self.body[0][0][0] <= self.body[1][0][0]:
self.body[0][1] = (-SNAKE_SPEED, 0)
return True
return False
def move_down(self):
if len(self.body) <= 1 or self.body[0][0][1] >= self.body[1][0][1]:
self.body[0][1] = (0, SNAKE_SPEED)
return True
return False
def move_right(self):
if len(self.body) <= 1 or self.body[0][0][0] >= self.body[1][0][0]:
self.body[0][1] = (SNAKE_SPEED, 0)
return True
return False
def head_in_block(self):
return self.coords_in_block(self.body[0][0])
@staticmethod
def coords_in_block(coords):
return coords[0] % SNAKE_SIZE == coords[1] % SNAKE_SIZE == 0
def display(self, canvas):
for part_index, (part_coords, part_velocity) in enumerate(self.body):
pygame.draw.rect(canvas, WHITE, (part_coords[0] + 1, part_coords[1] + 1, SNAKE_SIZE - 2, SNAKE_SIZE - 2))
if part_index != 0:
while True:
part_coords = (part_coords[0] + part_velocity[0], part_coords[1] + part_velocity[1])
pygame.draw.rect(canvas, WHITE, (part_coords[0] + 1, part_coords[1] + 1, SNAKE_SIZE - 2, SNAKE_SIZE - 2))
if self.coords_in_block(part_coords):
break
if part_index != len(self.body) - 1:
while True:
part_coords = (part_coords[0] - part_velocity[0], part_coords[1] - part_velocity[1])
pygame.draw.rect(canvas, WHITE, (part_coords[0] + 1, part_coords[1] + 1, SNAKE_SIZE - 2, SNAKE_SIZE - 2))
if self.coords_in_block(part_coords):
break
def collided(self):
if not (0 <= self.body[0][0][0] < WINDOW_SIZE[0] - SNAKE_SIZE + 1) or \
not (MARGIN <= self.body[0][0][1] < WINDOW_SIZE[1] - SNAKE_SIZE + 1):
return True
if self.head_in_block():
for part_index, (part_coords, part_velocity) in enumerate(self.body[1:], 1):
if abs(self.body[0][0][0] - part_coords[0]) < SNAKE_SIZE and \
abs(self.body[0][0][1] - part_coords[1]) < SNAKE_SIZE:
return True
else:
return False
class Game:
def __init__(self):
clock = pygame.time.Clock()
self.canvas = pygame.display.set_mode(WINDOW_SIZE)
self.font = pygame.font.SysFont('Arial', FONT_SIZE)
self.finished = False
self.lost = self.paused = False
self.lose_time = self.pause_time = None
self.init_head = (WINDOW_SIZE[0] // 2 // 10 * 10, WINDOW_SIZE[1] // 2 // 10 * 10)
self.init_body = [[self.init_head, (0, 0)]]
self.snake = Snake(self.init_body)
self.food = Food()
while not self.finished:
self.canvas.fill(BLACK)
self.__update()
clock.tick(180)
pygame.draw.rect(self.canvas, WHITE, ((0, MARGIN - 1), (WINDOW_SIZE[0], 1)))
pygame.display.update()
self.__reset()
def __reset(self):
self.finished = False
self.lost = self.paused = False
self.lose_time = self.pause_time = None
self.init_head = (WINDOW_SIZE[0] // 2 // 10 * 10, WINDOW_SIZE[1] // 2 // 10 * 10)
self.init_body = [[self.init_head, (0, 0)]]
self.snake.remove_from_list()
self.food.remove_from_list()
self.snake = Snake(self.init_body)
self.food = Food()
def __update(self):
snake_update_keys = []
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.finished = True
if event.type == pygame.KEYDOWN:
if not self.lost and (event.key == pygame.K_p or event.key == pygame.K_ESCAPE):
self.paused = not self.paused
self.pause_time = time.time() if self.paused else None
snake_update_keys.append(event.key)
if self.lost and event.key == pygame.K_r:
self.__reset()
if not self.lost and self.snake.collided():
self.lost = True
self.lose_time = time.time()
if not self.lost and not self.paused:
self.snake.update_key_function(snake_update_keys)
self.snake.update()
self.food.display(self.canvas)
self.snake.display(self.canvas)
self.__update_score()
self.__update_pause()
self.__update_fail()
def __update_score(self):
self.score = self.food.count - 1
self.score_text = self.font.render('Score: ' + str(self.score), True, GREEN)
self.canvas.blit(self.score_text, (10, 10))
def __update_fail(self):
if self.lost:
fail_text = self.font.render('You lost!', True, GREEN)
self.canvas.blit(fail_text, (WINDOW_SIZE[0] - 10 - fail_text.get_rect().width, 10))
if (time.time() - self.lose_time) % 1 > 0.5:
restart = self.font.render('Press R to restart', True, GREEN)
self.canvas.blit(restart, (WINDOW_SIZE[0] // 2 - restart.get_rect().width // 2,
WINDOW_SIZE[1] // 2 - restart.get_rect().height // 2))
def __update_pause(self):
if self.paused:
if (time.time() - self.pause_time) % 1 > 0.5:
self.pause_text = self.font.render('Press P or Esc to resume', True, GREEN)
self.canvas.blit(self.pause_text, (WINDOW_SIZE[0] - 10 - self.pause_text.get_rect().width, 10))
elif not self.lost:
self.pause_text = self.font.render('Press P or Esc to pause', True, GREEN)
self.canvas.blit(self.pause_text, (WINDOW_SIZE[0] - 10 - self.pause_text.get_rect().width, 10))
game = Game()我注意到的唯一错误是,蛇每隔几秒钟就会慢一点。我不知道为什么会发生这种情况,但由于它几乎没有引起注意,所以我现在就离开了它。
我对游戏还是很陌生的,这是我的第一个游戏项目之一,所以我非常希望能回顾一下我的代码。
我也在考虑在蛇和食物中添加纹理,但我不确定如何实现它。对此的任何评论也将不胜感激!
发布于 2020-10-10 22:38:01
代码看起来结构很好,格式化很好,函数使用了很好的名称,所以这些“明显”的东西不需要修复。
if WINDOW_SIZE[0] % SNAKE_SIZE != 0 or WINDOW_SIZE[1] % SNAKE_SIZE != 0 or MARGIN % SNAKE_SIZE != 0:
raise Exception('Size of grid should be divisible by SNAKE_SIZE')既然你总是用同样的东西除法,我宁愿
for x in [WINDOW_SIZE[0], WINDOW_SIZE[1], MARGIN]:
if x % SNAKE_SIZE != 0:
raise Exception('...')而且,由于您的窗口大小已经是边距的倍数,您可以从一开始就这样定义它们,从而使其变得更简单(稍微不那么灵活)。
MARGIN = 60
WINDOW_SIZE = (10 * MARGIN, 11 * MARGIN)然后,您只需检查边距与SNAKE_SIZE,而不是其他两个。
def create_food(self):
self.count += 1
check = set(tuple(part[0]) for snake in Snake.snakes for part in snake.body)
self.coords = random.choice([(i, j) for i in range(0, WINDOW_SIZE[0], SNAKE_SIZE) for j in range(MARGIN, WINDOW_SIZE[1], SNAKE_SIZE) if (i, j) not in check])这些行非常长,很难理解或修改。我不认为Python或一般编程的目的是编写尽可能少的行。如果您认为这是可读性和可维护性的,那么请继续,但我更希望看到它被分成多行,以便易于理解。
pygame.draw.rect(canvas, RED, (self.coords[0] + 1, self.coords[1] + 1, SNAKE_SIZE - 2, SNAKE_SIZE - 2))这里也有同样的问题。我认为self.x和self.y比self.coords[0]更具可读性,您应该分别命名rect的大小,特别是因为您要使用它两次。
size = SNAKE_SIZE - 2
pygame.draw.rect(canvas, RED, (self.x + 1, self.y + 1, size, size))def update_key_function(self, keys):
for key in keys:
if key in (pygame.K_w, pygame.K_UP):
self.last_key_function = 0
if key in (pygame.K_s, pygame.K_DOWN):
self.last_key_function = 2
if key in (pygame.K_a, pygame.K_LEFT):
self.last_key_function = 1
if key in (pygame.K_d, pygame.K_RIGHT):
self.last_key_function = 3因此,可能有多个键,但您只想保存一个,也只保存最后一个。为了更清晰、更少重复,我首先定义一个keymap (在此函数之外将其定义为“全局”或类成员,无论什么合适。
direction_keys = {
pygame.K_w: 0,
pygame.K_UP: 0,
...
pygame.K_d: 3,
pygame.K_RIGHT: 3
} 然后在结束时进行迭代,并在第一次命中时返回,这就清楚了这段代码的意图,并且不会检查超出需要的键。(就速度而言,它可能并不重要,但它更清晰和DRYer。
for k in reversed(keys):
if k in direction_keys:
self.last_key_function = direction_keys[k]
returnif self.last_key_function == 0:
if not self.move_up():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 1:
if not self.move_left():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 2:
if not self.move_down():
self.last_key_function = self.prev_key_function
elif self.last_key_function == 3:
if not self.move_right():
self.last_key_function = self.prev_key_function此代码也有类似的问题。当你不需要的时候,你在重复一堆事情。
这4个ifs的结果是相同的,self.last_key_function = self.prev_key_function,所以elif是不需要的,它们可以重写为一个大的or子句,如下所示
if A or B or C or D:
self.last_key_function = self.prev_key_function但我们可以做得更好。
由于这些移动函数很好地使用了数字0, 1, 2, 3,例如,我们可以这样做
for number, func in enumerate ([self.move_up, self.move_left, self.move_down, self.move_right]):
if self.last_key_function == number and not func():
self.last_key_function = self.prev_key_function
break如果您认为这很难阅读,只需使用or版本,它比当前版本短得多,而且更好。
我会写更多的评论,但我已经没有时间了。希望这会有帮助。
https://codereview.stackexchange.com/questions/250089
复制相似问题