Lập trình Pac-Man


31

Chương trình 'Pac-Man

Cài đặt

Bạn chơi như Pac-Man. Bạn muốn thu thập các viên, trái cây và viên năng lượng trước bất kỳ ai khác, trong khi tránh ma.

Quy tắc

  1. Mỗi Pac-Man hợp lệ sẽ ở trong một mê cung duy nhất. Người chơi có số điểm tích lũy cao nhất sau 10 trận sẽ giành chiến thắng.
  2. Một trò chơi kết thúc khi tất cả Pac-Men đã chết, tất cả các viên đã biến mất, hoặc 500 lượt đã trôi qua
  3. Nếu Pac-Man chết, anh ta tiếp tục chơi như một con ma
  4. Ăn một viên năng lượng sẽ khiến bạn bất khả chiến bại trong 10 lượt và cho phép bạn ăn ma
  5. Ăn một con ma sẽ dịch chuyển con ma đến một vị trí ngẫu nhiên
  6. Ma quỷ không thể ăn bất cứ thứ gì ngoại trừ Pac-Men và không nhận được bất kỳ điểm nào
  7. Ăn các món sau đây như Pac-Man sẽ giúp bạn có được những điểm sau:
    1. Viên: 10
    2. Viên năng lượng: 50
    3. Quả: 100
    4. Ma: 200

Mê cung

Nếu có n Pac-Men, sau đó một mê cung của kích thước sqrt(n)*10bằng sqrt(n)*10sẽ được tạo ra sử dụng Thuật toán Prim (do yếu tố sông thấp của nó), sau đó bện hoàn toàn, ưu tiên cho những ngõ cụt đã tồn tại. Hơn nữa, việc bện này có thể được thực hiện trên các cạnh, do đó có một vài con đường từ trên xuống dưới và từ trái sang phải.

Sẽ có:

  1. 2n Ma
  2. 4n Viên năng lượng
  3. 2n Trái cây
  4. n Pac-Men ở những điểm mà các ô vuông lân cận được kết nối trống rỗng.
  5. Tất cả các điểm trống còn lại sẽ được lấp đầy bằng các viên

Do đó, một bản đồ ban đầu với 10 người chơi sẽ trông giống như thế này (Ghosts = green, Pellets = aqua, fruit = red, Pac-Man = yellow):

mê cung

Đầu ra đầu vào

Khi bắt đầu trò chơi , Bạn sẽ được cung cấp một dòng nhân vật duy nhất, đại diện cho các bức tường ở mỗi ô vuông trên bản đồ. Đối với mỗi hình vuông, bắt đầu từ trên cùng bên trái, di chuyển sang phải và gói sang dòng tiếp theo, bạn sẽ được cung cấp một chữ số hex biểu thị tình huống tường:

0: No walls
1: North wall
2: East wall
3: East & North wall
4: South wall
5: South & North wall
6: South & East wall
7: Won't occur
8: West wall
9: West & North wall
A: West & East wall
B: Won't occur
C: West & South wall
D: Won't occur
E: Won't occur
F: Won't occur

Nói một cách đơn giản, North = 1, East = 2, South = 4 và West = 8, được cộng lại với nhau.

Sau đó, mỗi lượt , bạn sẽ được trao vị trí hiện tại và các vật phẩm trong tầm nhìn của bạn (nếu bạn là Pac-Man. Tất cả các hồn ma đều nhận được tất cả các ô vuông từ -5 đến +5 từ vị trí tương đối của chúng). Tầm nhìn của bạn sẽ dựa trên hướng bạn đi trong lượt cuối. Nếu bạn đi về phía bắc, bạn sẽ được cung cấp tất cả các hình vuông trực tiếp về phía Bắc, Đông và Tây của bạn cho đến khi một bức tường cắt tầm nhìn của bạn cộng với một hình vuông Tây Bắc và Đông Bắc duy nhất, nếu không có bức tường nào cắt tầm nhìn của bạn. Nếu bạn chọn không di chuyển, bạn sẽ được cung cấp các ô vuông theo cả 8 hướng.

Đối với hình ảnh, Icó nghĩa là vô hình, Vcó nghĩa là có thể nhìn thấy, Pcó nghĩa là Pac-Man (giả sử Pac-Man đang quay mặt về hướng bắc):

|I I|V|I|
|I V|V V|
|V V P|I|
|I I|I|I|

Mỗi ô vuông sẽ được cung cấp bởi một tọa độ, và sau đó là nội dung. Nội dung của nó được thể hiện bằng các ký tự sau:

  1. P: 1 hoặc nhiều Pac-Man
  2. G: 1 hoặc nhiều ma
  3. o: Viên
  4. O: Viên điện
  5. F: Miếng trái cây
  6. X: Không có gì

Nếu có một con ma và một cái gì đó khác trên một hình vuông, Gsẽ được trả lại.

Do đó, nếu bạn ở trên hình vuông 23,70, bạn chỉ cần di chuyển về phía bắc, hình vuông phía trên bạn là một ngõ cụt và chứa một viên năng lượng, và bạn có những bức tường ở cả hai bên, đầu vào của bạn sẽ là:

23,70X 22,70O

Trên hình vuông hiện tại của bạn, nó sẽ hiển thị Gnếu bạn là Ghost, Pnếu có một Pac-Man khác trên hình vuông của bạn, nếu không thìX

Sau đó, bạn sẽ trả lại các mục sau qua STDOUT:

Một ký tự duy nhất đại diện cho một hướng ( North, East, South, West hoặc XStay).

Trước khi đi theo một hướng, bạn cũng có thể chuyển qua bất kỳ tọa độ nào x,yvà các bức tường của hình vuông đó sẽ được chuyển trở lại (như được mô tả ở trên)

