팩맨 프로그래밍


31

Programmin의 팩맨

환경

당신은 팩맨으로 플레이합니다. 유령을 피하면서 다른 사람보다 먼저 펠렛, 과일 및 파워 펠렛을 수집하려고합니다.

규칙

  1. 모든 유효한 팩맨은 하나의 미로에있을 것입니다. 10 번의 게임 후 누적 점수가 가장 높은 플레이어가 승리합니다.
  2. 모든 팩맨이 죽거나 펠렛이 모두 사라지거나 500 턴이 지나면 게임이 종료됩니다.
  3. 팩맨이 죽으면 계속 유령으로 플레이합니다
  4. 파워 펠렛을 먹으면 10 턴 동안 무적 상태가되어 유령을 먹을 수 있습니다
  5. 유령을 먹으면 유령이 무작위로 이동합니다
  6. 유령은 팩맨 이외의 음식을 먹을 수 없으며 점수도 얻지 못합니다
  7. 다음 항목을 Pac-Man으로 먹으면 다음과 같은 장점이 있습니다.
    1. 펠릿 : 10
    2. 힘 펠릿 : 50
    3. 과일 : 100
    4. 유령 : 200

미로

이 경우 N 팩맨 남자는, 다음 크기의 미로 sqrt(n)*10sqrt(n)*10사용하여 생성됩니다 프림의 알고리즘 (때문에 그것의 낮은 강 요인)를 선택한 다음, 기존의 막 다른 골목에 우선 순위를주고, 완전히 꼰. 또한,이 편조는 가장자리를 가로 질러 수행 될 수있어서, 위에서 아래로 그리고 왼쪽에서 오른쪽으로 몇 가지 경로가있다.

있을 것이다 :

  1. 2n 귀신
  2. 4n 파워 펠렛
  3. 2n 과일
  4. n 연결된 이웃 사각형이 비어있는 지점에서 팩맨.
  5. 남은 빈 곳은 모두 펠렛으로 채워집니다

따라서 10 명의 플레이어가있는 초기 맵은 다음과 같습니다 (Ghosts = green, Pellets = aqua, fruit = red, Pac-Man = yellow) :

미로

입출력

게임 시작 시, 맵의 모든 칸에 벽을 나타내는 한 줄의 캐릭터가 제공됩니다. 왼쪽 위부터 시작하여 오른쪽으로 이동하고 다음 줄로 줄 바꿈하는 각 사각형에 대해 벽 상황을 나타내는 16 진수가 제공됩니다.

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

간단히 말해서 North = 1, East = 2, South = 4 및 West = 8을 합치면됩니다.

그런 다음, 매 차례마다 , 당신은 당신의 현재 위치와 당신의 시야에있는 아이템들을받을 것입니다 (팩맨이라면 모든 유령들은 그들의 상대 위치로부터 -5에서 +5까지의 모든 사각형을받습니다). 당신의 시선은 당신이 지난 차례에 여행 한 방향에 기초 할 것입니다. 당신이 북쪽을 여행하는 경우, 당신은 당신의보기 떨어져 벽 삭감 될 때까지 모든 사각형 직접 북쪽, 동쪽, 그리고 서쪽을 받게됩니다 플러스 경우 어떤 벽 삭감보기 떨어져, 하나의 사각형 모양 노스 웨스트와 동북. 움직이지 않기로 선택하면 8 방향의 사각형이 나타납니다.

시각적으로 I는 보이지 않는 것을, 가시는 V것을 의미합니다 P.

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

각 정사각형은 좌표로 주어진 다음 내용입니다. 내용은 다음 문자로 표시됩니다.

  1. P: 1 명 이상의 팩맨
  2. G: 1 명 이상의 유령
  3. o펠렛
  4. O: 파워 펠릿
  5. F: 과일 조각
  6. X: 아무것도

귀신과 사각형에 다른 G것이 있으면 반환됩니다.

따라서, 당신이 square에 있었다면 23,70, 당신은 북쪽으로 갔고, 당신 위의 사각형은 막 다른 골목이고 파워 펠릿을 포함하고, 당신의 양쪽에 벽이 있습니다.

23,70X 22,70O

현재 광장에서, G당신이 유령 P이라면 , 광장에 다른 팩맨 이 있다면 , 그렇지 않으면X

