Python PacMan с сыном

Я кодировал Pac-Man с 16 лет. Надеюсь, это не слишком скучный проект, который поможет улучшить его кодирование на Python. Мы только что переместили «Призраков» в класс, который стал для него хорошим первым знакомством с объектами.

Мое кодирование далеко от совершенства, особенно на Python. Который только-только придумал, когда я изучал этот материал. Ищу отзывы о том, как повысить профессионализм его кода:

  • как быть более «питоническим»
  • как дать ему возможность добавить больше функций в этот проект в будущем

Очевидно, ему предстоит пройти долгий путь. Так что ищите небольшие шаги вперед, которые мы можем понять и с которыми мы сможем работать, а не перепишите полную продажу, пожалуйста 🙂

В конечном итоге я бы хотел, чтобы он реализовал некоторые алгоритмы поиска призраков (BFS, A * и т. Д.), Поэтому было бы хорошо убедиться, что текущая структура подходит для этого.

Код приведен ниже. Или как застежка-молния с текстурами и др.. В настоящее время код работает, но у нас еще не написаны жизни, призраки, убивающие пакмана, уровни и т. Д.

#imports
import pygame
import os
import time
import math as maths

#Constants
# Text Positioning
CENTRE_MID = 1
LEFT_MID = 2
RIGHT_MID = 3
CENTRE_TOP = 4
LEFT_TOP = 5
RIGHT_TOP = 6
CENTRE_BOT = 7
LEFT_BOT = 8
RIGHT_BOT = 9

#Pacman Orientation
UP = 10
RIGHT = 11
LEFT = 12
DOWN = 13

HYPERJUMPALLOWED = True
HYPERJUMPNOTALLOWED = False

PIXEL = 20
FRAMERATE = 1

# DX and DY for each direction
NORTH = (0, -PIXEL)
SOUTH = (0, PIXEL)
EAST = (PIXEL, 0)
WEST = (-PIXEL, 0)

YELLOW = (255, 255, 102)
PALEYELLOW = (128, 128, 51)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255,0,0)

BOARDPIXELWIDTH = 200
BOARDPIXELHEIGHT = 200

#Global Variables
gameOver = False
win = False
score = 0


#Dictionary mapping between board chars and gif's to display.
char_to_image = {'.' : pygame.transform.scale(pygame.image.load('pellet.gif'), (PIXEL, PIXEL)),
                 '=' : pygame.transform.scale(pygame.image.load('wall-nub.gif'), (PIXEL, PIXEL)), 
                 '=T' : pygame.transform.scale(pygame.image.load('wall-end-b.gif'), (PIXEL, PIXEL)),
                 '=R' : pygame.transform.scale(pygame.image.load('wall-end-l.gif'), (PIXEL, PIXEL)),
                 '=L' : pygame.transform.scale(pygame.image.load('wall-end-r.gif'), (PIXEL, PIXEL)),
                 '=B' : pygame.transform.scale(pygame.image.load('wall-end-t.gif'), (PIXEL, PIXEL)) ,
                 '=TR' : pygame.transform.scale(pygame.image.load('wall-corner-ll.gif'), (PIXEL, PIXEL)),
                 '=TL' : pygame.transform.scale(pygame.image.load('wall-corner-lr.gif'), (PIXEL, PIXEL)),
                 '=BR' : pygame.transform.scale(pygame.image.load('wall-corner-ul.gif'), (PIXEL, PIXEL)),
                 '=BL' : pygame.transform.scale(pygame.image.load('wall-corner-ur.gif'), (PIXEL, PIXEL)),
                 '=TB' : pygame.transform.scale(pygame.image.load('wall-straight-vert.gif'), (PIXEL, PIXEL)),
                 '=RL' : pygame.transform.scale(pygame.image.load('wall-straight-horiz.gif'), (PIXEL, PIXEL)),
                 '=LTR' : pygame.transform.scale(pygame.image.load('wall-t-bottom.gif'), (PIXEL, PIXEL)),
                 '=TRB' : pygame.transform.scale(pygame.image.load('wall-t-left.gif'), (PIXEL, PIXEL)),
                 '=BLT' : pygame.transform.scale(pygame.image.load('wall-t-right.gif'), (PIXEL, PIXEL)),
                 '=RBL' : pygame.transform.scale(pygame.image.load('wall-t-top.gif'), (PIXEL, PIXEL)),
                 '=TRLB' : pygame.transform.scale(pygame.image.load('wall-x.gif'), (PIXEL, PIXEL)),
                 'U' : pygame.transform.scale(pygame.image.load('pacman-u 4.gif'), (PIXEL, PIXEL)),
                 'R' : pygame.transform.scale(pygame.image.load('pacman-r 4.gif'), (PIXEL, PIXEL)),
                 'L' : pygame.transform.scale(pygame.image.load('pacman-l 4.gif'), (PIXEL, PIXEL)),
                 'D' : pygame.transform.scale(pygame.image.load('pacman-d 4.gif'), (PIXEL, PIXEL)),
                 '!P' : pygame.transform.scale(pygame.image.load('Pinky.gif'), (PIXEL, PIXEL)),
                 '!P.' : pygame.transform.scale(pygame.image.load('Pinky.gif'), (PIXEL, PIXEL)),
                 '!B' : pygame.transform.scale(pygame.image.load('Blinky.gif'), (PIXEL, PIXEL)),
                 '!B.' : pygame.transform.scale(pygame.image.load('Blinky.gif'), (PIXEL, PIXEL)),
                 '!I' : pygame.transform.scale(pygame.image.load('Inky.gif'), (PIXEL, PIXEL)),
                 '!I.' : pygame.transform.scale(pygame.image.load('Inky.gif'), (PIXEL, PIXEL)),
                 '!C' : pygame.transform.scale(pygame.image.load('Clyde.gif'), (PIXEL, PIXEL)),
                 '!C.' : pygame.transform.scale(pygame.image.load('Clyde.gif'), (PIXEL, PIXEL)),
                 }
