การเขียนโปรแกรม Pac-Man


31

โปรแกรมของ Pac-Man

การตั้งค่า

คุณเล่นเป็น Pac-Man คุณต้องการรวบรวมเม็ดผลไม้และเม็ดพลังงานก่อนใครในขณะที่หลีกเลี่ยงผี

กฎระเบียบ

  1. Pac-Man ที่ถูกต้องทุกคนจะอยู่ในเขาวงกตเดียว ผู้เล่นที่มีคะแนนสะสมสูงสุดหลังจาก 10 เกมจะเป็นผู้ชนะ
  2. เกมจะจบลงเมื่อ Pac-Men ทั้งหมดตายไปเม็ดทั้งหมดจะหายไปหรือครบ 500 รอบ
  3. หาก Pac-Man ตายเขาจะยังคงเล่นเป็นผี
  4. กินเม็ดพลังงานจะทำให้คุณอยู่ยงคงกระพันเป็นเวลา 10 รอบและช่วยให้คุณกินผี
  5. กินผีจะส่งผีไปยังตำแหน่งสุ่ม
  6. ผีไม่สามารถกินอะไรได้นอกจาก Pac-Men และไม่ได้รับคะแนนใด ๆ
  7. การรับประทานรายการต่อไปนี้เป็น Pac-Man คุณจะได้รับคะแนนต่อไปนี้:
    1. เม็ด: 10
    2. เม็ดพลังงาน: 50
    3. ผลไม้: 100
    4. ผี: 200

เขาวงกต

หากมีn Pac-Men แล้วเขาวงกตของขนาดsqrt(n)*10โดยsqrt(n)*10จะได้รับการสร้างขึ้นโดยใช้อัลกอริทึมของ Prim (เนื่องจากมันปัจจัยแม่น้ำต่ำ) ถักสมบูรณ์แล้วให้ตั้งค่าไปยังปลายตายที่มีอยู่แล้ว นอกจากนี้การถักเปียนี้สามารถทำได้ข้ามขอบเพื่อให้มีเส้นทางไม่กี่จากบนลงล่างและจากซ้ายไปขวา

จะมี:

  1. 2n ผี
  2. 4n เม็ดพลังงาน
  3. 2n ผลไม้
  4. n Pac-Men ในจุดที่ช่องว่างของเพื่อนบ้านที่เชื่อมต่อนั้นว่างเปล่า
  5. จุดที่ว่างเปล่าที่เหลือทั้งหมดจะถูกเติมด้วยเม็ด

ดังนั้นแผนที่เริ่มต้นที่มีผู้เล่น 10 คนจะมีลักษณะเช่นนี้ (Ghosts = green, Pellets = aqua, fruit = red, Pac-Man = yellow):

เขาวงกต

Input / Output

ในตอนต้นของเกมคุณจะได้รับอักขระหนึ่งบรรทัดซึ่งแสดงถึงกำแพงในทุกตารางของแผนที่ สำหรับแต่ละช่องสี่เหลี่ยมเริ่มต้นด้วยซ้ายบนขวาเคลื่อนที่และล้อมรอบบรรทัดถัดไปคุณจะได้รับเลขฐานสิบหกที่แสดงถึงสถานการณ์ของกำแพง:

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

ใส่อย่างง่ายทิศเหนือ = 1, ตะวันออก = 2, ใต้ = 4 และตะวันตก = 8, รวมเข้าด้วยกัน