Chương trình phải được chạy liên tục cho đến khi Qđược truyền cho nó thông qua STDIN. Các chương trình sẽ được khởi động lại cho mỗi trò chơi.

Không được phép truy cập các thông tin khác bên ngoài những gì được truyền đến STDIN (bao gồm cả dữ liệu Pac-male khác hoặc dữ liệu được giữ bởi chương trình máy chủ).

Việc không trả lại di chuyển trong vòng 1000 ms sẽ chấm dứt chương trình (Chạy trên máy Win8 khá tốt của tôi). Bạn sẽ có 2 giây để xử lý bố cục mê cung ban đầu khi được đưa ra

Máy chủ sẽ được viết bằng Python và mã để kiểm tra bot của bạn sắp ra mắt.

Trường hợp đặc biệt

  • Nếu nhiều Pac-Men kết thúc trên cùng một vị trí, không nhận được nội dung của hình vuông hiện tại, trừ khi chính xác 1 trong số đó là bất khả chiến bại, trong trường hợp đó, Pac-Man bất khả chiến bại sẽ nhận được viên.
  • Một Pac-Man bị Ghost ăn thịt sẽ không bị dịch chuyển đi nơi khác. Nếu hai Pac-Men ở trên một hình vuông và một là bất khả chiến bại, hồn ma sẽ bị dịch chuyển.
  • Được dịch chuyển tức thời như một Ghost ngăn bạn di chuyển trong 1 lượt. Khi chơi như một Ghost, bạn chỉ cần bỏ qua lượt của mình
  • Cố gắng di chuyển qua một bức tường sẽ được hiểu là "Ở lại"
  • Mỗi con ma ban đầu sẽ nhận được một trong 4 đặc điểm tính cách, như được mô tả ở đây , với sửa đổi sau:

    1. Các lỗi được mô tả sẽ không được nhân đôi
    2. Tất cả họ sẽ hoạt động ngay từ đầu
    3. Chúng chỉ dễ bị tổn thương đối với người chơi đã ăn viên
    4. Họ sẽ chuyển đổi vô hạn từ phân tán sang đuổi theo, mỗi lần có một số lượt cố định trước khi chuyển đổi
    5. Khi chuyển sang đuổi theo, họ sẽ tìm thấy Pac-Man gần nhất để đuổi theo, và sẽ đuổi theo Pac-Man đó trong suốt thời gian họ đuổi theo. (Nếu có sự ràng buộc cho sự gần gũi, Pac-Man sẽ được chọn giả danh)
    6. Blinky sẽ không tăng tốc
    7. Inky sẽ chọn con ma gần nhất để căn cứ vào tính toán của mình sau khi chuyển sang đuổi theo.
    8. Clyde sẽ tìm tất cả người chơi 8 ô vuông, sau đó theo người chơi xa nhất.
    9. Tất cả ma trừ Clyde sẽ không nhắm mục tiêu đến một người chơi ở xa hơn 5 ô vuông

Tôi sẽ chấp nhận mã có thể biên dịch từ một ngôn ngữ tiêu chuẩn hoặc .exe (có mã đi kèm).

Mẹo lập trình

Bạn có thể với bộ điều khiển của tôi. Bạn cần đặt thư mục / bot / your_bot_name / trong cùng thư mục với chương trình. Trong thư mục, bạn cần thêm một lệnh.txt chứa lệnh để thực thi chương trình của bạn (ví dụ python my_bot.py:) và bot của bạn.

Mã điều khiển nằm trên Github (mã Python, yêu cầu Pygame nếu bạn muốn đồ họa.) Đã thử nghiệm trên windows và linux

QUẦN ÁO

ghostbuster: 72.840 điểm

con đường: 54.570 điểm

cận thị: 50.820 điểm

tránh tương tác: 23.580 điểm

nhà vật lý: 18.330 điểm

ngẫu nhiên: 7.760 điểm

dumbpac: 4.880 điểm


9
+1. Đây là lần đầu tiên tôi thấy từ "Pacmen"
ngay

5
Trông giống như một thử thách thú vị! Nhân tiện: (1) Chúng thực sự được gọi là "máy phát điện" chứ không phải "viên năng lượng". (2) Chữ "M" trong Pac-Man được viết hoa và được gạch nối là "Pac-Man" chứ không phải "Pacman" hay "Pacman". Đây là một nguồn tài nguyên tuyệt vời cho thông tin Pac-Man: home.comcast.net/~jpittman2/pacman/pacmandossier.html
Todd Lehman

2
Bất cứ ai làm việc với thử thách này nên tham gia cùng chúng tôi trong phòng chat dành cho codegolf. chat.stackexchange.com/rooms/240/the-nineteenth-byte
Sparr

1
Được. Bộ điều khiển hiện hoạt động trên windows và linux, nhưng sẽ đóng băng trên windows nếu bot của bạn không phản hồi.
Nathan Merrill

1
Tôi bị mù màu và không thể nói PacMen từ Ghost, chúng ta có thể thay đổi màu sắc không?
Moop

Câu trả lời:


8

GhostBuster - Python

Chọn một điểm ngẫu nhiên trên bản đồ, sử dụng thuật toán A * để tìm đường dẫn tốt nhất về phía trước. Khi đến đích, nó sẽ chọn một cái mới và tiếp tục. Nó sẽ cố gắng tránh ma, nhưng với FOV hạn chế, đôi khi nó sẽ chạy vào chúng. Nó sẽ tránh đi bộ trên các điểm đã truy cập.

  • Đã thêm logic cho ma. Chọn một điểm ngẫu nhiên gần (<8) và di chuyển đến đó, bỏ qua các điểm khác ngoài pacmen
  • Đã thêm logic bất khả chiến bại
  • Giá trị điểm điều chỉnh của hình vuông
  • Lỗi (nếu anh ta quá giỏi và ăn tất cả các viên, trò chơi đóng băng vì một số lý do)