#Class stuff
class Ghost:
    def __init__(self, ghostPixelX, ghostPixelY, sprite):
        print("Init " + sprite)
        self.ghostPixelX = ghostPixelX
        self.ghostPixelY = ghostPixelY
        self.sprite = sprite

        
    def draw(self):
        #print("draw " + self.sprite)
        dis.blit(char_to_image[self.sprite], (self.ghostPixelX, self.ghostPixelY))


    def erase(self):
        #print("erase " + self.sprite)
        # Erase Ghost by drawing black rectangle over it
        pygame.draw.rect(dis, BLACK, [self.ghostPixelX, self.ghostPixelY, PIXEL, PIXEL])
        
        boardX = int(self.ghostPixelX/PIXEL)
        boardY = int(self.ghostPixelY/PIXEL)
        
        # If the space contains food, redraw the food
        if "." in board[boardY][boardX]:
            dis.blit(char_to_image["."], (self.ghostPixelX, self.ghostPixelY))


    def move(self, pacManPixelX, pacManPixelY):

        #print("PreMove: " + str(self.sprite) + " " + str(self.ghostPixelX) + " " + str(self.ghostPixelY))

        #if score moves, so does directions
        #Sorts directions to which direction is best to take
        directions = [NORTH, EAST, SOUTH, WEST]
        score = ["","","",""] #Which move is best

        #Calculate distance between Ghost and PacMan
        pixelDistanceX = pacManPixelX - self.ghostPixelX
        pixelDistanceY = pacManPixelY - self.ghostPixelY
        pixelDistance = maths.sqrt(pixelDistanceX**2 + pixelDistanceY**2)

        #Calculate distance between Ghost and PacMan after a move in each direction
        for i, direction in enumerate(directions):
            ghostDX, ghostDY = direction
            newGhostPixelX = self.ghostPixelX + ghostDX
            newGhostPixelY = self.ghostPixelY + ghostDY
            
            newPixelDistanceX = pacManPixelX - newGhostPixelX
            newPixelDistanceY = pacManPixelY - newGhostPixelY
            newPixelDistance = maths.sqrt(newPixelDistanceX**2 + newPixelDistanceY**2)

            #Store how much better (closer) or worse (further away) the move would take the ghost from PacMan
            score[i] = pixelDistance - newPixelDistance

        #Insertion sort O(n)
        #Iterates through the list for the next number to sort (start at pos 1)
        for index in range(1, len(score)):
            currentEntry = score[index]
            currentEntryDir = directions[index]
            position = index

            #Iterates through the list for the number to swap
            while position > 0 and score[position-1] > currentEntry:
                #Copies the lower position into the original position, overwriting it
                score[position] = score[position-1]
                directions[position] = directions[position-1]
                
                position = position - 1
                
            #puts the stored value from position, into the final lower position
            score[position] = currentEntry
            directions[position] = currentEntryDir

        # Take the now sorted list of moves, trying each one in turn and take the best move possible
        for direction in reversed(directions):
            ghostDX, ghostDY = direction
            newGhostPixelX = self.ghostPixelX + ghostDX
            newGhostPixelY = self.ghostPixelY + ghostDY

            # Ghosts cant hyperjump
            if newGhostPixelX >= 0 and newGhostPixelX < BOARDPIXELWIDTH and newGhostPixelY >= 0 and newGhostPixelX < BOARDPIXELHEIGHT:
                # Ghosts can't go through walls
                if TestMove(newGhostPixelX, newGhostPixelY, HYPERJUMPNOTALLOWED):
                    #print(direction)
                    self.ghostPixelX = newGhostPixelX
                    self.ghostPixelY = newGhostPixelY

                    #print("PostMove: " + str(self.sprite) + " " + str(self.ghostPixelX) + " " + str(self.ghostPixelY))
                    print("")
                    return
        
           


        


#Functions

