Программирование Pac-Man

31

Programmin '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

Лабиринт

Если есть п Pac-Men, то лабиринт размера sqrt(n)*10по sqrt(n)*10будет генерироваться с помощью алгоритма Примы (из - за его низкий коэффициент реки), затем скручивается полностью, отдавая предпочтение уже существующих тупиков. Кроме того, это плетение может быть сделано по краям, так что есть несколько путей сверху вниз и слева направо.

Там будет:

  1. 2n привидения
  2. 4n Power Pellets
  3. 2n Фрукты
  4. n Pac-Men в местах, где соседние квадраты пустые.
  5. Все оставшиеся пустые места будут заполнены гранулами

Следовательно, исходная карта с 10 игроками будет выглядеть примерно так (Ghosts = зеленый, Pellets = аква, фрукты = красный, Pac-Man = желтый):

Лабиринт

Ввод, вывод

В начале игры вам будет предоставлена ​​одна строка символов, представляющая стены в каждом квадрате карты. Для каждого квадрата, начиная с верхнего левого угла, двигаясь вправо и заканчивая следующей строкой, вам будет дана шестнадцатеричная цифра, обозначающая ситуацию на стене:

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: 1 или больше Pac-Man
  2. G: 1 или более призраков
  3. o: Пеллет
  4. O: Мощность пеллет
  5. F: Фрукт
  6. X: Ничего такого

Если на квадрате есть призрак и что-то еще, Gбудет возвращено.

Следовательно, если вы были на квадрате 23,70, вы просто двинулись на север, квадрат над вами - тупик и содержит шарик силы, и у вас есть стены по обе стороны от вас, ваш вход будет:

23,70X 22,70O

На вашем текущем квадрате будет показано, Gесли вы Призрак, a, Pесли на вашем квадрате есть другой Pac-Man, в противном случаеX

Затем вы вернете следующие товары через STDOUT:

Отдельный символ, обозначающий направление ( North, East, South, West или XStay).

Перед передачей в направлении, вы также можете передать в любой координате как x,y, и стены этого квадрата будут переданы обратно (как описано выше)

Программа должна непрерывно работать, пока не Qбудет передана через STDIN. Программы будут перезапущены для каждой игры.

Доступ к другой информации за пределами того, что передается в STDIN (включая другие данные Pac-Men или данные, хранящиеся в хост-программе), не разрешен.

Невозможность вернуть ход в течение 1000 мс завершит программу (работает на моей довольно приличной машине с Win8). Вам дадут 2 секунды, чтобы обработать начальный макет лабиринта, когда он

Хост будет написан на Python, и скоро появится код для тестирования вашего бота.

Исключительные случаи

  • Если несколько Pac-Men окажутся в одном месте, ни один из них не получит содержимое текущего квадрата, если только один из них не является непобедимым, и в этом случае непобедимый Pac-Man получит шарик.
  • Pac-Man, съеденный Призраком, не будет телепортироваться куда-то еще. Если двое Pac-Men находятся на квадрате, а один непобедим, призрак будет телепортирован.
  • Будучи телепортированным как Призрак, вы не можете двигаться на 1 ход. Играя за Призрака, вы просто пропустите свой ход
  • Попытка пройти сквозь стену будет интерпретирована как «Пребывание»
  • Каждый из первоначальных призраков получит одну из 4 черт личности, как описано здесь , со следующей модификацией:

    1. Описанные ошибки не будут продублированы
    2. Все они будут активны с самого начала
    3. Они уязвимы только для игрока, который съел шарик
    4. Они будут бесконечно переключаться с разброса на погони, каждый из которых будет иметь фиксированное число оборотов до переключения
    5. После переключения в погоню, они найдут ближайшего Pac-Man, чтобы преследовать, и будут преследовать этого Pac-Man в течение всего времени их погони. (Если есть связь для близости, Pac-Man будет выбран псевдослучайно)
    6. Блинки не будет ускоряться
    7. Инки выберет ближайшего призрака, чтобы основать свои расчеты после переключения на погоню.
    8. Клайд найдет всех игроков на расстоянии 8 клеток, а затем последует за самым дальним игроком.
    9. Все призраки, кроме Клайда, не будут нацеливаться на игрока дальше, чем на 5 клеток