Sử dụng một số mã của Sparr, cảm ơn bạn vì logic.


Windows 7, Visual Studios với Công cụ Python. Nên làm việc trên các hộp linux.

#!/usr/bin/env python

import os
import re
import sys
import math
import random

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # automatically flush stdout

P,G,o,O,F,X = 5,600,-10,-100,-100,10
PreviousSquarePenalty = 10

# read in the maze description
maze_desc = sys.stdin.readline().rstrip()
mazeSize = int(math.sqrt(len(maze_desc)))

North,East,South,West = range(4)
DIRECTIONS = ['N','E','S','W']
Euclidian,Manhattan,Chebyshev = range(3)

sign = lambda x: (1, -1)[x<0]
wrap = lambda v : v % mazeSize

class Node(object):

    def __init__(self, x, y, value):
        self.x, self.y = x,y
        self.wallValue = int(value, 16); #Base 16
        self.nodes = {}
        self.item = 'o' # Start as normal pellet

    def connect(self, otherNode, dir):    
        if dir not in self.nodes:
            self.nodes[dir] = otherNode       
            otherNode.nodes[(dir+2)%4] = self

    def distance(self, otherNode, meth = Manhattan):
        xd = abs(otherNode.x - self.x)        
        yd = abs(otherNode.y - self.y)
        xd = min(xd, mazeSize - xd)
        yd = min(yd, mazeSize - yd)
        if meth == Euclidian:
            return math.sqrt(xd * xd + yd * yd)       
        if meth == Manhattan:
            return xd + yd
        if meth == Chebyshev:      
            return max(xd, yd)

    def direction(self, otherNode):
        for key, value in self.nodes.iteritems():
            if value == otherNode:
                return DIRECTIONS[key]            
        return 'ERROR'

    def getScore(self):
        score = eval(self.item)
        for node in self.nodes.values():
            score += eval(node.item)
        return score

    def nearbyGhost(self):
        if self.item == 'G':
            return True
        for node in self.nodes.values():
            if node.item == 'G':
                return True
        return False

    def __hash__(self):  
        return  (391 + hash(self.x))*23 + hash(self.y)

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __ne__(self, other):
        return (self.x, self.y) != (other.x, other.y)

    def __str__(self):
        return str(self.x)+","+str(self.y)

    def __repr__(self):
        return str(self.x)+","+str(self.y)

# Make all the nodes first
nodes = {}
i = 0
for y in range(mazeSize):
    for x in range(mazeSize):       
        nodes[x,y] = Node(x,y,maze_desc[i])  
        i+=1

# Connect all the nodes together to form the maze
for node in nodes.values():
    walls = node.wallValue
    x,y = node.x,node.y    
    if not walls&1:  
        node.connect(nodes[x,wrap(y-1)], North)
    if not walls&2:
        node.connect(nodes[wrap(x+1),y], East)
    if not walls&4:
        node.connect(nodes[x,wrap(y+1)], South)
    if not walls&8:
        node.connect(nodes[wrap(x-1),y], West)

toVisit = set(nodes.values())
currentNode = None
destinationNode = None
previousNode = None
testInvincibilty = False
invincibility = 0
isGhost = False
turns = 0

def aStar(startNode, endNode):
    openSet = set([startNode])
    closedSet = set()
    gScores = {startNode: 0}
    cameFrom = {}
    curNode = startNode  
    while openSet:
        minF = 100000000
        for node in openSet:
            g = gScores[node]
            h = node.distance(endNode)
            f = g+h
            if f < minF:
                minF = f
                curNode = node

        if curNode == endNode:
            path = []
            while curNode != startNode:
                path.insert(0, curNode)
                curNode = cameFrom[curNode]
            return path

        openSet.remove(curNode)
        closedSet.add(curNode)
        for node in curNode.nodes.values():
            if node in closedSet:
                continue
            g = gScores[curNode]
            if isGhost:
                g += 1
                if node.item == 'P':
                    g -= 10 # prefer PacMen
            else:
                s = node.getScore();
                if invincibility > 1:
                    g -= abs(s) # everything is tasty when invincible
                else:
                    g += s
                if previousNode and node == previousNode:
                    g += PreviousSquarePenalty # penalize previous square
            isBetter = False
            if node not in openSet:
                openSet.add(node)
                isBetter = True
            elif g < gScores[node]:
                isBetter = True
            if isBetter:
                gScores[node] = g
                cameFrom[node]=curNode

# regex to parse a line of input
input_re = re.compile('(?:([-\d]+),([-\d]+)([PGoOFX]?) ?)+')

while True:
    info = sys.stdin.readline().rstrip()
    if (not info) or (info == "Q"):
        break

    turns += 1

    # break a line of input up into a list of tuples (X,Y,contents)
    info = [input_re.match(item).groups() for item in info.split()]

    # update what we know about all the cells we can see
    for cell in info:
        nodes[int(cell[0]),int(cell[1])].item = cell[2]

    currentNode = nodes[int(info[0][0]),int(info[0][1])]    

    if turns == 1:
        print 'X'
        continue

    if not isGhost and currentNode.item == 'G':
        isGhost = True
        destinationNode = random.sample(nodes.values(), 1)[0]

    if isGhost:     
        while destinationNode == currentNode or currentNode.distance(destinationNode) > 8:
            destinationNode = random.sample(nodes.values(), 1)[0]
    else:     

        if invincibility > 0:
            invincibility -=  1

        if testInvincibilty:
            testInvincibilty = False
            if currentNode.item == 'X':
                invincibility += 10

        while not destinationNode or destinationNode == currentNode:
            destinationNode = random.sample(toVisit, 1)[0]

        if currentNode.item == 'X':
            toVisit.discard(currentNode)

    bestPath = aStar(currentNode, destinationNode)

    nextNode = bestPath[0]

    direction = currentNode.direction(nextNode)

    if not isGhost and nextNode.item == 'O':   
        testInvincibilty = True      

    previousNode = currentNode

    print direction