# Load Board from a file in current directory
# Boards are text files called "board-X.txt"
def LoadBoard():   
    #ToDo load board from file
    #10 x 10 Board
    board = [['=BR', '=RL', '=RL', '=L', 'O', '.', '=R', '=RL', '=RL', '=BL'],
             ['=TB', '!B.', '.', '.', '.', '.', '.', '.', '!I.', '=TB'],
             ['=TB', '.', '=BR', '=L', '.', '.', '=R', '=BL', '.', '=TB'],
             ['=T', '.', '=T', '.', '.', '.', '.', '=T', '.', '=T'],
             ['.', '.', '.', '.', '.', 'U', '.', '.', '.', 'O'],
             ['O', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
             ['=B', '.', '=B', '.', '.', '.', '.', '=B', '.', '=B'],
             ['=TB', '.', '=TR', '=L', '.', '.', '=R', '=TL', '.', '=TB'],
             ['=TB', '!C.', '.', '.', '.', '.', '.', '.', '!P.', '=TB'],
             ['=TR', '=RL', '=RL', '=L', '.', 'O', '=R', '=RL', '=RL', '=TL']]

    global foodTotal
    global pacManPixelX, pacManPixelY, pacManFacing, pacManDX, pacManDY
    global Pinky, Blinky, Inky, Clyde
    foodTotal = 0
    pacManPixelX = pacManPixelY = pacManDX = pacManDY = 0
    pacManFacing = UP
    
    #ToDo Load Board Pixel Width and Height here and delete from top of this file
    for boardY, line in enumerate(board):
        for boardX, symbol in enumerate(line):
            if symbol == ".":
                foodTotal +=1 # Count how much food we start with
                
            elif symbol == "!P." or symbol == "!P": #Which Ghost is it?
                Pinky = Ghost(boardX * PIXEL, boardY * PIXEL, "!P") #Create the ghost!
            elif symbol == "!B." or symbol == "!B": 
                Blinky = Ghost(boardX * PIXEL, boardY * PIXEL, "!B")
            elif symbol == "!I." or symbol == "!I": 
                Inky = Ghost(boardX * PIXEL, boardY * PIXEL, "!I")
            elif symbol == "!C." or symbol == "!C": 
                Clyde = Ghost(boardX * PIXEL, boardY * PIXEL, "!C") 
        
            elif symbol == "U":
                pacManPixelX = boardX * PIXEL # Get PacMan starting position
                pacManPixelY = boardY * PIXEL
    return board


#Draw Board
def DrawBoard():
    for y, line in enumerate(board):
        # Convert from board PIXEL to real PIXEL
        y *= PIXEL
        for x, symbol in enumerate(line):
            # Convert from board PIXEL to real PIXEL
            x *= PIXEL
            
            # Convert board chars to gif filename using dictionary
            if symbol != "O":
                dis.blit(char_to_image[symbol], (x, y))


#Test if Character can move to new location
def TestMove(newPixelX, newPixelY, hyperJumpAllowed):

    #TODO This is used for Ghosts and PacMan, Ghosts are not allowed to move in to a square already occupied by a Ghost
    # Pacman is, but then will die
    if newPixelX >= BOARDPIXELWIDTH or newPixelY >= BOARDPIXELHEIGHT or newPixelX < 0 or newPixelY < 0:
        if (hyperJumpAllowed):
            #If move would be a HyperJump, and HypeJumps are allowed then move must be ok
            return True
        else:
            #If move would be a HyperJump, and HypeJumps are not allowed then move must not be ok
            return False
    
    newBoardX = int(newPixelX/PIXEL)
    newBoardY = int(newPixelY/PIXEL)
  
    #Test if move would end up in a wall    
    if "=" in board[newBoardY][newBoardX]:
        return False
    else:
        return True


#Move PacMan to new location, but dont draw the update
def MovePacMan(pixelX, pixelY, dPixelX, dPixelY, facing):

    # Move PacMan
    newPixelX = pixelX + dPixelX
    newPixelY = pixelY + dPixelY

    # Check if move needs to be a HyperJump and if so HyperJump
    if (newPixelX >= BOARDPIXELWIDTH):
        newPixelX = 0
    elif (newPixelX < 0):
        newPixelX = BOARDPIXELWIDTH - PIXEL

    if (newPixelY >= BOARDPIXELHEIGHT):
        newPixelY = 0
    elif (newPixelY < 0):
        newPixelY = BOARDPIXELHEIGHT - PIXEL      
        
    return newPixelX, newPixelY


def moveGhosts(pacManPixelX, pacManPixelY):
    Pinky.move(pacManPixelX, pacManPixelY)
    Blinky.move(pacManPixelX, pacManPixelY)
    Inky.move(pacManPixelX, pacManPixelY)
    Clyde.move(pacManPixelX, pacManPixelY)


def eraseGhosts():
    Pinky.erase()
    Blinky.erase()
    Inky.erase()
    Clyde.erase()


def ErasePacMan(pixelX, pixelY):
    # Erase PacMan from old position by drawing black rectangle over it
    pygame.draw.rect(dis, BLACK, [pixelX, pixelY, PIXEL, PIXEL])


def drawGhosts():
    Pinky.draw()
    Blinky.draw()
    Inky.draw()
    Clyde.draw()


#Draw PacMan at a new position
def DrawPacMan(pixelX, pixelY, facing):
    # Draw PacMan at new position
    if facing == UP:
        dis.blit(char_to_image['U'], (pixelX, pixelY))
    elif facing == DOWN:
        dis.blit(char_to_image['D'], (pixelX, pixelY))
    elif facing == LEFT:
        dis.blit(char_to_image['L'], (pixelX, pixelY))
    elif facing == RIGHT:
        dis.blit(char_to_image['R'], (pixelX, pixelY))
        
    # Remove food at new board position
    board[int(pixelY / PIXEL)][int(pixelX / PIXEL)] = "O"


#Play sounds as PacMan eats
def PlaySound(pixelX, pixelY):
    boardX = int(pixelX / PIXEL)
    boardY = int(pixelY / PIXEL)
    
    #Play sound if new position has food
    if board[boardY][boardX] == ".":
        # Alternate between two different sounds
        if (boardX + boardY) % 2 == 0:
            food1Sound.play()
        else:
            food2Sound.play()
    else:
        defaultSound.play()


def message(msg, color, pixelX, pixelY, fontSize, align):
    #Setup font
    font_style = pygame.font.SysFont("bahnschrift", fontSize)
    
    # Render text ont a surface
    msgRendered = font_style.render(msg, True, color)
    
    # Get size of surface
    msgPixelWidth, msgPixelHeight = msgRendered.get_size()
    
    # Change position to draw in relation to align 
    if align == CENTRE_MID:
        pixelX = pixelX - (msgPixelWidth / 2)
        pixelY = pixelY - (msgPixelHeight / 2)
    elif align == CENTRE_TOP:
        pixelX = pixelX - (msgPixelWidth / 2)
    
    dis.blit(msgRendered, [pixelX, pixelY])



#Main Code
pygame.init()

#Setup display and pygame clock
dis = pygame.display.set_mode((BOARDPIXELWIDTH, BOARDPIXELHEIGHT + ( 2 * PIXEL)))
pygame.display.set_caption('Pac-man by ME')
clock = pygame.time.Clock()

#Setup Sounds
if os.path.isfile("1-pellet1.wav") and os.path.isfile("1-pellet2.wav") and os.path.isfile("1-default.wav"):
    sound = True
    food1Sound = pygame.mixer.Sound("1-pellet1.wav")
    food2Sound = pygame.mixer.Sound("1-pellet2.wav")
    defaultSound = pygame.mixer.Sound("1-default.wav")
else:
    print("Warning: Sound files not found, not playing sounds.")
    sound = False
 
#Load board from file
#ToDo Load random board or different board each level
board = LoadBoard()

#Draw Board
DrawBoard()
pygame.display.flip()
       
#Game Loop
while not gameOver:
    
    for event in pygame.event.get():
        #Allows quitting
        if event.type == pygame.QUIT:
            gameOver = True
            
        if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    pacManFacing = LEFT
                    pacManDX, pacManDY = WEST
                    #pacManDX = -PIXEL
                    #pacManDY = 0
                elif event.key == pygame.K_RIGHT:
                    pacManFacing = RIGHT
                    pacManDX, pacManDY = EAST
                    #pacManDX = PIXEL
                    #pacManDY = 0
                elif event.key == pygame.K_UP:
                    pacManFacing = UP
                    pacManDX, pacManDY = NORTH
                    #pacManDY = -PIXEL
                    #pacManDX = 0
                elif event.key == pygame.K_DOWN:
                    pacManFacing = DOWN
                    pacManDX, pacManDY = SOUTH
                    #pacManDY = PIXEL
                    #pacManDX = 0
                    
    #Can we move to new position?
    if TestMove(pacManPixelX + pacManDX, pacManPixelY + pacManDY, HYPERJUMPALLOWED):
        #Erase PacMan
        ErasePacMan(pacManPixelX, pacManPixelY)
        
        #Calculate new position
        pacManPixelX, pacManPixelY = MovePacMan(pacManPixelX, pacManPixelY, pacManDX, pacManDY, pacManFacing)

        #print("pacManPixelX " + str(pacManPixelX) + " pacManPixelY " + str(pacManPixelY))
        if board[int(pacManPixelY / PIXEL)][int(pacManPixelX / PIXEL)] == ".":
            score+=1
            foodTotal-=1
            
        #Sound
        if sound:
            PlaySound(pacManPixelX, pacManPixelY)
            
        #Draw the turn and remove food
        DrawPacMan(pacManPixelX, pacManPixelY, pacManFacing)

        #Update the score
        pygame.draw.rect(dis, BLACK, [0, BOARDPIXELHEIGHT, BOARDPIXELWIDTH, PIXEL])
        message(("You're score is " +str(score)), RED, 0, (BOARDPIXELHEIGHT), 15, LEFT_TOP)

    # Ghosts
    eraseGhosts()

    #Calculate new Ghost position  
    moveGhosts(pacManPixelX, pacManPixelY)

    #Draw new Ghost positions on the screen   
    drawGhosts()
 
    pygame.display.update()

    #TODO Has the ghost caughtPacMan, if so pacman looses 1 of 3 lives.
    # So need lives system - 3 pacmen bottom right of screen that get 'used up' each time one dies
    # What happens when Pacman dies?  Ghosts get reset, pacman gets reset, score -10 and then carry on?
    # Hint, pac man moves first, so when each ghost moves you can test if it has hit pacman
    #if ghostPixelX == pacManPixelX and ghostPixelY == pacManPixelY:
    #    gameOver = True
        
    #Win
    if foodTotal == 0:
        gameOver = True
        win = True
        
    #Tick the clock
    clock.tick(FRAMERATE)

if win == True:
    pygame.draw.rect(dis, YELLOW, [0, 0, BOARDPIXELWIDTH, BOARDPIXELHEIGHT])
    message(("You Win!"), RED, BOARDPIXELWIDTH / 2, BOARDPIXELHEIGHT / 2, 15, CENTRE_MID)
    message(("This message will dissapear in 5 seconds"), RED, (BOARDPIXELWIDTH / 2), (BOARDPIXELHEIGHT / 2 + PIXEL), 10, CENTRE_TOP)
    pygame.display.update()
    time.sleep(5)
else:
    pygame.draw.rect(dis, RED, [0, 0, BOARDPIXELWIDTH, BOARDPIXELHEIGHT])
    message(("You Lose!"), YELLOW, BOARDPIXELWIDTH / 2, BOARDPIXELHEIGHT / 2, 15, CENTRE_MID)
    message(("This message will dissapear in 5 seconds"), YELLOW, (BOARDPIXELWIDTH / 2), (BOARDPIXELHEIGHT / 2 + PIXEL), 10, CENTRE_TOP)
    pygame.display.update()
    time.sleep(5)
    
pygame.quit()
quit()

2 ответа
2

Ищу отзывы о том, как повысить профессионализм его кода

как быть более «питоническим»

Стиль, который вы использовали, нетипичен для Python, поскольку ваш код не соответствует все из PEP 8. Например, вы использовали camelCase скорее, чем snake_case для имен функций и переменных. Лично, если ваш сын хочет быть программистом, а не программистом на Python, то игнорирование отдельных частей Python вполне нормально. Однако в вашем коде есть некоторые несоответствия, которые было бы лучше устранить на раннем этапе, чтобы не допустить превращения в дурные привычки.

Например; CENTRE_MID против HYPERJUMPALLOWED, moveGhosts против MovePacMan, с использованием " или же ' для разделения строк, используя пробелы после запятых — (0, 0, 255) и (255,0,0), всегда используя равные пробелы вокруг операторов x *= PIXEL или же foodTotal +=1.

Я бы рекомендовал установка линтера чтобы проверить свой код. Или вы можете использовать хинтер, например Чернить, делать или же autopep8.


  • char_to_image = {'.' : pygame.transform.scale(pygame.image.load('pellet.gif'), (PIXEL, PIXEL)),
                     '=' : pygame.transform.scale(pygame.image.load('wall-nub.gif'), (PIXEL, PIXEL)),
                     ...
                     }
    

    Мы видим, что у вас здесь довольно много повторяющегося кода. Я бы построил словарь строк, а потом построил бы словарь спрайтов PyGame.

    _CHAR_TO_IMAGE = {
        ".": "pellet.gif",
        "=": "wall-nub.gif",
    }
    
    char_to_image = {}
    for key, value in _CHAR_TO_IMAGE.items():
        char_to_image[key] = pygame.transform.scale(pygame.image.load(value), (PIXEL, PIXEL))
    

    Вместо этого мы можем использовать понимание словаря для создания словаря питоническим способом.
    Толкование словаря похожи составить список однако мы бы построили словарь, а не список. Преобразование цикла for в понимание словаря будет выглядеть так:

    char_to_image = {
        key: pygame.transform.scale(pygame.image.load(value), (PIXEL, PIXEL))
        for key, value in _CHAR_TO_IMAGE.items()
    }
    
  • Вы должны переместить свой основной код в основную функцию. Затем вы можете использовать __main__ сторожить для запуска кода только в том случае, если файл является точкой входа в программу.

    Нравиться main() на C-подобных языках программирования.

  • if win == True:
    

    С bool является подклассом int истина равна 1, 1.0 и 1+0j. Вы должны использовать одно из:

    • Убедитесь, что тип «правдивый», поэтому if win: правда. Код будет соответствовать гораздо большему количеству значений, например 2 правда. Так это [1], {1: 1} и {1}.

    • Если вы хотите убедиться, что значение True ты должен использовать is. Нужно сравнить просто True очень редко.

    Рекомендуемый способ проверить значение — правдивый.

  • if "=" in board[newBoardY][newBoardX]:
        return False
    else:
        return True
    

    Мы можем просто вернуть выражение в if.

    return "=" not in board[newBoardY][newBoardX]
    

    Поскольку использование достоверных проверок является нормой в Python; даже если in не возвращается True или же False любой код, вызывающий функцию, по-прежнему будет работать так же. (Примечание: in всегда возвращается True или же False однако некоторые другие операции могут возвращать правду, но не True значения.)

  • Ghost.move можно сделать немного проще.

    1. pixelDistance не требуется, поскольку использование теоремы Пифагора уже дает вам гипотенузу (расстояние) между двумя объектами.

    2. Мы можем использовать list.sort отсортировать список.

      1. Код сортировки будет работать на C, а не на Python. (Наверное будет быстрее)
      2. Вам не нужно писать алгоритм сортировки. (Делает код проще)
      3. Python использует Timsort; оптимизированная версия сортировки вставками. (Наверное будет быстрее)
    3. # Ghosts cant hyperjump
      if newGhostPixelX >= 0 and newGhostPixelX < BOARDPIXELWIDTH and newGhostPixelY >= 0 and newGhostPixelX < BOARDPIXELHEIGHT:
          # Ghosts can't go through walls
          if TestMove(newGhostPixelX, newGhostPixelY, HYPERJUMPNOTALLOWED):
      

      Мне кажется TestMove должен включать первый оператор if. Поскольку мы прошли HYPERJUMPNOTALLOWED к функции, и в вашем комментарии говорится, что код предотвращает гипер-прыжки.

  • Я думаю, вам нужно добавить класс доски.

    • Плата должна преобразовать (внутренне) из списка Python в пиксели на экране.
    • Плата должна позволять потребителям взаимодействовать с простым 2D-массивом, чтобы потребитель никогда не касается пикселей.
    • Доска должна иметь некоторые удобные функции, такие как проверка того, является ли место допустимым местом для перемещения.
    • Доска должна позволять потребителю перемещать предметы на доске.

    Я думаю, что наличие надежного класса платы — основная проблема в вашем коде.

    После создания стандартной доски вы должны создать игровой класс. Класс игры должен содержать все глобальные переменные, кроме не следует включить цикл событий или блит-циклы. Вызов игрового класса из таких циклов — нормально.

#imports
import pygame
import os
import time
import math as maths

#Constants
# Text Positioning
CENTRE_MID = 1
LEFT_MID = 2
RIGHT_MID = 3
CENTRE_TOP = 4
LEFT_TOP = 5
RIGHT_TOP = 6
CENTRE_BOT = 7
LEFT_BOT = 8
RIGHT_BOT = 9

#Pacman Orientation
UP = 10
RIGHT = 11
LEFT = 12
DOWN = 13

HYPERJUMPALLOWED = True
HYPERJUMPNOTALLOWED = False

PIXEL = 20
FRAMERATE = 1

# DX and DY for each direction
NORTH = (0, -PIXEL)
SOUTH = (0, PIXEL)
EAST = (PIXEL, 0)
WEST = (-PIXEL, 0)

YELLOW = (255, 255, 102)
PALEYELLOW = (128, 128, 51)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255,0,0)