그런 다음 STDOUT을 통해 다음 항목을 반환합니다.

방향 ( North, East, South, West 또는 XStay)을 나타내는 단일 문자 입니다.

방향을 전달하기 전에로 좌표를 전달할 수 있으며 x,y해당 정사각형의 벽이 다시 전달됩니다 (위 설명 참조).

QSTDIN을 통해 프로그램 이 전달 될 때까지 프로그램은 계속 실행 중이어야합니다 . 각 게임마다 프로그램이 다시 시작됩니다.

STDIN으로 전달 된 정보 이외의 다른 정보 (다른 Pac-Men의 데이터 또는 호스트 프로그램이 보유한 데이터 포함)는 액세스 할 수 없습니다.

1000ms 이내에 이동을 반환하지 않으면 프로그램이 종료됩니다 (정말 괜찮은 Win8 컴퓨터에서 실행 중). 초기 미로 레이아웃을 처리 할 때 2 초가 주어집니다.

호스트는 Python으로 작성되며 봇을 테스트하는 코드는 곧 나옵니다.

예외적 인 경우

  • 여러 Pac-Men이 같은 위치에있는 경우 정확히 1 개가 불가피한 경우를 제외하고는 현재 사각형의 내용을 얻지 마십시오.
  • 유령이 먹는 팩맨은 다른 곳으로 순간 이동되지 않습니다. 두 명의 팩맨이 정사각형에 있고 하나가 무적이라면 유령은 순간 이동됩니다.
  • 유령으로 순간 이동하면 1 턴 동안 움직일 수 없습니다. 유령으로 플레이 할 때는 턴을 건너 뛰기 만하면됩니다
  • 벽을 통과하려는 시도는 "숙박"으로 해석됩니다
  • 초기 귀신들 각각은 여기 에 설명 된대로 다음과 같이 수정 된 4 가지 성격 특성 중 하나를받습니다 :

    1. 설명 된 버그는 복제되지 않습니다
    2. 그들은 모두 처음부터 활성화됩니다
    3. 그들은 펠렛을 먹은 플레이어에게만 취약합니다.
    4. 그들은 스 캐터에서 체이스로 무기한으로 전환 할 것입니다.
    5. 추격으로 전환하면 가장 가까운 Pac-Man을 추격하고 추격 기간 동안 해당 Pac-Man을 추격합니다. (친밀한 관계가 있으면 팩맨은 의사 무작위로 선택됩니다)
    6. 깜박임이 빨라지지 않습니다
    7. Inky는 체이스로 전환 한 후 계산을 기반으로 가장 가까운 유령을 선택합니다.
    8. 클라이드는 8 칸 떨어진 모든 플레이어를 찾은 다음 가장 먼 플레이어를 따릅니다.
    9. 클라이드를 제외한 모든 유령은 5 칸 이상 떨어진 플레이어를 대상으로하지 않습니다.

표준 언어 또는 .exe (동봉 코드 포함)의 컴파일 가능한 코드를 수락합니다.

프로그래밍 팁

내 컨트롤러와 함께 할 수 있습니다. / bots / your_bot_name / 폴더를 프로그램과 같은 디렉토리에 두어야합니다. 폴더 내에 프로그램 (예 python my_bot.py:) 및 봇 을 실행하는 명령이 포함 된 command.txt를 추가해야합니다 .

컨트롤러 코드는 Github에 있습니다 (Python 코드, 그래픽을 원하면 Pygame이 필요합니다). Windows 및 Linux에서 테스트되었습니다.

점수

유령 버스터 : 72,840 포인트

한심한 : 54,570 포인트

근시 : 50,820 포인트

상호 작용을 피하십시오 : 23,580 포인트

물리학 자 : 18,330 포인트

랜덤 워크 : 7,760 포인트

덤팍 : 4,880 포인트


9
+1. "Pacmen"이라는 단어가 처음으로 나타납니다
Justhalf

5
재미있는 도전 인 것 같습니다! 그건 그렇고 : (1) 그들은 실제로 "파워 펠렛"이 아니라 "에너지 발생기"라고합니다. (2) Pac-Man의 "M"은 대문자로 표시되며 "Pacman"또는 "PacMan"이 아닌 "Pac-Man"으로 하이픈으로 표시됩니다. 여기 팩맨 정보에 대한 훌륭한 자원입니다 home.comcast.net/~jpittman2/pacman/pacmandossier.html
토드 리먼