8

thiển cận

Pac này tránh những con ma liền kề trừ khi anh ta có thể ăn chúng, di chuyển lên trái cây hoặc viên liền kề và đi bộ ngẫu nhiên như một phương sách cuối cùng.

#!/usr/bin/env python

import os
import re
import sys
import math
import random

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # automatically flush stdout

# read in the maze description
maze_desc = sys.stdin.readline().rstrip()
maze_size = int(math.sqrt(len(maze_desc)))

# turn the maze description into an array of arrays
# [wall bitmask, item last seen in square]

def chunks(l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]
maze = []
for row in chunks(maze_desc, maze_size):
    maze.append([[int(c,16),'X'] for c in row])

# regex to parse a line of input
input_re = re.compile('(?:([-\d]+),([-\d]+)([PGoOFX]?) ?)+')

turn = 0
invincibility_over = 0
last_move = None

while True:
    info = sys.stdin.readline().rstrip()
    if (not info) or (info == "Q"):
        break

    # break a line of input up into a list of tuples (X,Y,contents)
    info = info.split()
    info = [input_re.match(item).groups() for item in info]

    # update what we know about all the cells we can see
    for cell in info:
        maze[int(cell[1])][int(cell[0])][1] = cell[2]

    # current location
    x = int(info[0][0])
    y = int(info[0][1])

    # which directions can we move from our current location?
    valid_directions = []
    # we might consider sitting still
    # valid_directions.append('X')
    walls = maze[y][x][0]
    if not walls&1:
        valid_directions.append('N')
    if not walls&2:
        valid_directions.append('E')
    if not walls&4:
        valid_directions.append('S')
    if not walls&8:
        valid_directions.append('W')

    # which direction has the highest value item?
    best_value = 0
    best_direction = 'X'
    for c in [(x,y-1,'N'),(x+1,y,'E'),(x,y+1,'S'),(x-1,y,'W')]:
        if c[2] in valid_directions:
            # am I a ghost?
            if maze[y][x][1] == 'G':
                if maze[c[1]%maze_size][c[0]%maze_size][1] == "P":
                    best_value = 999
                    best_direction = c[2]
            else:
                if maze[c[1]%maze_size][c[0]%maze_size][1] == 'F':
                    if best_value < 100:
                        best_value = 100
                        best_direction = c[2]
                elif maze[c[1]%maze_size][c[0]%maze_size][1] == 'O':
                    if best_value < 50:
                        best_value = 50
                        best_direction = c[2]
                elif maze[c[1]%maze_size][c[0]%maze_size][1] == 'o':
                    if best_value < 10:
                        best_value = 10
                        best_direction = c[2]
                elif maze[c[1]%maze_size][c[0]%maze_size][1] == 'G':
                    if turn < invincibility_over:
                        # eat a ghost!
                        if best_value < 200:
                            best_value = 200
                            best_direction = c[2]
                    else:
                        # avoid the ghost
                        valid_directions.remove(c[2])

    # don't turn around, wasteful and dangerous
    if last_move:
        reverse = ['N','E','S','W'][['S','W','N','E'].index(last_move)]
        if reverse in valid_directions:
            valid_directions.remove(reverse)

    if best_value == 50:
        invincibility_over = turn + 10      
    if best_direction != 'X':
        # move towards something worth points
        # sys.stderr.write("good\n")
        last_move = best_direction
    elif len(valid_directions)>0:
        # move at random, not into a wall
        # sys.stderr.write("valid\n")
        last_move = random.choice(valid_directions)
    else:
        # bad luck!
        # sys.stderr.write("bad\n")
        last_move = random.choice(['N','E','S','W'])
    print last_move

    turn += 1

6

người tránh

Tránh tất cả các hồn ma như một pacman, và tất cả các pacman khi một con ma. Cố gắng tránh bất kỳ loại nào của chính nó nếu có thể, và sau đó tránh quay 180 nếu có thể.

#!/usr/bin/env python
import os
import re
import sys
import math
import random

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # automatically flush stdout

# read in the maze description
maze_desc = sys.stdin.readline().rstrip()
maze_size = int(math.sqrt(len(maze_desc)))

# turn the maze description into an array of arrays of numbers indicating wall positions

def chunks(l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]
maze = []
for row in chunks(maze_desc, maze_size):
    maze.append([[int(c,16),'X'] for c in row])

# regex to parse a line of input
input_re = re.compile('(?:([-\d]+),([-\d]+)([PGoOFX]?) ?)+')

last_moved = 'X'