BOARDPIXELWIDTH = 200
BOARDPIXELHEIGHT = 200

#Global Variables
gameOver = False
win = False
score = 0


#Dictionary mapping between board chars and gif's to display.
_CHAR_TO_IMAGE = {'.' : 'pellet.gif',
                  '=' : 'wall-nub.gif', 
                  '=T' : 'wall-end-b.gif',
                  '=R' : 'wall-end-l.gif',
                  '=L' : 'wall-end-r.gif',
                  '=B' : 'wall-end-t.gif',
                  '=TR' : 'wall-corner-ll.gif',
                  '=TL' : 'wall-corner-lr.gif',
                  '=BR' : 'wall-corner-ul.gif',
                  '=BL' : 'wall-corner-ur.gif',
                  '=TB' : 'wall-straight-vert.gif',
                  '=RL' : 'wall-straight-horiz.gif',
                  '=LTR' : 'wall-t-bottom.gif',
                  '=TRB' : 'wall-t-left.gif',
                  '=BLT' : 'wall-t-right.gif',
                  '=RBL' : 'wall-t-top.gif',
                  '=TRLB' : 'wall-x.gif',
                  'U' : 'pacman-u 4.gif',
                  'R' : 'pacman-r 4.gif',
                  'L' : 'pacman-l 4.gif',
                  'D' : 'pacman-d 4.gif',
                  '!P' : 'Pinky.gif',
                  '!P.' : 'Pinky.gif',
                  '!B' : 'Blinky.gif',
                  '!B.' : 'Blinky.gif',
                  '!I' : 'Inky.gif',
                  '!I.' : 'Inky.gif',
                  '!C' : 'Clyde.gif',
                  '!C.' : 'Clyde.gif',
                  }