2
이 과제를 수행하는 사람은 codegolf 대화방에서 우리와 함께해야합니다. chat.stackexchange.com/rooms/240/the-nineteenth-byte
Sparr

1
승인. 컨트롤러는 이제 Windows 및 Linux에서 작동하지만 봇이 응답하지 않으면 Windows에서 정지됩니다.
Nathan Merrill

1
나는 색맹이며 PacMen에게 유령에게 말할 수 없습니다. 색상을 바꿀 수 있습니까?
Moop

답변:


8

고스트 버스터-파이썬

지도에서 임의의 지점을 선택하고 A * 알고리즘을 사용하여 최상의 경로를 찾습니다. 목적지에 도달하면 새로운 목적지를 선택하고 계속 진행합니다. 유령을 피하려고 시도하지만 제한된 FOV를 사용하면 때때로 튀어 나옵니다. 이미 방문한 곳을 걷지 않아도됩니다.

  • 유령에 대한 논리가 추가되었습니다. pacmen 이외의 점수를 무시하고 가까운 (<8) 랜덤 포인트를 골라 그곳으로 이동
  • 무적 논리 추가
  • 조정 된 제곱 점 값
  • 버그 (그가 너무 좋고 펠렛을 모두 먹으면 어떤 이유로 게임이 멈춤)

Sparr의 코드를 사용합니다. 논리에 감사드립니다.


Windows 7, Python 도구가 포함 된 Visual Studio 리눅스 박스에서 작동합니다.

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

회 피자

모든 유령은 팩맨으로, 모든 팩맨은 유령으로 피하십시오. 가능하면 자신의 종류를 피하려고 시도하고 가능하면 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를 정의하지 않았습니다
Nathan Merrill

<module> maze [int (cell [1])] [int (cell [0])] [1] = cell [2]의 42 번 라인에있는 "avoider.py"파일 TypeError : 'int'개체는 지원하지 않습니다 아이템 할당
Nathan Merrill

valid_directions.remove ( 'W') ValueError : list.remove (x) : x는리스트에 없음
Nathan Merrill

@NathanMerrill 지금 수정해야합니다.
es1024

4

물리학 자, 하스켈

물리학 자 팩맨 (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

메이크 파일

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

command.txt

./physicist

나는 이것을 실행할 수 없습니다. 만들려고하면 "파일 이름이 모듈 이름과 일치하지 않습니다 : Saw Main' Expected Pacman '"이 표시됩니다. 또한, 그것을 실행하려면 그냥 만들어야합니까, 아니면 다른 명령을 실행해야합니까?
Nathan Merrill

@NathanMerrill 먼저 physicist실행 파일 을 실행해야 합니다. 을 (를) 수정하고 추가했습니다 command.txt.
Ray

만들고 있습니다. 내가 작성한 오류는 내가 만들 때 발생합니다. 또한 물리학 자 디렉토리에 있다고 가정합니다. command.txt에서 물리학자가 아닐까요?
Nathan Merrill

@NathanMerrill 이상합니다. Windows에서 GHC의 다른 동작으로 인한 것일 수 있습니다. 이름 바꾸기 physicist.hsMain.hs5 월 일. 답변을 업데이트했습니다.
Ray

@NathanMerrill이 두 파일을 결합 했습니까? 작동하지 않습니다.
Ray

3

덤팍

이 팩은 미로 레이아웃이나 유령 또는 다른 요소에 관계없이 무작위로 움직입니다.

펄 :

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

무작위 걷기

이 pac는 무작위로 걷지 만 벽에는 들어 가지 않습니다.

#!/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를 사용하여 가장 짧은 경로를 찾습니다. 경로 찾기는 다음에서 사용됩니다.

  • 파워 펠렛, 과일 또는 펠렛을 찾으십시오.
  • 무적이라면 유령을 쫓아 라
  • 유령이라면 팩맨을 쫓아 라
  • 귀신으로부터 도망 치다
  • 주어진 위치 쌍 사이의 거리를 계산합니다.

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(' ')]던지기ValueError: invalid literal for int() with base 10: '8o'
나단 메릴

컨트롤러는 무엇을 보냈습니까? 매번 실패합니까? 여기에서 작동 하며이 진술이 잘 작동한다고 생각합니다.
Ray
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.