while True:
    info = sys.stdin.readline().rstrip()
    if (not info) or (info == "Q"):
        break

    # break a line of input up into a list of tuples (X,Y,contents)
    info = info.split()
    info = [input_re.match(item).groups() for item in info]

    # location
    x = int(info[0][0])
    y = int(info[0][1])

    # update what we know about all the cells we can see
    for cell in info:
        maze[int(cell[1])][int(cell[0])][1] = cell[2]

    # which directions can we move from our current location?
    valid_directions = []
    walls = maze[y][x][0]
    if not walls&1: 
        valid_directions.append('N')
    if not walls&2:
        valid_directions.append('E')
    if not walls&4:
        valid_directions.append('S')
    if not walls&8:
        valid_directions.append('W')

    bad_directions = []
    for c in [(x,y-1,'N'),(x+1,y,'E'),(x,y+1,'S'),(x-1,y,'W')]:
        if c[2] in valid_directions:
            # am I a ghost?
            if maze[y][x][1] == 'G':
                # it's a pacman, run. interaction is always a risk.
                if maze[c[1]%maze_size][c[0]%maze_size][1] == "P":
                    valid_directions.remove(c[2])
                # another ghost? let me move away.
                elif maze[c[1]%maze_size][c[0]%maze_size][1] == "G":
                    bad_directions.append(c[2])
            else:
                # it's a ghost, run. ghosts are evil.
                if maze[c[1]%maze_size][c[0]%maze_size][1] == "G":
                    valid_directions.remove(c[2])
                # its another pacman, move away!
                elif maze[c[1]%maze_size][c[0]%maze_size][1] == "P":
                    bad_directions.append(c[2])

    # if possible to avoid normal contact, do so
    good_directions = list(set(valid_directions) - set(bad_directions))
    if len(good_directions) > 0:
        valid_directions = good_directions

    # if not the only option, remove going backwards from valid directions
    if len(valid_directions) > 1:
        if last_moved == 'N' and 'S' in valid_directions:
            valid_directions.remove('S')
        elif last_moved == 'S' and 'N' in valid_directions:
            valid_directions.remove('N')
        elif last_moved == 'W' and 'E' in valid_directions:
            valid_directions.remove('E')
        elif 'W' in valid_directions:
            valid_directions.remove('W')

    # if possible, continue in the same direction
    if last_moved in valid_directions:
        print last_moved
    # prefer turning left/right randomly instead of turning 180
    #   backwards has been removed from valid_directions if not
    #   the only option
    elif len(valid_directions) > 0:
        last_moved=random.choice(valid_directions)
        print last_moved
    # can't move, so stay put. desire to continue in original 
    #   direction remains.
    else:
        print 'X'

Câu trả lời này ném một lỗi. Bạn chưa xác định x hoặc y
Nathan Merrill

Tệp "néer.py", dòng 42, trong <module> maze [int (cell [1])] [int (cell [0])] [1] = cell [2] Đối tượng TypeError: 'int' không hỗ trợ chuyển nhượng vật phẩm
Nathan Merrill

valid_directions.remove ('W') ValueError: list.remove (x): x không có trong danh sách
Nathan Merrill

@NathanMerrill Nên sửa ngay.
es1024

4

Nhà vật lý, Haskell

Nhà vật lý Pac-Man tin rằng định luật vạn vật hấp dẫn của Newton có thể giúp anh ta giành chiến thắng trong trò chơi. Sau đó, anh ta chỉ áp dụng nó cho tất cả các đối tượng khác mà anh ta biết trong trò chơi. Vì nhà vật lý đã già và có trí nhớ kém, anh ta chỉ có thể nhớ những thứ trong 5 vòng. Hooooly, trí nhớ tồi thực sự giúp anh ta ghi điểm tốt hơn.

Câu trả lời này có hai fille:

  • Main.hs, chứa phần thú vị.
  • Pacman.hs, chỉ cần một số mã nhàm chán xử lý giao thức. Bạn có thể sử dụng nó để viết giải pháp haskell của riêng bạn. Nó không chứa bất kỳ mã AI nào.

Oh, đợi đã, chúng tôi cũng có một Makefile.

Họ đến đây:

Chính

import Pacman
import Data.Complex
import Data.List
import Data.Function
import qualified Data.Map as Map
import Data.Maybe
import System.IO

data DebugInfo = DebugInfo {
  debugGrid :: Grid
, debugForce :: Force
, debugAction :: Action
} deriving (Show)

data Physicist = Physicist [(Int, Object)] (Maybe DebugInfo)

type Force = Complex Double


calcForce :: Int -> Position -> PlayerType -> Object -> Force
calcForce n p1 t1 object = if d2 == 0 then 0 else base / (fromIntegral d2 ** 1.5 :+ 0)
  where
    (x1, y1) = p1
    (x2, y2) = p2
    wrap d = minimumBy (compare `on` abs) [d, n - d]
    dx = wrap $ x2 - x1
    dy = wrap $ y2 - y1
    Object t2 p2 = object
    d2 = dx * dx + dy * dy
    base = (fromIntegral dx :+ fromIntegral dy) * case t1 of
      PacmanPlayer -> case t2 of
        Pellet -> 10.0
        PowerPellet -> 200.0
        Ghost -> -500.0
        Pacman -> -20.0
        Fruit -> 100.0
        Empty -> -2.0
      GhostPlayer -> case t2 of
        Pellet -> 10.0
        PowerPellet -> 200.0
        Ghost -> -50.0
        Pacman -> 500.0
        Fruit -> 100.0
        Empty -> -2.0

instance PlayerAI Physicist where
  findAction player info = (action, player') where
    Player {
      playerType = type_
    , playerField = field
    , playerMemory = Physicist objectsWithAge _
    } = player

    n = fieldSize field
    NormalRound pos _ objects = info
    objectsWithAge' = combineObjects objectsWithAge objects
    objects' = map snd objectsWithAge'
    directionChoices = filter (not . gridHasWall grid) directions4
    totalForce = sum $ map (calcForce n pos type_) objects'
    grid = fromMaybe (error $ "invalid position " ++ show pos) $ (fieldGetGrid field) pos
    action = if magnitude totalForce < 1e-10
      then if null directionChoices
        then Stay
        else Move $ head directionChoices
      else Move $ maximumBy (compare `on` (projectForce totalForce)) directionChoices
    debugInfo = Just $ DebugInfo grid totalForce action
    player' = player {
      playerMemory = Physicist objectsWithAge' debugInfo
    }

  -- roundDebug player _ = do
  --   let Physicist objects debugInfo = playerMemory player
  --       type_ = playerType player
  --   hPrint stderr (objects, debugInfo)

