Memprogram Pac-Man

31

Programmin 'Pac-Man

Pengaturan

Anda bermain sebagai Pac-Man. Anda ingin mengumpulkan pelet, buah, dan pelet listrik sebelum orang lain, sambil menghindari hantu.

Aturan

  1. Setiap Pac-Man yang valid akan berada dalam satu labirin. Pemain dengan skor kumulatif tertinggi setelah 10 pertandingan akan menang.
  2. Sebuah permainan berakhir ketika semua Pac-Men mati, semua pelet hilang, atau 500 putaran telah berlalu
  3. Jika Pac-Man meninggal, ia terus bermain sebagai hantu
  4. Makan pelet Power akan membuat Anda tak terkalahkan selama 10 putaran, dan memungkinkan Anda untuk makan Hantu
  5. Makan Hantu akan memindahkan hantu ke lokasi acak
  6. Hantu tidak bisa makan apa pun kecuali Pac-Men, dan tidak mendapat poin
  7. Makan item berikut sebagai Pac-Man akan memberi Anda poin-poin berikut:
    1. Pelet: 10
    2. Power Pellet: 50
    3. Buah: 100
    4. Hantu: 200

Labirin

Jika ada n Pac-Men, maka labirin ukuran sqrt(n)*10oleh sqrt(n)*10akan dihasilkan menggunakan algoritma Prim (karena faktor sungai rendah), kemudian dikepang sepenuhnya, memberikan preferensi untuk jalan buntu yang sudah ada. Selanjutnya, jalinan ini dapat dilakukan melintasi tepian, sehingga ada beberapa jalur dari atas ke bawah dan dari kiri ke kanan.

Akan ada:

  1. 2n Hantu
  2. 4n Pelet Daya
  3. 2n Buah
  4. n Pac-Men di tempat di mana kotak tetangga yang terhubung kosong.
  5. Semua tempat kosong yang tersisa akan diisi dengan pelet

Karenanya, peta awal dengan 10 pemain akan terlihat seperti ini (Hantu = hijau, Pelet = aqua, buah = merah, Pac-Man = kuning):

Labirin

Input output

Di awal permainan , Anda akan diberikan satu baris karakter, yang mewakili dinding di setiap kotak peta. Untuk setiap kotak, mulai dengan kiri atas, bergerak ke kanan, dan membungkus ke baris berikutnya, Anda akan diberi digit heks yang mewakili situasi dinding:

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

Sederhananya, Utara = 1, Timur = 2, Selatan = 4, dan Barat = 8, ditambahkan bersamaan.

Kemudian, setiap belokan , Anda akan diberikan posisi Anda saat ini dan barang-barang di garis pandang Anda (jika Anda adalah Pac-Man. Semua hantu menerima semua kotak dari -5 hingga +5 dari posisi relatif mereka). Garis pandang Anda akan didasarkan pada arah perjalanan terakhir Anda. Jika Anda bepergian ke utara, Anda akan diberikan semua bujur sangkar langsung ke Utara, Timur, dan Barat sampai dinding memotong pandangan Anda ditambah satu bujur sangkar Northwest dan Northeast, jika tidak ada dinding yang memotong pandangan Anda. Jika Anda memilih untuk tidak bergerak, Anda akan diberikan kotak di semua 8 arah.

Untuk visual, Iberarti tidak terlihat, Vberarti terlihat, Pberarti Pac-Man (dengan asumsi Pac-Man menghadap ke utara):

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

Setiap kotak akan diberikan oleh koordinat, dan kemudian isinya. Konten itu diwakili oleh karakter berikut:

  1. P: 1 atau lebih Pac-Man
  2. G: 1 atau lebih Hantu
  3. o: Pelet
  4. O: Power pellet
  5. F: Sepotong Buah
  6. X: Tidak ada

Jika ada hantu dan sesuatu yang lain di lapangan, Gakan dikembalikan.

Oleh karena itu, jika Anda berada di bujur sangkar 23,70, Anda baru saja bergerak ke utara, alun-alun di atas Anda adalah jalan buntu dan berisi pelet Power, dan Anda memiliki dinding di kedua sisi Anda, input Anda adalah:

23,70X 22,70O

Di alun-alun Anda saat ini, maka akan tampil Gjika Anda Ghost, satu Pjika ada yang lain Pac-Man di alun-alun Anda, jika tidak sebuahX

Maka Anda akan mengembalikan barang-barang berikut melalui STDOUT:

Karakter tunggal mewakili arah ( Nort, East, South, West, atau XStay).

Sebelum melewati suatu arah, Anda juga dapat melewati koordinat apa pun sebagai x,y, dan dinding persegi itu akan dilewati kembali (seperti dijelaskan di atas)

Program harus terus berjalan sampai Qdilewatkan melalui STDIN. Program akan dimulai kembali untuk setiap game.