Я приму скомпилированный код из стандартного языка или .exe (с сопровождающим кодом).

Советы по программированию

Вы можете с моим контроллером. Вам нужно поместить папку / bots / your_bot_name / в тот же каталог, что и программа. В этой папке вам нужно добавить command.txt, содержащий команду для выполнения вашей программы (например:) python my_bot.py, и вашего бота.

Код контроллера находится на Github (код Python, если вам нужна графика, требуется Pygame). Протестировано на Windows и Linux.

РЕКОРДЫ

Охотник за привидениями: 72 840 очков

путь: 54 570 баллов

близорукость: 50 820 баллов

избегать взаимодействия: 23 580 баллов

физик: 18,330 баллов

randomwalk: 7 760 очков

тупица: 4880 баллов

Натан Меррилл
источник
9
+1. Это первый раз, когда я вижу слово "Pacmen"
justhalf
5
Выглядит как забавный вызов! Кстати: (1) На самом деле их называют «активаторами», а не «силовыми гранулами». (2) Буква «М» в Pac-Man пишется с заглавной буквы и переносится как «Pac-Man», а не «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, если ваш бот не отвечает.
Натан Меррилл
1
Я дальтоник и не могу сказать PacMen от Призраков, мы могли бы изменить цвета?
Moop

Ответы:

8

GhostBuster - Python

Выбирает случайное место на карте, использует алгоритм A *, чтобы найти лучший путь вперед. Как только он достигнет пункта назначения, он выберет новый и продолжит. Он будет пытаться избежать призраков, но из-за ограниченного поля зрения он будет иногда сталкиваться с ними. Это позволит избежать прогулок по уже посещенным местам.

  • Добавлена ​​логика для призрака. Выбирает близкую (<8) случайную точку и перемещается туда, игнорируя оценки, отличные от pacmen
  • Добавлена ​​логика непобедимости
  • Скорректированные точечные значения квадратов
  • Ошибка (если он слишком хорош и ест все гранулы, игра почему-то зависает)

Использует код Спарра, спасибо за логику.


Windows 7, Visual Studios с инструментами Python. Должен работать на коробках 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
Moop
источник
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
Sparr
источник
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'
es1024
источник
Этот ответ выдает ошибку. Вы не определили x или y
Натан Меррилл
Файл "avoider.py", строка 42, в <module> maze [int (cell [1])] [int (cell [0])] [1] = cell [2] TypeError: объект 'int' не поддерживает назначение предмета
Натан Меррилл
valid_directions.remove ('W') ValueError: list.remove (x): x отсутствует в списке
Натан Меррилл
@NathanMerrill Должен быть исправлен сейчас.
es1024
4

Физик, Хаскелл

Физик Пак-Мэн считает, что закон всемирного тяготения Ньютона может помочь ему выиграть игру. Затем он просто применяет его ко всем другим объектам, которые он знает во время игры. Поскольку физик стар и имеет плохую память, он может помнить вещи только за 5 раундов. Оооочень, плохая память на самом деле помогает ему забивать лучше.

Этот ответ имеет два филе:

  • 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
луч
источник
Я не могу запустить это. Я получаю «Имя файла не соответствует имени модуля: Saw 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");
}

Python:

#!/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'])
Sparr
источник
3

случайная прогулка

этот пак ходит наугад, но не в стенах

#!/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)
Sparr
источник
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(' ')]бросаетValueError: invalid literal for int() with base 10: '8o'
Натан Меррилл
Что отправил контроллер? Это терпит неудачу каждый раз? Это работает здесь, и я думаю, что это утверждение должно работать хорошо.
Рэй