combineObjects :: [(Int, Object)] -> [Object] -> [(Int, Object)]
combineObjects xs ys = Map.elems $ foldr foldFunc initMap ys where
  foldFunc object@(Object type_ pos) = Map.insert pos (0, object)
  addAge (age, object) = (age + 1, object)
  toItem (age, object@(Object _ pos)) = (pos, (age, object))
  initMap = Map.fromList . map toItem . filter filterFunc . map addAge $ xs
  filterFunc (age, object@(Object type_ _))
    | type_ == Empty = True
    | age < maxAge = True
    | otherwise = False

maxAge = 5

projectForce :: Force -> Direction -> Double
projectForce (fx :+ fy) (Direction dx dy) = fx * fromIntegral dx + fy * fromIntegral dy

main :: IO ()
main = runAI $ Physicist [] Nothing

Pacman.hs

module Pacman (
    Field(..)
  , Grid(..)
  , Direction(..)
  , directions4, directions8
  , Position
  , newPosition
  , Player(..)
  , PlayerType(..)
  , ObjectType(..)
  , Object(..)
  , RoundInfo(..)
  , Action(..)
  , runAI
  , PlayerAI(..)
  ) where

import Data.Bits
import Data.Char
import Data.List
import Data.Maybe
import qualified Data.Map as Map
import qualified System.IO as SysIO

data Field = Field {
  fieldGetGrid :: Position -> Maybe Grid
, fieldSize :: Int
}

data Grid = Grid {
  gridHasWall :: Direction -> Bool
, gridPos :: Position
}

instance Show Grid where
  show g = "Grid " ++ show (gridPos g) ++ ' ':reverse [if gridHasWall g d then '1' else '0' | d <- directions4]

data Direction = Direction Int Int
  deriving (Show, Eq)

directions4, directions8 :: [Direction]
directions4 = map (uncurry Direction) [(-1, 0), (0, 1), (1, 0), (0, -1)]
directions8 = map (uncurry Direction) $ filter (/=(0, 0)) [(dx, dy) | dx <- [-1..1], dy <- [-1..1]]

type Position = (Int, Int)
newPosition :: (Int, Int)  -> Position
newPosition = id

data Player a = Player {
  playerType :: PlayerType
, playerField :: Field
, playerRounds :: Int
, playerMemory :: a
}
data PlayerType = PacmanPlayer | GhostPlayer
  deriving (Show, Eq)

class PlayerAI a where
  onGameStart :: a -> Field -> IO ()
  onGameStart _ _ = return ()

  onGameEnd :: a -> IO ()
  onGameEnd _ = return ()

  findAction :: Player a -> RoundInfo -> (Action, Player a)

  roundDebug :: Player a -> RoundInfo -> IO ()
  roundDebug _ _ = return ()


data ObjectType = Pacman | Ghost | Fruit | Pellet | PowerPellet | Empty
  deriving (Eq, Show)
data Object = Object ObjectType Position
  deriving (Show)

data RoundInfo = EndRound | NormalRound Position PlayerType [Object]

data Action = Stay | Move Direction
  deriving (Show)