Mengakses informasi lain di luar apa yang diteruskan ke STDIN (termasuk data Pac-Men lainnya atau data yang dipegang oleh program host) tidak diizinkan.

Gagal mengembalikan gerakan dalam waktu 1000 ms akan menghentikan program (Menjalankan mesin Win8 saya yang cukup baik). Anda akan diberikan 2 detik untuk memproses tata letak labirin awal saat diberikan

Host akan ditulis dengan Python, dan kode untuk menguji bot Anda akan datang.

Kasus luar biasa

  • Jika beberapa Pac-Men berakhir di lokasi yang sama, keduanya tidak mendapatkan isi dari kuadrat saat ini, kecuali jika salah satu dari mereka tidak terkalahkan, dalam hal ini, Pac-Man yang tak terkalahkan akan menerima pelet.
  • Pac-Man yang dimakan oleh Ghost tidak akan diteleportasi ke tempat lain. Jika dua Pac-Men berada di bujur sangkar, dan satu tidak terkalahkan, hantu itu akan diteleportasi.
  • Teleportasi sebagai Hantu mencegah Anda bergerak untuk 1 giliran. Saat bermain sebagai Hantu, giliran Anda akan dilewati
  • Mencoba bergerak melalui dinding akan ditafsirkan sebagai "Tetap"
  • Setiap hantu awal akan menerima salah satu dari 4 ciri kepribadian, seperti yang dijelaskan di sini , dengan modifikasi berikut:

    1. Bug yang dijelaskan tidak akan diduplikasi
    2. Mereka semua akan aktif dari awal
    3. Mereka hanya rentan terhadap pemain yang memakan pelet
    4. Mereka akan secara tak terbatas beralih dari scatter ke chase, masing-masing memiliki jumlah putaran sebelum beralih
    5. Setelah beralih ke pengejaran, mereka akan menemukan Pac-Man terdekat untuk mengejar, dan akan mengejar Pac-Man itu selama durasi pengejaran mereka. (Jika ada dasi untuk kedekatan, Pac-Man akan dipilih secara acak)
    6. Blinky tidak akan mempercepat
    7. Inky akan memilih hantu terdekat untuk mendasarkan perhitungannya setelah beralih ke pengejaran.
    8. Clyde akan menemukan semua pemain 8 kotak jauhnya, kemudian ikuti pemain terjauh.
    9. Semua hantu kecuali Clyde tidak akan menargetkan pemain lebih jauh dari 5 kotak jauhnya

Saya akan menerima kode yang dapat dikompilasi dari bahasa standar atau .exe (dengan kode yang disertakan).

Tips Pemrograman

Anda mungkin dengan controller saya. Anda harus meletakkan folder / bots / your_bot_name / di direktori yang sama dengan program. Di dalam folder, Anda perlu menambahkan command.txt yang berisi perintah untuk menjalankan program Anda (mis:) python my_bot.py, dan bot Anda.

Kode pengontrol ada di Github (kode Python, membutuhkan Pygame jika Anda ingin grafik.) Diuji pada windows dan linux

SKOR

ghostbuster: 72.840 poin

pathy: 54.570 poin

picik: 50.820 poin

Hindari interaksi: 23.580 poin

fisikawan: 18.330 poin

jalan acak: 7.760 poin

dumbpac: 4.880 poin

Nathan Merrill
sumber
9
+1. Ini adalah pertama kalinya saya melihat kata "Pacmen"
justhalf
5
Sepertinya tantangan yang menyenangkan! Ngomong-ngomong: (1) Mereka sebenarnya disebut "energizers" dan bukan "power pellet." (2) "M" dalam Pac-Man dikapitalisasi, dan itu ditulis dgn tanda penghubung sebagai "Pac-Man" dan bukan "Pacman" atau "PacMan". Berikut adalah sumber yang bagus untuk informasi Pac-Man: home.comcast.net/~jpittman2/pacman/pacmandossier.html
Todd Lehman
2
Siapa pun yang mengerjakan tantangan ini harus bergabung dengan kami di ruang obrolan untuk codegolf. chat.stackexchange.com/rooms/240/the-nineteenth-byte
Sparr
1
Baik. Kontroler sekarang berfungsi di windows dan linux, tetapi akan membeku di windows jika bot Anda tidak merespons.
Nathan Merrill
1
Saya buta warna dan tidak bisa membedakan PacMen dari para Hantu, bisakah kita mengubah warnanya?
Moop

Jawaban:

8

GhostBuster - Python