char_to_image = {
    key: pygame.transform.scale(pygame.image.load(value), (PIXEL, PIXEL))
    for key, value in _CHAR_TO_IMAGE.items()
}


#Class stuff
class Ghost:
    def __init__(self, ghostPixelX, ghostPixelY, sprite):
        print("Init " + sprite)
        self.ghostPixelX = ghostPixelX
        self.ghostPixelY = ghostPixelY
        self.sprite = sprite

        
    def draw(self):
        #print("draw " + self.sprite)
        dis.blit(char_to_image[self.sprite], (self.ghostPixelX, self.ghostPixelY))


    def erase(self):
        #print("erase " + self.sprite)
        # Erase Ghost by drawing black rectangle over it
        pygame.draw.rect(dis, BLACK, [self.ghostPixelX, self.ghostPixelY, PIXEL, PIXEL])
        
        boardX = int(self.ghostPixelX/PIXEL)
        boardY = int(self.ghostPixelY/PIXEL)
        
        # If the space contains food, redraw the food
        if "." in board[boardY][boardX]:
            dis.blit(char_to_image["."], (self.ghostPixelX, self.ghostPixelY))


    def move(self, pacManPixelX, pacManPixelY):
        scored_directions = [
            (
                maths.sqrt(
                    (pacManPixelX - self.ghostPixelX - ghostDX) ** 2
                    + (pacManPixelY - self.ghostPixelY - ghostDY) ** 2
                ),
                direction
            )
            for direction in directions
        ]
        scored_directions.sort()
        for _, (ghostDX, ghostDY) in scored_direction:
            newGhostPixelX = self.ghostPixelX + ghostDX
            newGhostPixelY = self.ghostPixelY + ghostDY

            if TestMove(newGhostPixelX, newGhostPixelY, HYPERJUMPNOTALLOWED):
                self.ghostPixelX = newGhostPixelX
                self.ghostPixelY = newGhostPixelY
                return