parseField :: String -> Field
parseField s = if validateField field
  then field 
  else error $ "Invalid field: " ++ show ("n", n, "s", s, "fieldMap", fieldMap)
  where
    field = Field {
      fieldGetGrid = flip Map.lookup fieldMap
    , fieldSize = n
    }
    (n : _) = [x | x <- [0..], x * x == length s]
    fieldMap = Map.fromList [
        ((i, j), parseGrid c (newPosition (i, j))) 
        | (i, row) <- zip [0..n-1] rows,
          (j, c) <- zip [0..n-1] row
      ]
    rows = reverse . snd $ foldr rowFoldHelper (s, []) [1..n]
    rowFoldHelper _ (s, rows) =
      let (row, s') = splitAt n s
      in (s', row:rows)

validateField :: Field -> Bool
validateField field@(Field { fieldGetGrid=getGrid, fieldSize=n }) = 
  all (validateGrid field) $ map (fromJust.getGrid) [(i, j) | i <- [0..n-1], j <- [0..n-1]]

validateGrid :: Field -> Grid -> Bool
validateGrid
  field@(Field { fieldGetGrid=getGrid, fieldSize=n })
  grid@(Grid { gridPos=pos })
  = all (==True) [gridHasWall grid d == gridHasWall (getNeighbour d) (reverse d) | d <- directions4]
  where
    reverse (Direction dx dy) = Direction (-dx) (-dy)
    (x, y) = pos
    getNeighbour (Direction dx dy) = fromJust . getGrid . newPosition $ (mod (x + dx) n, mod (y + dy) n)

parseGrid :: Char -> Position -> Grid
parseGrid c pos = Grid gridHasWall pos
  where
    walls = zip directions4 bits
    bits = [((x `shiftR` i) .&. 1) == 1 | i <- [0..3]]
    Just x = elemIndex (toLower c) "0123456789abcdef"
    gridHasWall d = fromMaybe (error $ "No such direction " ++ show d) $
      lookup d walls

parseRoundInfo :: String -> RoundInfo
parseRoundInfo s = if s == "Q" then EndRound else NormalRound pos playerType objects'
  where
    allObjects = map parseObject $ words s
    Object type1 pos : objects = allObjects
    objects' = if type1 `elem` [Empty, Ghost] then objects else allObjects
    playerType = case type1 of
      Ghost -> GhostPlayer
      _ -> PacmanPlayer

parseObject :: String -> Object
parseObject s = Object type_ (newPosition (x, y)) where
  (y, x) = read $ "(" ++ init s ++ ")"
  type_ = case last s of
    'P' -> Pacman
    'G' -> Ghost
    'o' -> Pellet
    'O' -> PowerPellet
    'F' -> Fruit
    'X' -> Empty
    c -> error $ "Unknown object type: " ++ [c]

sendAction :: Action -> IO ()
sendAction a = putStrLn name >> SysIO.hFlush SysIO.stdout where
  name = (:[]) $ case a of
    Stay -> 'X'
    Move d -> fromMaybe (error $ "No such direction " ++ show d) $
      lookup d $ zip directions4 "NESW"

runPlayer :: PlayerAI a => Player a -> IO ()
runPlayer player = do
  roundInfo <- return . parseRoundInfo =<< getLine
  case roundInfo of
    EndRound -> return ()
    info@(NormalRound _ type_' _) -> do
      let
        updateType :: Player a -> Player a
        updateType player = player { playerType = type_' }
        player' = updateType player
        (action, player'') = findAction player' info
      roundDebug player'' info
      sendAction action
      let 
        updateRounds :: Player a -> Player a
        updateRounds player = player { playerRounds = playerRounds player + 1}
        player''' = updateRounds player''
      runPlayer player'''

runAI :: PlayerAI a => a -> IO ()
runAI mem = do
  field <- return . parseField =<< getLine
  let player = Player {
    playerType = PacmanPlayer
  , playerField = field
  , playerRounds = 0
  , playerMemory = mem
  }
  runPlayer player

Makefile

physicist: Main.hs Pacman.hs
    ghc -O3 -Wall Main.hs -o physicist

lệnh.txt

./physicist

Tôi không thể chạy cái này. Tôi nhận được "Tên tệp không khớp với tên mô-đun: Saw Main' Expected Pacman '" khi tôi cố gắng tạo tên đó. Ngoài ra, để chạy nó, tôi chỉ cần thực hiện nó, hoặc có một lệnh khác tôi cần chạy?
Nathan Merrill

@NathanMerrill Trước tiên bạn nên tạo nó sau đó chạy physicisttệp thực thi. Chỉnh sửa và thêm command.txt, bây giờ.
Ray

Tôi đang làm nó Các lỗi tôi liệt kê được ném khi tôi thực hiện nó. Cũng giả sử bạn đang ở trong thư mục vật lý. Nó sẽ không phải là nhà vật lý ghc trong lệnh.txt?
Nathan Merrill

@NathanMerrill Thật lạ. Có thể do hành vi khác nhau của GHC trên Windows. Đổi tên physicist.hsthành Main.hscó thể làm việc. Tôi đã cập nhật câu trả lời.
Ray

@NathanMerrill Bạn đã kết hợp hai tệp này? Điều đó sẽ không làm việc.
Ray

3

dumbpac

Pac này chỉ di chuyển ngẫu nhiên, không liên quan đến bố cục mê cung hoặc ma hoặc bất kỳ yếu tố nào khác.

Perl:

#!/usr/bin/perl
local $| = 1; # auto flush!
$maze_desc = <>;
while(<>) { 
    if($_ eq "Q"){
        exit;
    }
    $move = (("N","E","S","W","X")[rand 5]);
    print ($move . "\n");
}

Con trăn

#!/usr/bin/env python

import os
import sys
import random

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # automatically flush stdout

maze_desc = sys.stdin.readline().rstrip()
while True:
    info = sys.stdin.readline().rstrip()
    if (not int) or (info == "Q"):
        break
    print random.choice(['N', 'E', 'S', 'W', 'X'])

3

ngẫu nhiên

pac này đi ngẫu nhiên, nhưng không vào tường

#!/usr/bin/env python

import os
import re
import sys
import math
import random

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # automatically flush stdout

# read in the maze description
maze_desc = sys.stdin.readline().rstrip()
maze_size = int(math.sqrt(len(maze_desc)))

# turn the maze description into an array of arrays of numbers indicating wall positions
def chunks(l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]
map = []
for row in chunks(maze_desc, maze_size):
    map.append([int(c,16) for c in row])

# regex to parse a line of input
input_re = re.compile('(?:([-\d]+),([-\d]+)([PGoOFX]?) ?)+')

while True:
    info = sys.stdin.readline().rstrip()
    if (not info) or (info == "Q"):
        break

    # break a line of input up into a list of tuples (X,Y,contents)
    info = info.split()
    info = [input_re.match(item).groups() for item in info]

    # this pac only cares about its current location
    info = info[0]

    # which directions can we move from our current location?
    valid_directions = []
    # we might consider sitting still
    # valid_directions.append('X')
    walls = map[int(info[1])][int(info[0])]
    if not walls&1:
        valid_directions.append('N')
    if not walls&2:
        valid_directions.append('E')
    if not walls&4:
        valid_directions.append('S')
    if not walls&8:
        valid_directions.append('W')

    # move at random, not into a wall
    print random.choice(valid_directions)

1

Bệnh hoạn, Python 3

Bot này sử dụng đường dẫn tìm rất nhiều. Với một vị trí bắt đầu và điều kiện kết thúc, nó sử dụng BFS đơn giản để tìm đường đi ngắn nhất. Tìm đường dẫn được sử dụng trong:

  • Tìm viên năng lượng, trái cây hoặc viên.
  • Nếu nó bất khả chiến bại, hãy đuổi theo ma
  • Nếu đó là ma, hãy đuổi theo Pac-Men
  • Chạy trốn khỏi ma
  • Tính khoảng cách giữa một cặp vị trí nhất định.

lệnh.txt

python3 pathy.py

pathy.py

import sys
import random
from collections import deque

DIRECTIONS = [(-1, 0), (0, 1), (1, 0), (0, -1)]
GHOST = 'G'
PACMAN = 'P'
FRUIT = 'F'
PELLET = 'o'
POWER_PELLET = 'O'
EMPTY = 'X'

PACMAN_PLAYER = 'pacman-player'
GHOST_PLAYER = 'ghost-player'


class Field:
    def __init__(self, s):
        n = int(.5 + len(s) ** .5)
        self.size = n
        self.mp = {(i, j): self.parse_grid(s[i * n + j]) for i in range(n) for j in range(n)}

    @staticmethod
    def parse_grid(c):
        x = int(c, 16)
        return tuple((x >> i) & 1 for i in range(4))

    def can_go_dir_id(self, pos, dir_id):
        return self.mp[pos][dir_id] == 0

    def connected_neighbours(self, pos):
        return [(d, self.go_dir_id(pos, d)) for d in range(4) if self.can_go_dir_id(pos, d)]

    def find_path(self, is_end, start):
        que = deque([start])
        prev = {start: None}
        n = self.size

        def trace_path(p):
            path = []
            while prev[p]:
                path.append(p)
                p = prev[p]
            path.reverse()
            return path

        while que:
            p = x, y = que.popleft()
            if is_end(p):
                return trace_path(p)
            for d, p1 in self.connected_neighbours(p):
                if p1 in prev:
                    continue
                prev[p1] = p
                que.append(p1)
        return None

    def go_dir_id(self, p, dir_id):
        dx, dy = DIRECTIONS[dir_id]
        x, y = p
        n = self.size
        return (x + dx) % n, (y + dy) % n

    def distance(self, p1, p2):
        return len(self.find_path((lambda p: p == p2), p1)) 

    def get_dir(self, p1, p2):
        x1, y1 = p1
        x2, y2 = p2
        return (self.dir_wrap(x2 - x1), self.dir_wrap(y2 - y1))

    def dir_wrap(self, x):
        if abs(x) > 1:
            return 1 if x < 0 else -1
        return x


class Player:
    def __init__(self, field):
        self.field = field

    def interact(self, objects):
        " return: action: None or a direction_id"
        return action

    def send(self, msg):
        print(msg)
        sys.stdout.flush()


class Pathy(Player):
    FLEE_COUNT = 8

    def __init__(self, field):
        super().__init__(field)
        self.type = PACMAN_PLAYER
        self.pos = None
        self.mem_field = {p: GHOST for p in self.field.mp}
        self.power = 0
        self.flee = 0
        self.ghost_pos = None
        self.ghost_distance = None

    @property
    def invincible(self):
        return self.type == PACMAN_PLAYER and self.power > 0

    def detect_self(self, objects):
        ((x, y), type) = objects[0]
        self.type = GHOST_PLAYER if type == GHOST else PACMAN_PLAYER
        self.pos = (x, y)

    def update_mem_field(self, objects):
        for (p, type) in objects:
            self.mem_field[p] = type

    def find_closest_ghost_pos(self, objects):
        try:
            return min(
                (p for (p, t) in objects if t == GHOST),
                key=lambda p: self.field.distance(self.pos, p)
            )
        except:
            return None

    def chase(self, types):
        is_end = lambda p: self.mem_field[p] in types
        path = self.field.find_path(is_end, self.pos)
        if not path:
            return None
        return DIRECTIONS.index(self.field.get_dir(self.pos, path[0]))

    def interact(self, objects):
        self.detect_self(objects)
        self.update_mem_field(objects)

        action = None
        if self.invincible:
            self.debug('invincible!!!')
            action = self.chase((GHOST,))
            if action is None:
                action = self.chase((POWER_PELLET,))
            if action is None:
                action = self.chase((FRUIT, PELLET,))
        elif self.type == GHOST_PLAYER:
            action = self.chase((PACMAN,))
        else:
            # PACMAN_PLAYER
            ghost_pos = self.find_closest_ghost_pos(objects)
            if ghost_pos:
                ghost_distance = self.field.distance(ghost_pos, self.pos)
                if not self.flee or ghost_distance < self.ghost_distance:
                    self.flee = self.FLEE_COUNT
                    self.ghost_distance = ghost_distance
                    self.ghost_pos = ghost_pos

            if self.flee > 0:
                self.flee -= 1
                action = max(
                    self.field.connected_neighbours(self.pos),
                    key=lambda dp: self.field.distance(dp[1], self.ghost_pos)
                )[0]
                # self.debug('flee: ghost_pos {} pos {} dir {} dist {}'.format(
                #     self.ghost_pos, self.pos, DIRECTIONS[action], self.field.distance(self.pos, self.ghost_pos)))
            else:
                self.ghost_pos = self.ghost_distance = None
                action = self.chase((POWER_PELLET, FRUIT))
                if action is None:
                    action = self.chase((PELLET,))
                if action is None:
                    action = random.choice(range(5))
                    if action > 3:
                        action = None

        # Detect power pellet
        if action is None:
            next_pos = self.pos
        else:
            next_pos = self.field.go_dir_id(self.pos, action)
        if self.mem_field[next_pos] == POWER_PELLET:
            self.power = 9
        elif self.invincible and self.mem_field[next_pos] == GHOST:
            self.debug('Got a ghost!')
        else:
            self.power = max(0, self.power - 1)
        return action

    def debug(self, *args, **kwargs):
        return
        print(*args, file=sys.stderr, **kwargs)


def start_game(player_class):
    field = Field(input())
    player = player_class(field)
    while True:
        line = input()
        if line == 'Q':
            break
        objects = [(tuple(map(int, x[:-1].split(',')))[::-1], x[-1]) for x in line.split(' ')]
        action = player.interact(objects)
        player.send('NESW'[action] if action is not None else 'X')


if __name__ == '__main__':
    start_game(Pathy)

objects = [(tuple(map(int, x[:-1].split(',')))[::-1], x[-1]) for x in line.split(' ')]ném mộtValueError: invalid literal for int() with base 10: '8o'
Nathan Merrill

Bộ điều khiển đã gửi cái gì? Có phải nó thất bại mọi lúc? Nó hoạt động ở đây và tôi nghĩ rằng tuyên bố này nên hoạt động tốt.
Ray
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.