Pilihan tempat acak di peta, menggunakan algoritma A * untuk menemukan jalur terbaik ke depan. Setelah mencapai tujuannya, ia akan memilih yang baru dan melanjutkan. Ini akan mencoba untuk menghindari hantu, tetapi dengan FOV terbatas, itu kadang-kadang akan menabrak mereka. Ini akan menghindari berjalan di tempat yang sudah dikunjungi.

  • Logika ditambahkan untuk hantu. Pilihan dekat (<8) titik acak dan bergerak di sana, mengabaikan skor selain pacmen
  • Menambahkan logika tak terkalahkan
  • Nilai titik kuadrat yang disesuaikan
  • Bug (jika dia terlalu bagus dan makan semua pelet, permainan membeku karena alasan tertentu)

Menggunakan beberapa kode Sparr, terima kasih atas logikanya.


Windows 7, Visual Studios dengan Python Tools. Harus bekerja di kotak 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
sumber
8

rabun dekat

Pac ini menghindari hantu yang berdekatan kecuali dia bisa memakannya, bergerak ke buah atau pelet yang berdekatan, dan berjalan secara acak sebagai pilihan terakhir.

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

penghindar

Hindari semua hantu sebagai pacman, dan semua pacmen saat hantu. Cobalah untuk menghindari jenisnya sendiri jika memungkinkan, dan kemudian hindari memutar 180 jika memungkinkan.

#!/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
sumber
Jawaban ini menimbulkan kesalahan. Anda belum menentukan x atau y
Nathan Merrill
File "avoider.py", baris 42, dalam <module> labirin [int (sel [1])] [int (sel [0])] [1] = sel [2] TypeError: objek 'int' tidak mendukung penugasan item
Nathan Merrill
valid_directions.remove ('W') ValueError: list.remove (x): x tidak ada dalam daftar
Nathan Merrill
@NathanMerrill Harus diperbaiki sekarang.
es1024
4

Fisikawan, Haskell

Fisikawan Pac-Man percaya bahwa hukum gravitasi universal Newton dapat membantunya memenangkan permainan. Lalu dia hanya menerapkannya ke semua objek lain yang dia tahu selama pertandingan. Karena fisikawan sudah tua dan memiliki ingatan yang buruk, ia hanya dapat mengingat hal-hal dalam 5 putaran. Hooooly, memori buruk sebenarnya membantunya untuk mencetak skor yang lebih baik.

Jawaban ini memiliki dua fille:

  • Main.hs, mengandung bagian yang menarik.
  • Pacman.hs, hanya beberapa kode yang membosankan menangani protokol. Anda dapat menggunakannya untuk menulis solusi haskell Anda sendiri. Itu tidak mengandung kode AI apa pun.

Oh, tunggu, kami juga punya Makefile.

Mereka datang:

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
sinar
sumber
Saya tidak dapat menjalankan ini. Saya mendapatkan "Nama file tidak cocok dengan nama modul: Saw Main' Expected Pacman '" ketika saya mencoba membuatnya. Juga, untuk menjalankannya, apakah saya hanya perlu membuatnya, atau adakah perintah lain yang perlu saya jalankan?
Nathan Merrill
@NathanMerrill Anda harus membuatnya terlebih dahulu kemudian menjalankan physicistexecutable. Diedit dan ditambahkan command.txt, sekarang.
Ray
Saya membuatnya. Kesalahan yang saya daftarkan terlempar ketika saya membuatnya. Juga asumsikan Anda berada di direktori fisikawan. Bukankah itu fisikawan ghc di command.txt?
Nathan Merrill
@NathanMerrill Itu aneh. Mungkin karena perilaku GHC yang berbeda pada Windows. Mengganti nama physicist.hsmenjadi Main.hsdapat bekerja. Saya sudah memperbarui jawabannya.
Ray
@NathanMerrill Apakah Anda menggabungkan dua file ini? Itu tidak akan berhasil.
Ray
3

dumbpac

Pac ini hanya bergerak secara acak, tanpa memperhatikan tata letak labirin atau hantu atau faktor lainnya.

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
sumber
3

jalan acak

pac ini berjalan secara acak, tetapi tidak ke dinding

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

Pathy, Python 3

Bot ini menggunakan banyak jalan menemukan. Diberikan posisi awal dan kondisi akhir, ia menggunakan BFS sederhana untuk menemukan jalur terpendek. Temuan jalur digunakan di:

  • Temukan pelet listrik, buah-buahan atau pelet.
  • Jika tidak terkalahkan, kejar hantu
  • Jika itu hantu, kejar Pac-Men
  • Larilah dari hantu
  • Hitung jarak antara sepasang posisi yang diberikan.

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)
sinar
sumber
objects = [(tuple(map(int, x[:-1].split(',')))[::-1], x[-1]) for x in line.split(' ')]throws aValueError: invalid literal for int() with base 10: '8o'
Nathan Merrill
Apa yang dikirim pengontrol? Apakah itu gagal setiap saat? Ini berfungsi di sini dan saya pikir pernyataan ini harus bekerja dengan baik.
Ray