#Functions

# Load Board from a file in current directory
# Boards are text files called "board-X.txt"
def LoadBoard():   
    #ToDo load board from file
    #10 x 10 Board
    board = [['=BR', '=RL', '=RL', '=L', 'O', '.', '=R', '=RL', '=RL', '=BL'],
             ['=TB', '!B.', '.', '.', '.', '.', '.', '.', '!I.', '=TB'],
             ['=TB', '.', '=BR', '=L', '.', '.', '=R', '=BL', '.', '=TB'],
             ['=T', '.', '=T', '.', '.', '.', '.', '=T', '.', '=T'],
             ['.', '.', '.', '.', '.', 'U', '.', '.', '.', 'O'],
             ['O', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
             ['=B', '.', '=B', '.', '.', '.', '.', '=B', '.', '=B'],
             ['=TB', '.', '=TR', '=L', '.', '.', '=R', '=TL', '.', '=TB'],
             ['=TB', '!C.', '.', '.', '.', '.', '.', '.', '!P.', '=TB'],
             ['=TR', '=RL', '=RL', '=L', '.', 'O', '=R', '=RL', '=RL', '=TL']]

    global foodTotal
    global pacManPixelX, pacManPixelY, pacManFacing, pacManDX, pacManDY
    global Pinky, Blinky, Inky, Clyde
    foodTotal = 0
    pacManPixelX = pacManPixelY = pacManDX = pacManDY = 0
    pacManFacing = UP
    
    #ToDo Load Board Pixel Width and Height here and delete from top of this file
    for boardY, line in enumerate(board):
        for boardX, symbol in enumerate(line):
            if symbol == ".":
                foodTotal +=1 # Count how much food we start with
                
            elif symbol == "!P." or symbol == "!P": #Which Ghost is it?
                Pinky = Ghost(boardX * PIXEL, boardY * PIXEL, "!P") #Create the ghost!
            elif symbol == "!B." or symbol == "!B": 
                Blinky = Ghost(boardX * PIXEL, boardY * PIXEL, "!B")
            elif symbol == "!I." or symbol == "!I": 
                Inky = Ghost(boardX * PIXEL, boardY * PIXEL, "!I")
            elif symbol == "!C." or symbol == "!C": 
                Clyde = Ghost(boardX * PIXEL, boardY * PIXEL, "!C") 
        
            elif symbol == "U":
                pacManPixelX = boardX * PIXEL # Get PacMan starting position
                pacManPixelY = boardY * PIXEL
    return board


#Draw Board
def DrawBoard():
    for y, line in enumerate(board):
        # Convert from board PIXEL to real PIXEL
        y *= PIXEL
        for x, symbol in enumerate(line):
            # Convert from board PIXEL to real PIXEL
            x *= PIXEL
            
            # Convert board chars to gif filename using dictionary
            if symbol != "O":
                dis.blit(char_to_image[symbol], (x, y))


#Test if Character can move to new location
def TestMove(newPixelX, newPixelY, hyperJumpAllowed):

    #TODO This is used for Ghosts and PacMan, Ghosts are not allowed to move in to a square already occupied by a Ghost
    # Pacman is, but then will die
    if newPixelX >= BOARDPIXELWIDTH or newPixelY >= BOARDPIXELHEIGHT or newPixelX < 0 or newPixelY < 0:
        return hyperJumpAllowed
    
    newBoardX = int(newPixelX/PIXEL)
    newBoardY = int(newPixelY/PIXEL)
  
    #Test if move would end up in a wall    
    return "=" not in board[newBoardY][newBoardX]


#Move PacMan to new location, but dont draw the update
def MovePacMan(pixelX, pixelY, dPixelX, dPixelY, facing):

    # Move PacMan
    newPixelX = pixelX + dPixelX
    newPixelY = pixelY + dPixelY

    # Check if move needs to be a HyperJump and if so HyperJump
    if (newPixelX >= BOARDPIXELWIDTH):
        newPixelX = 0
    elif (newPixelX < 0):
        newPixelX = BOARDPIXELWIDTH - PIXEL

    if (newPixelY >= BOARDPIXELHEIGHT):
        newPixelY = 0
    elif (newPixelY < 0):
        newPixelY = BOARDPIXELHEIGHT - PIXEL      
        
    return newPixelX, newPixelY


def moveGhosts(pacManPixelX, pacManPixelY):
    Pinky.move(pacManPixelX, pacManPixelY)
    Blinky.move(pacManPixelX, pacManPixelY)
    Inky.move(pacManPixelX, pacManPixelY)
    Clyde.move(pacManPixelX, pacManPixelY)


def eraseGhosts():
    Pinky.erase()
    Blinky.erase()
    Inky.erase()
    Clyde.erase()


def ErasePacMan(pixelX, pixelY):
    # Erase PacMan from old position by drawing black rectangle over it
    pygame.draw.rect(dis, BLACK, [pixelX, pixelY, PIXEL, PIXEL])


def drawGhosts():
    Pinky.draw()
    Blinky.draw()
    Inky.draw()
    Clyde.draw()


#Draw PacMan at a new position
def DrawPacMan(pixelX, pixelY, facing):
    # Draw PacMan at new position
    if facing == UP:
        dis.blit(char_to_image['U'], (pixelX, pixelY))
    elif facing == DOWN:
        dis.blit(char_to_image['D'], (pixelX, pixelY))
    elif facing == LEFT:
        dis.blit(char_to_image['L'], (pixelX, pixelY))
    elif facing == RIGHT:
        dis.blit(char_to_image['R'], (pixelX, pixelY))
        
    # Remove food at new board position
    board[int(pixelY / PIXEL)][int(pixelX / PIXEL)] = "O"


def message(msg, color, pixelX, pixelY, fontSize, align):
    #Setup font
    font_style = pygame.font.SysFont("bahnschrift", fontSize)
    
    # Render text ont a surface
    msgRendered = font_style.render(msg, True, color)
    
    # Get size of surface
    msgPixelWidth, msgPixelHeight = msgRendered.get_size()
    
    # Change position to draw in relation to align 
    if align == CENTRE_MID:
        pixelX = pixelX - (msgPixelWidth / 2)
        pixelY = pixelY - (msgPixelHeight / 2)
    elif align == CENTRE_TOP:
        pixelX = pixelX - (msgPixelWidth / 2)
    
    dis.blit(msgRendered, [pixelX, pixelY])


#Play sounds as PacMan eats
def PlaySound(pixelX, pixelY):
    boardX = int(pixelX / PIXEL)
    boardY = int(pixelY / PIXEL)
    
    #Play sound if new position has food
    if board[boardY][boardX] == ".":
        # Alternate between two different sounds
        if (boardX + boardY) % 2 == 0:
            food1Sound.play()
        else:
            food2Sound.play()
    else:
        defaultSound.play()



def main():
    pygame.init()

    #Setup display and pygame clock
    dis = pygame.display.set_mode((BOARDPIXELWIDTH, BOARDPIXELHEIGHT + ( 2 * PIXEL)))
    pygame.display.set_caption('Pac-man by ME')
    clock = pygame.time.Clock()

    #Setup Sounds
    if os.path.isfile("1-pellet1.wav") and os.path.isfile("1-pellet2.wav") and os.path.isfile("1-default.wav"):
        sound = True
        food1Sound = pygame.mixer.Sound("1-pellet1.wav")
        food2Sound = pygame.mixer.Sound("1-pellet2.wav")
        defaultSound = pygame.mixer.Sound("1-default.wav")
    else:
        print("Warning: Sound files not found, not playing sounds.")
        sound = False
     
    #Load board from file
    #ToDo Load random board or different board each level
    board = LoadBoard()

    #Draw Board
    DrawBoard()
    pygame.display.flip()
           
    #Game Loop
    while not gameOver:
        
        for event in pygame.event.get():
            #Allows quitting
            if event.type == pygame.QUIT:
                gameOver = True
                
            if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_LEFT:
                        pacManFacing = LEFT
                        pacManDX, pacManDY = WEST
                        #pacManDX = -PIXEL
                        #pacManDY = 0
                    elif event.key == pygame.K_RIGHT:
                        pacManFacing = RIGHT
                        pacManDX, pacManDY = EAST
                        #pacManDX = PIXEL
                        #pacManDY = 0
                    elif event.key == pygame.K_UP:
                        pacManFacing = UP
                        pacManDX, pacManDY = NORTH
                        #pacManDY = -PIXEL
                        #pacManDX = 0
                    elif event.key == pygame.K_DOWN:
                        pacManFacing = DOWN
                        pacManDX, pacManDY = SOUTH
                        #pacManDY = PIXEL
                        #pacManDX = 0
                        
        #Can we move to new position?
        if TestMove(pacManPixelX + pacManDX, pacManPixelY + pacManDY, HYPERJUMPALLOWED):
            #Erase PacMan
            ErasePacMan(pacManPixelX, pacManPixelY)
            
            #Calculate new position
            pacManPixelX, pacManPixelY = MovePacMan(pacManPixelX, pacManPixelY, pacManDX, pacManDY, pacManFacing)

            #print("pacManPixelX " + str(pacManPixelX) + " pacManPixelY " + str(pacManPixelY))
            if board[int(pacManPixelY / PIXEL)][int(pacManPixelX / PIXEL)] == ".":
                score+=1
                foodTotal-=1
                
            #Sound
            if sound:
                PlaySound(pacManPixelX, pacManPixelY)
                
            #Draw the turn and remove food
            DrawPacMan(pacManPixelX, pacManPixelY, pacManFacing)

            #Update the score
            pygame.draw.rect(dis, BLACK, [0, BOARDPIXELHEIGHT, BOARDPIXELWIDTH, PIXEL])
            message(("You're score is " +str(score)), RED, 0, (BOARDPIXELHEIGHT), 15, LEFT_TOP)

        # Ghosts
        eraseGhosts()

        #Calculate new Ghost position  
        moveGhosts(pacManPixelX, pacManPixelY)

        #Draw new Ghost positions on the screen   
        drawGhosts()
     
        pygame.display.update()

        #TODO Has the ghost caughtPacMan, if so pacman looses 1 of 3 lives.
        # So need lives system - 3 pacmen bottom right of screen that get 'used up' each time one dies
        # What happens when Pacman dies?  Ghosts get reset, pacman gets reset, score -10 and then carry on?
        # Hint, pac man moves first, so when each ghost moves you can test if it has hit pacman
        #if ghostPixelX == pacManPixelX and ghostPixelY == pacManPixelY:
        #    gameOver = True
            
        #Win
        if foodTotal == 0:
            gameOver = True
            win = True
            
        #Tick the clock
        clock.tick(FRAMERATE)

    if win:
        pygame.draw.rect(dis, YELLOW, [0, 0, BOARDPIXELWIDTH, BOARDPIXELHEIGHT])
        message(("You Win!"), RED, BOARDPIXELWIDTH / 2, BOARDPIXELHEIGHT / 2, 15, CENTRE_MID)
        message(("This message will dissapear in 5 seconds"), RED, (BOARDPIXELWIDTH / 2), (BOARDPIXELHEIGHT / 2 + PIXEL), 10, CENTRE_TOP)
        pygame.display.update()
        time.sleep(5)
    else:
        pygame.draw.rect(dis, RED, [0, 0, BOARDPIXELWIDTH, BOARDPIXELHEIGHT])
        message(("You Lose!"), YELLOW, BOARDPIXELWIDTH / 2, BOARDPIXELHEIGHT / 2, 15, CENTRE_MID)
        message(("This message will dissapear in 5 seconds"), YELLOW, (BOARDPIXELWIDTH / 2), (BOARDPIXELHEIGHT / 2 + PIXEL), 10, CENTRE_TOP)
        pygame.display.update()
        time.sleep(5)
        
    pygame.quit()
    quit()


if __name__ == "__main__":
    main()

  • 1

    Ваш четвертый пункт глючит, вы вообще хотите return "=" not in board[newBoardY][newBoardX], по крайней мере, это то, что делает исходный оператор if.

    — рискованный пингвин


  • даже если in не возвращается True или же False: Пройдите тест на членство, используя in не возвращаются True / False? >>> ("h" in "hello") is True возвращается True для меня (Python 3.9.1).

    — рискованный пингвин


  • 1

    @riskypenguin Спасибо! Я отредактировал, чтобы исправить in скорее not in. Нет __contains__ всегда преобразуется в логическое значение. Однако я собираюсь использовать конкретное, чтобы поговорить об общем, например, если у вас есть if a: return True else: return False -> return a. a не всегда гарантировано быть True. Можете ли вы дважды проверить, что мои последние правки устранили обе проблемы?

    — Пейлонрайз


  • Проблема решена, а путаница устранена! знак равно

    — рискованный пингвин

  • Большое спасибо за комментарии @Peilonrayz. Полностью согласен насчет класса Board — я все время упираюсь в позицию Pixels vs Board. Однако я изо всех сил пытаюсь понять новую функцию Ghosts.move. Я понимаю, что отдельные направления и оценка, которые у меня были, не очень хороши (поскольку они не связаны), но в вашем коде вы делаете что-то совсем другое, чего я не понимаю. Вы создаете кортеж из двух значений? Вам не хватает определения направлений (для использования в выражении «направление в направлениях»)? Почему for x in x идет внизу цикла for? Спасибо

    — Кевин В.

учить своего ребенка языку программирования — это здорово!

У меня есть несколько советов, идей:

1. Зачем использовать gif, если они не анимированы, вы также можете использовать png для этих цветовых стилей.

2: второй для вашего макета уровня, вы можете просто сохранить 1 значение для стены и позволить графическому рендеру решать, какую часть вашей стены рисовать (на основе соседних ячеек), может даже использоваться позже, если вы решите изменить структуру уровня в игре.

3: даже если ваша игра основана на сетке, вы можете добавить плавное движение с помощью перевода (бот gosths и pacman)

4: в версии 10, которую я тестировал, отмечая, что произошло, когда я получил это призрак

5: для большего удобства вы можете создать класс уровня, который загружает ваш уровень, а когда вы выиграете 1 (я не смог) загрузить следующий.

6: вы также, вероятно, можете настроить частоту кадров, чтобы получить больше детализации, что позволяет изменять направление в середине (не уверен, что вы поймете, что я имею в виду)

7: добавление меню, GUI, пауза …

Я не играл в оригинальный пакман, но попробовал http://www.jeux.org/jeu/pacman-2.html, вы могли бы увидеть кое-что, что можно улучшить

    Добавить комментарий

    Ваш адрес email не будет опубликован.