จากนั้นในแต่ละตาคุณจะได้รับตำแหน่งปัจจุบันของคุณและรายการในสายตาของคุณ (ถ้าคุณเป็น Pac-Man ผีทั้งหมดจะได้รับสี่เหลี่ยมทั้งหมดตั้งแต่ -5 ถึง +5 จากตำแหน่งที่สัมพันธ์กัน สายตาของคุณจะขึ้นอยู่กับทิศทางที่คุณเดินทางไปทางโค้งสุดท้าย หากคุณเดินทางไปทางเหนือคุณจะได้รับสี่เหลี่ยมทั้งหมดโดยตรงเหนือตะวันออกและตะวันตกของคุณจนกว่ากำแพงจะตัดมุมมองของคุณบวกกับสี่เหลี่ยมจัตุรัสตะวันตกเฉียงเหนือและตะวันออกเฉียงเหนือเดียวหากไม่มีกำแพงตัดมุมมองของคุณ หากคุณเลือกที่จะไม่ย้ายคุณจะได้รับสี่เหลี่ยมทั้ง 8 ทิศทาง

สำหรับภาพIหมายถึงมองไม่เห็นVหมายถึงมองเห็นได้Pหมายถึง Pac-Man (สมมติว่า Pac-Man หันหน้าไปทางทิศเหนือ):

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

แต่ละตารางจะได้รับจากพิกัดแล้วเนื้อหาของมัน มันเป็นเนื้อหาที่แสดงโดยตัวละครต่อไปนี้:

  1. P: Pac-Man 1 รายการขึ้นไป
  2. G: 1 ผีขึ้นไป
  3. o: เม็ด
  4. O: เม็ดพลังงาน
  5. F: ชิ้นส่วนของผลไม้
  6. X: ไม่มีอะไร

หากมีผีและอย่างอื่นในสี่เหลี่ยมGจะถูกส่งกลับ

ดังนั้นถ้าคุณอยู่ที่จตุรัส23,70คุณเพิ่งขยับไปทางเหนือสี่เหลี่ยมจัตุรัสเหนือคุณคือจุดจบและบรรจุเม็ดพลังงานและคุณมีกำแพงทั้งสองด้านของคุณอินพุตของคุณจะเป็น:

23,70X 22,70O

บนสแควร์ปัจจุบันของคุณมันจะแสดงGว่าคุณเป็นผีหรือไม่Pถ้ามีPac-Man อีกอันหนึ่งอยู่ในสแควร์ของคุณX

จากนั้นคุณจะส่งคืนรายการต่อไปนี้ผ่าน STDOUT:

อักขระตัวเดียวที่แสดงทิศทาง ( North, East, South, West หรือXStay)

ก่อนที่จะผ่านไปในทิศทางหนึ่งคุณอาจผ่านไปในพิกัดใด ๆ เช่นx,yกันและผนังของสี่เหลี่ยมนั้นจะถูกส่งกลับ (ดังอธิบายข้างต้น)

โปรแกรมจะต้องทำงานอย่างต่อเนื่องจนกว่าQจะผ่านมันผ่านทาง STDIN โปรแกรมจะเริ่มใหม่สำหรับแต่ละเกม

การเข้าถึงข้อมูลอื่นนอกเหนือจากสิ่งที่ส่งผ่านไปยัง STDIN (รวมถึงข้อมูล Pac-Men อื่น ๆ หรือข้อมูลที่โฮสต์โดยโปรแกรมโฮสต์) ไม่ได้รับอนุญาต

ความล้มเหลวในการส่งคืนการย้ายภายใน 1,000 มิลลิวินาทีจะเป็นการยุติโปรแกรม (การรันบนเครื่อง Win8 ที่เหมาะสมของฉัน) คุณจะได้รับ 2 วินาทีในการประมวลผลรูปแบบเขาวงกตเริ่มต้นเมื่อได้รับ

โฮสต์จะถูกเขียนใน Python และรหัสเพื่อทดสอบบอทของคุณกำลังจะมาถึง

กรณีพิเศษ

  • หาก Pac-Men หลายคนจบลงที่สถานที่เดียวกันไม่ได้รับเนื้อหาของจัตุรัสปัจจุบันเว้นแต่ว่าพวกเขา 1 คนจะอยู่ยงคงกระพันในกรณีนี้ Pac-Man ที่อยู่ยงคงกระพันจะได้รับเม็ด
  • Pac-Man ที่ผีกินเข้าไปจะไม่ถูกเคลื่อนย้ายไปที่อื่น หาก Pac-Men สองคนอยู่ในจตุรัสและอีกคนอยู่ยงคงกระพันผีจะหายตัวไป
  • การถูกเทเลพอร์ตเป็นผีจะป้องกันไม่ให้คุณเคลื่อนที่ 1 เทิร์น เมื่อเล่นเป็นผีคุณก็จะได้ตาของคุณข้าม
  • ความพยายามที่จะย้ายผ่านกำแพงจะถูกตีความว่าเป็น "อยู่"
  • ผีเริ่มต้นแต่ละตัวจะได้รับหนึ่งใน 4 ลักษณะตามที่อธิบายไว้ที่นี่พร้อมกับการดัดแปลงต่อไปนี้:

    1. ข้อบกพร่องที่อธิบายไว้จะไม่ซ้ำกัน
    2. พวกเขาทั้งหมดจะเปิดใช้งานตั้งแต่ต้น
    3. พวกเขามีความเสี่ยงต่อผู้เล่นที่กินเม็ดเท่านั้น
    4. พวกเขาจะเปลี่ยนจากการกระจายไปยังการไล่ล่าอย่างไม่มีกำหนดแต่ละคนมีจำนวนรอบที่แน่นอนก่อนที่จะเปลี่ยน
    5. เมื่อเปลี่ยนไปใช้การไล่ล่าพวกเขาจะพบ Pac-Man ที่ใกล้ที่สุดเพื่อไล่ล่าและจะไล่ล่า Pac-Man นั้นในช่วงระยะเวลาของการไล่ล่า (หากมีเน็คไทสำหรับความใกล้ชิด Pac-Man จะได้รับการสุ่มหลอก)
    6. บลิงกี้จะไม่เร่งความเร็ว
    7. Inky จะเลือกผีที่อยู่ใกล้ที่สุดเพื่อยึดการคำนวณของเขาหลังจากเปลี่ยนไปใช้การไล่ล่า
    8. ไคลด์จะพบผู้เล่นทั้งหมด 8 สแควร์สจากนั้นทำตามผู้เล่นที่ไกลที่สุด
    9. ผีทั้งหมดยกเว้นไคลด์จะไม่พุ่งเป้าไปยังผู้เล่นที่อยู่ไกลออกไปมากกว่า 5 สี่เหลี่ยม

ฉันจะยอมรับรหัสที่คอมไพล์ได้จากภาษามาตรฐานหรือ. exe (พร้อมกับรหัสประกอบ)

เคล็ดลับการเขียนโปรแกรม

คุณอาจมีตัวควบคุมของฉัน คุณต้องใส่ a / bots / your_bot_name / folder ในไดเรกทอรีเดียวกับโปรแกรม ภายในโฟลเดอร์คุณจะต้องเพิ่ม command.txt ที่มีคำสั่งเพื่อรันโปรแกรมของคุณ (เช่น:) python my_bot.pyและบอทของคุณ

รหัสคอนโทรลเลอร์อยู่บนGithub (รหัส Python ต้องการ Pygame หากคุณต้องการกราฟิก) ทดสอบบน windows และ linux

คะแนน

ghostbuster: 72,840 คะแนน

น่ากลัว: 54,570 คะแนน

สายตาสั้น: 50,820 คะแนน

หลีกเลี่ยงการโต้ตอบ: 23,580 คะแนน

นักฟิสิกส์: 18,330 คะแนน

Randomwalk: 7,760 คะแนน

dumbpac: 4,880 คะแนน


9
+1 นี่เป็นครั้งแรกที่ฉันเห็นคำว่า "Pacmen"
justhalf

5
ดูเหมือนว่าจะเป็นการท้าทายที่สนุก! โดยวิธีการ: (1) จริงๆแล้วพวกเขาเรียกว่า "พลังงาน" และไม่ใช่ "พลังงานเม็ด" (2) "M" ใน Pac-Man นั้นเป็นตัวพิมพ์ใหญ่และมีการใส่ยัติภังค์เป็น "Pac-Man" และไม่ใช่ "Pacman" หรือ "PacMan" นี่เป็นแหล่งข้อมูลที่ยอดเยี่ยมสำหรับข้อมูล Pac-Man: home.comcast.net/~jpittman2/pacman/pacmandossier.html
Todd Lehman

2
ทุกคนที่ทำงานกับความท้าทายนี้ควรเข้าร่วมกับเราในห้องแชทเพื่อ codegolf chat.stackexchange.com/rooms/240/the-nineteenth-byte
Sparr

1
ตกลง. ตอนนี้คอนโทรลเลอร์ใช้งานได้บน windows และ linux แต่จะหยุดทำงานบน windows หาก bot ของคุณไม่ตอบสนอง
Nathan Merrill

1
ฉันตาบอดสีและไม่สามารถบอก PacMen จากผีได้เราจะเปลี่ยนสีได้ไหม?
Moop

คำตอบ:


8

GhostBuster - Python

เลือกจุดสุ่มบนแผนที่ใช้อัลกอริทึม A * เพื่อค้นหาเส้นทางที่ดีที่สุดไปข้างหน้า เมื่อถึงปลายทางแล้วจะเลือกใหม่และดำเนินการต่อ มันจะพยายามหลีกเลี่ยงผี แต่ด้วย FOV ที่ จำกัด มันจะวิ่งเข้าหาพวกมันเป็นครั้งคราว มันจะหลีกเลี่ยงการเดินบนจุดที่เคยเยี่ยมชมแล้ว

  • เพิ่มลอจิกสำหรับผี เลือกจุดสุ่มใกล้ (<8) แล้วเคลื่อนที่ไปที่นั่นโดยไม่คำนึงถึงคะแนนนอกเหนือจาก pacmen
  • เพิ่มตรรกะการอยู่ยงคงกระพัน
  • ปรับค่าจุดของกำลังสอง
  • บั๊ก (ถ้าเขาเก่งเกินไปและกินเม็ดทั้งหมดเกมค้างด้วยเหตุผลบางอย่าง)

ใช้โค้ดบางส่วนของ Sparr ขอขอบคุณสำหรับเหตุผล


Windows 7, Visual Studios พร้อม Python Tools ควรทำงานกับกล่อง 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

สั้น

แพคนี้หลีกเลี่ยงผีที่อยู่ติดกันยกเว้นว่าเขาสามารถกินพวกมันเคลื่อนไปยังผลไม้หรือเม็ดที่อยู่ติดกันและเดินสุ่มเป็นทางเลือกสุดท้าย

#!/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

avoider

หลีกเลี่ยงผีทั้งหมดเป็น pacman และ pacmen ทั้งหมดเมื่อผี พยายามหลีกเลี่ยงชนิดใด ๆ ของตัวเองถ้าเป็นไปได้แล้วหลีกเลี่ยงการเปลี่ยน 180 ถ้าเป็นไปได้

#!/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'

คำตอบนี้ส่งข้อผิดพลาด คุณยังไม่ได้กำหนด x หรือ y
นาธานเมอร์ริลล์

ไฟล์ "Avoider.py", บรรทัดที่ 42, ใน <module> maze [int (เซลล์ [1])] [int (เซลล์ [0])] [1] = เซลล์ [2] TypeError: วัตถุ 'int' ไม่สนับสนุน การมอบหมายรายการ
Nathan Merrill

valid_directions.remove ('W') ValueError: list.remove (x): x ไม่อยู่ในรายการ
Nathan Merrill

@NathanMerrill ควรได้รับการแก้ไขแล้ว
es1024

4

นักฟิสิกส์ Haskell

Pac-Man นักฟิสิกส์เชื่อว่ากฎแรงโน้มถ่วงสากลของนิวตันสามารถช่วยให้เขาชนะการแข่งขัน จากนั้นเขาก็นำมันไปใช้กับวัตถุอื่น ๆ ทั้งหมดที่เขารู้ในระหว่างเกม เนื่องจากนักฟิสิกส์นั้นมีอายุมากและมีความจำไม่ดีเขาจึงสามารถจำได้เพียง 5 รอบเท่านั้น Hooooly ความทรงจำที่แย่จริงๆช่วยให้เขาทำคะแนนได้ดีขึ้น

คำตอบนี้มีสองไฟล์:

  • Main.hsมีส่วนที่น่าสนใจ
  • Pacman.hsมีเพียงรหัสที่น่าเบื่อที่จัดการโปรโตคอล คุณสามารถใช้มันเพื่อเขียนโซลูชัน haskell ของคุณเอง ไม่มีรหัส AI ใด ๆ

โอ้รอเรามีMakefileเหมือนกัน

พวกเขามาที่นี่:

Main.hs

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

command.txt

./physicist

ฉันไม่สามารถเรียกใช้สิ่งนี้ได้ ฉันได้รับ "ชื่อไฟล์ไม่ตรงกับชื่อโมดูล: เห็นMain' Expected Pacman '" เมื่อฉันพยายามทำให้ นอกจากนี้ในการเรียกใช้ฉันต้องทำมันหรือมีคำสั่งอื่นที่ฉันจำเป็นต้องเรียกใช้หรือไม่
นาธานเมอร์ริล

@NathanMerrill คุณควรสร้างมันขึ้นมาก่อนจากนั้นจึงรันphysicistไฟล์ปฏิบัติการ แก้ไขและเพิ่มcommand.txtตอนนี้
เรย์

ฉันทำมัน ข้อผิดพลาดที่ฉันระบุไว้จะถูกโยนทิ้งเมื่อฉันสร้างมันขึ้นมา นอกจากนี้สมมติว่าคุณอยู่ในไดเรกทอรีนักฟิสิกส์ มันจะไม่เป็น ghc หรือไม่ใน command.txt?
นาธานเมอร์ริล

@NathanMerrill มันแปลกมาก อาจเป็นเพราะพฤติกรรมที่แตกต่างของ GHC บน Windows การเปลี่ยนชื่อphysicist.hsเป็นMain.hsอาจใช้งานได้ ฉันได้อัพเดตคำตอบแล้ว
เรย์

@NathanMerrill คุณรวมสองไฟล์นี้หรือไม่ นั่นไม่ได้ผล
เรย์

3

dumbpac

แพ็กนี้จะเคลื่อนที่แบบสุ่มโดยไม่คำนึงถึงเลย์เอาต์เขาวงกตหรือผีหรือปัจจัยอื่นใด

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");
}

งูหลาม:

#!/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

randomwalk

แพคนี้เดินสุ่ม แต่ไม่เข้าไปในกำแพง

#!/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

Pathy, Python 3

บอทนี้ใช้เส้นทางการค้นหามาก เมื่อกำหนดตำแหน่งเริ่มต้นและเงื่อนไขสิ้นสุดให้ใช้ BFS อย่างง่ายเพื่อค้นหาเส้นทางที่สั้นที่สุด การค้นหาเส้นทางใช้ใน:

  • ค้นหาเม็ดพลังงานผลไม้หรือเม็ด
  • ถ้าอยู่ยงคงกระพันไล่ล่าผี
  • หากเป็นผีให้ไล่ล่า Pac-Men
  • หนีจากผี
  • คำนวณระยะทางระหว่างตำแหน่งที่กำหนด

command.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(' ')]พ่น aValueError: invalid literal for int() with base 10: '8o'
Nathan Merrill

คอนโทรลเลอร์ส่งอะไร มันล้มเหลวทุกครั้งหรือไม่ มันใช้งานได้ที่นี่และฉันคิดว่าข้อความนี้ควรจะทำงานได้ดี
เรย์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.