コード例 #1
0
 def pick_spot(self):
     p = None
     while p is None:
         p = Pos(random.randint(0, self.w - 1),
                 random.randint(0, self.h - 1))
         for e in self.tail:
             if p.dist(e) < 1:
                 p = None
                 break
     return p
コード例 #2
0
class Game:
    GRAY = (51, 51, 51)
    WHITE = (255, 255, 255)
    OFF_WHITE = (200, 200, 200)
    GREEN = (0, 255, 0)
    RED = (255, 0, 0)

    def __init__(self,
                 scl,
                 fps,
                 w,
                 h,
                 load_image,
                 play_sound,
                 monitor,
                 show_fps=False):
        self.monitor = monitor
        os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % ((monitor.width -
                                                         (w * scl)) // 2,
                                                        (monitor.height -
                                                         (h * scl)) // 2)
        # Initializing pygame
        pygame.init()

        # Initializing the text module
        pygame.font.init()

        # Setting title
        pygame.display.set_caption("Snake")

        # Setting icon
        if load_image:
            try:
                icon = pygame.image.load('images/snake.png')
                pygame.display.set_icon(icon)
            except FileNotFoundError:
                print("Could not load image << snake.png >>")
                exit(1)

        self.scl = scl
        self.fps = fps
        self.w = w
        self.h = h
        self.load_image = load_image
        self.play_sound = play_sound
        self.screen = None

        self.width = None
        self.height = None
        self.keyQueue = None

        self.high_score = 0
        self.s = None
        self.tail = None
        self.direction = None
        self.food = None
        self.hit = None
        self.time = None
        self.apple = None
        self.target_frame_time = None

        self.menu_w = None
        self.menu_h = None
        self.menu_width = None
        self.menu_height = None
        self.show_fps = show_fps

        # Setting refresh rate
        self.clock = pygame.time.Clock()

        self.load_options()
        self.set_assets(self.scl, self.fps, self.w, self.h, self.load_image,
                        self.play_sound, self.show_fps)

    def set_assets(self, scl, fps, w, h, load_image, play_sound, show_fps):
        self.scl = scl
        self.fps = fps
        self.w = w
        self.h = h
        self.width = self.w * self.scl
        self.height = self.h * self.scl
        self.keyQueue = []
        self.load_image = load_image
        self.play_sound = play_sound

        self.menu_w = 16
        self.menu_h = 18
        self.menu_width = self.menu_w * self.scl
        self.menu_height = self.menu_h * self.scl
        self.show_fps = show_fps

        # Player variables
        self.s = Pos(1)
        self.tail = [Pos()]
        self.direction = Pos(1)
        self.food = self.pick_spot()
        # self.reset()

        # Loading sound
        self.hit = None
        if play_sound:
            try:
                self.hit = pygame.mixer.Sound('music/hit.wav')
            except FileNotFoundError:
                print("Could not load << hit.wav >>")
                exit(1)

        self.apple = None
        if load_image:
            try:
                self.apple = pygame.image.load('images/apple.png')
                self.apple = pygame.transform.scale(self.apple, (scl, scl))
            except FileNotFoundError:
                print("Could not load image << apple.png >>")
                exit(1)

        self.time = 0
        self.target_frame_time = 1000 / fps

    def set_screen_size(self):
        self.screen = pygame.display.set_mode(
            (self.w * self.scl, self.h * self.scl))

    def set_scale(self, scl):
        self.scl = scl
        self.height = self.h * self.scl
        self.width = self.w * self.scl
        self.set_screen_size()

    def reset(self):
        self.s = Pos(1)
        self.tail = [Pos()]
        self.direction = Pos(1)
        self.food = self.pick_spot()

    # Pick new random location for food. Finds a position that is not in the tail
    def pick_spot(self):
        p = None
        while p is None:
            p = Pos(random.randint(0, self.w - 1),
                    random.randint(0, self.h - 1))
            for e in self.tail:
                if p.dist(e) < 1:
                    p = None
                    break
        return p

    def run(self):
        self.menu()
        self.set_options()

    def menu(self):
        self.screen = pygame.display.set_mode(
            (self.menu_width, self.menu_height))
        running = True
        menu_position = 0
        menu_options = ['Play', 'Options', 'Quit']
        update_required = True
        while running:
            self.clock.tick(self.fps)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                if event.type == pygame.KEYDOWN:
                    update_required = True
                    if event.key == pygame.K_RETURN:
                        if menu_position == 0:
                            score = self.game()
                            new_hs = self.check_score(score)
                            if new_hs:
                                self.set_options()
                            self.game_over(score, new_hs)
                            self.reset()
                        elif menu_position == 1:
                            self.options_menu()
                        elif menu_position == 2:
                            running = False
                            continue
                    self.screen = pygame.display.set_mode(
                        (self.menu_width, self.menu_height))
                    if event.key == pygame.K_DOWN:
                        menu_position = (menu_position + 1) % len(menu_options)
                    if event.key == pygame.K_UP:
                        menu_position -= 1
                        if menu_position < 0:
                            menu_position = len(menu_options) - 1
            if update_required:
                # Drawing background
                self.screen.fill(self.GRAY)

                for i, option in enumerate(menu_options):
                    bg_color, fg_color = (
                        self.WHITE,
                        self.GRAY) if i == menu_position else (self.GRAY,
                                                               self.WHITE)
                    self.draw_text(option,
                                   self.scl * 2,
                                   self.menu_width // 2,
                                   self.scl * 3 * (i + 1),
                                   fg_color,
                                   bg_color,
                                   padding=(self.scl // 2, self.scl // 4),
                                   bg_w=self.menu_width // 2,
                                   h_alignment='CENTER')

                self.draw_text(f'High score: {self.high_score}', self.scl,
                               self.menu_width // 2,
                               self.menu_height - self.scl, self.WHITE)

                pygame.display.update()
                update_required = False

    def set_fps(self, fps):
        self.fps = fps

    def options_menu(self):
        self.screen = pygame.display.set_mode(
            (self.menu_width, self.menu_height))
        running = True
        update_required = True
        menu_position = 0

        menu_options = [
            {
                'text': 'Scale',
                'type': 'multi',
                'value': self.scl,
                'increment': 5,
            },
            {
                'text': 'Width',
                'type': 'multi',
                'value': self.w,
                'increment': 1,
            },
            {
                'text': 'Height',
                'type': 'multi',
                'value': self.h,
                'increment': 1,
            },
            {
                'text': 'Images',
                'type': 'checkbox',
                'value': self.load_image,
            },
            {
                'text': 'Music',
                'type': 'checkbox',
                'value': self.play_sound,
            },
            {
                'text': 'FPS',
                'type': 'multi',
                'value': self.fps,
                'increment': 1,
                # 'set': self.set_fps  # Idea for setting values
            },
            {
                'text': 'Show FPS',
                'type': 'checkbox',
                'value': self.show_fps,
            },
            {
                'text': 'Apply',
                'type': 'apply',
            }
        ]

        while running:
            self.clock.tick(self.fps)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                if event.type == pygame.KEYDOWN:
                    update_required = True

                    if event.key == pygame.K_BACKSPACE:
                        running = False
                    if event.key == pygame.K_RETURN:
                        option_type = menu_options[menu_position]['type']
                        if option_type == 'apply':
                            vals = list(
                                map(
                                    lambda e: e['value']
                                    if 'value' in e else '', menu_options))
                            self.set_assets(
                                scl=vals[0],
                                fps=vals[5],
                                w=vals[1],
                                h=vals[2],
                                load_image=vals[3],
                                play_sound=vals[4],
                                show_fps=vals[6],
                            )
                            self.set_options()
                            running = False
                        elif option_type == 'checkbox':
                            menu_options[menu_position][
                                'value'] = not menu_options[menu_position][
                                    'value']
                    if event.key == pygame.K_DOWN:
                        menu_position = (menu_position + 1) % len(menu_options)
                    if event.key == pygame.K_UP:
                        menu_position -= 1
                        if menu_position < 0:
                            menu_position = len(menu_options) - 1
                    if (event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT) \
                            and menu_options[menu_position]['type'] == 'multi':
                        if event.key == pygame.K_LEFT:
                            new_value = menu_options[menu_position][
                                'value'] - menu_options[menu_position][
                                    'increment']
                            if new_value > 0:
                                menu_options[menu_position][
                                    'value'] = new_value
                        else:
                            menu_options[menu_position][
                                'value'] += menu_options[menu_position][
                                    'increment']
            if update_required:
                self.screen.fill(self.GRAY)

                for i, option in enumerate(menu_options):
                    bg_color, fg_color = (
                        self.WHITE,
                        self.GRAY) if i == menu_position else (self.GRAY,
                                                               self.WHITE)
                    if option['type'] == 'apply':
                        bg_color, fg_color = (
                            self.RED,
                            self.WHITE) if i == menu_position else (self.GRAY,
                                                                    self.RED)
                    value = ''
                    if option['type'] == 'checkbox':
                        txt = '%-9s' % (option['text'])
                        value = '[x]' if option["value"] else '[  ]'
                    elif option['type'] == 'multi':
                        txt = '%-9s' % (option['text'])
                        value = f'< {option["value"]} >'
                    else:
                        txt = option['text']

                    padding = (self.scl, self.scl // 4)
                    self.draw_text(txt,
                                   2 * self.scl,
                                   self.scl,
                                   self.scl * 2 * (i + 1),
                                   fg_color,
                                   bg_color,
                                   h_alignment='LEFT',
                                   text_align='LEFT',
                                   padding=padding,
                                   bg_w=2 * self.menu_width // 3)
                    self.draw_text(value,
                                   2 * self.scl,
                                   self.menu_width - self.scl,
                                   self.scl * 2 * (i + 1),
                                   fg_color,
                                   bg_color,
                                   h_alignment='RIGHT',
                                   text_align='RIGHT',
                                   padding=padding,
                                   bg_w=self.menu_width // 3)
                pygame.display.update()
                update_required = False

    def game_over(self, score, new_hs):
        self.screen = pygame.display.set_mode((self.width, self.height))
        running = True
        update_required = True
        while running:
            self.clock.tick(self.fps)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                if event.type == pygame.KEYDOWN:
                    update_required = True
                    if event.key == pygame.K_RETURN:
                        running = False
            if update_required:
                self.screen.fill(self.GRAY)
                self.draw_text(f'Score: {score}', self.scl * 2,
                               self.width // 2,
                               self.height // 2 - (self.scl if new_hs else 0),
                               self.WHITE)
                if new_hs:
                    self.draw_text(f'New high score!', self.scl * 2,
                                   self.width // 2,
                                   (self.height // 2) + self.scl, self.RED)
                self.draw_text('Press enter to go back... ', self.scl,
                               self.width // 2, self.height - self.scl,
                               self.OFF_WHITE)
                pygame.display.update()
                update_required = False

    def draw_text(self,
                  text,
                  size,
                  x,
                  y,
                  color,
                  background=None,
                  h_alignment='CENTER',
                  v_alignment='CENTER',
                  text_align='CENTER',
                  padding=(0, 0),
                  bg_w=0,
                  bg_h=0):
        font = pygame.font.SysFont('Areal', size)
        text_surface = font.render(text, True, color, background)
        text_rect = text_surface.get_rect()

        bg_rect = text_surface.get_rect()
        if bg_h > 0:
            bg_rect.w = bg_h
        if bg_w > 0:
            bg_rect.w = bg_w

        text_w, text_h = text_rect.size

        if h_alignment == 'LEFT':
            x += bg_rect.w // 2
        elif h_alignment == 'RIGHT':
            x -= bg_rect.w // 2

        if v_alignment == 'TOP':
            y += text_h // 2
        elif v_alignment == 'BOTTOM':
            y -= text_h // 2

        align_x = x
        align_y = y
        width_diff = abs(text_rect.w - bg_rect.w)
        if text_align == 'LEFT':
            align_x -= width_diff // 2
        elif text_align == 'RIGHT':
            align_x += width_diff // 2

        text_rect.center = (align_x, align_y)
        bg_rect.center = (x, y)

        h_padding, v_padding = padding
        if background:
            pygame.draw.rect(
                self.screen, background,
                pygame.Rect(bg_rect.x - h_padding, bg_rect.y - v_padding,
                            bg_rect.w + 2 * h_padding,
                            bg_rect.h + 2 * v_padding))

        self.screen.blit(text_surface, text_rect)

    def game(self):
        self.screen = pygame.display.set_mode((self.width, self.height))
        os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (
            (self.monitor.width - self.width) // 2,
            (self.monitor.height - self.height) // 2)
        # Game loop
        running = True
        while running:
            self.clock.tick(self.fps)
            # Input
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_LEFT:
                        self.keyQueue.append(Pos(-1, 0))
                    elif event.key == pygame.K_UP:
                        self.keyQueue.append(Pos(0, -1))
                    elif event.key == pygame.K_RIGHT:
                        self.keyQueue.append(Pos(1, 0))
                    elif event.key == pygame.K_DOWN:
                        self.keyQueue.append(Pos(0, 1))

            # Direction update
            if len(self.keyQueue) > 0:
                n_dir = self.keyQueue[0]
                if self.direction.x != -n_dir.x and self.direction.y != -n_dir.y:
                    self.direction = n_dir  # Sets direction to the first element in the queue
                self.keyQueue.pop(0)  # Removes the element from the queue

            # Adding last position of head to tail
            self.tail.append(Pos(self.s.x, self.s.y))

            # Updating position of head
            self.s.x += self.direction.x
            self.s.y += self.direction.y

            # If overlapping with food-> Pick new spot for food. Else-> remove least recently added element of tail
            if self.s.dist(self.food) < 1:
                self.food = self.pick_spot()
                if self.hit is not None and self.play_sound:
                    self.hit.play()
            else:
                self.tail.pop(0)

            # If out of bounds
            if self.s.x > self.w - 1 or self.s.x < 0 or self.s.y > self.h - 1 or self.s.y < 0:
                running = False
                continue
            # If crashing into itself
            for t in self.tail:
                if self.s.dist(t) < 1:
                    running = False
                    continue

            # Drawing background
            self.screen.fill(self.GRAY)

            # Draw snake
            pygame.draw.rect(self.screen, self.OFF_WHITE,
                             (self.s.x * self.scl + 1, self.s.y * self.scl + 1,
                              self.scl - 2, self.scl - 2))
            for t in self.tail:
                pygame.draw.rect(self.screen, self.WHITE,
                                 (t.x * self.scl + 1, t.y * self.scl + 1,
                                  self.scl - 2, self.scl - 2))

            # Drawing apple
            if self.apple is not None:
                self.screen.blit(
                    self.apple,
                    (self.food.x * self.scl, self.food.y * self.scl))
            else:
                pygame.draw.rect(self.screen, self.GREEN,
                                 (self.food.x * self.scl,
                                  self.food.y * self.scl, self.scl, self.scl))

            # Displaying score
            self.draw_text(str(len(self.tail) - 1), 2 * self.scl, self.scl,
                           self.scl, self.WHITE)

            # Drawing high score
            self.draw_text(f'( {self.high_score} )', self.scl // 2, self.scl,
                           2 * self.scl, self.OFF_WHITE)

            # Drawing fps
            if self.show_fps:
                fps = 1000 / self.clock.get_time()
                self.draw_text('FPS: %3s' % round(fps),
                               self.scl // 2,
                               self.width,
                               self.height,
                               self.OFF_WHITE,
                               h_alignment='RIGHT',
                               v_alignment='BOTTOM')

            # Updating screen
            pygame.display.update()
        return len(self.tail) - 1

    def check_score(self, score):
        if score > self.high_score:
            self.high_score = score
            return True
        return False

    def load_options(self):
        options = {}
        try:
            with open('options.json', 'r') as file:
                json_string = file.read()
            options = json.loads(json_string)
        except FileNotFoundError:
            pass

        if 'highScore' in options:
            self.high_score = options['highScore']
        if 'scl' in options:
            self.scl = options['scl']
        if 'fps' in options:
            self.fps = options['fps']
        if 'w' in options:
            self.w = options['w']
        if 'h' in options:
            self.h = options['h']
        if 'loadImage' in options:
            self.load_image = options['loadImage']
        if 'playSound' in options:
            self.play_sound = options['playSound']
        if 'showFPS' in options:
            self.show_fps = options['showFPS']

    def set_options(self):
        options = {
            'highScore': self.high_score,
            'scl': self.scl,
            'fps': self.fps,
            'w': self.w,
            'h': self.h,
            'loadImage': self.load_image,
            'playSound': self.play_sound,
            'showFPS': self.show_fps,
        }
        json_string = json.dumps(options)
        with open('options.json', 'w') as file:
            file.write(json_string)