パックマンのプログラミング


31

Programmin 'Pac-Man

セッティング

あなたはパックマンとしてプレイします。ゴーストを避けながら、ペレット、フルーツ、パワーペレットを他の誰よりも早く収集したいと考えています。

ルール

  1. すべての有効なパックマンは1つの迷路になります。10ゲーム後に累積スコアが最も高いプレイヤーが勝ちます。
  2. すべてのパックメンが死んだとき、すべてのペレットがなくなったとき、または500ターンが経過したときにゲームオーバー
  3. パックマンが死んだ場合、彼はゴーストとしてプレイし続けます
  4. パワーペレットを食べると10ターン無敵になり、ゴーストを食べることができます
  5. ゴーストを食べると、ゴーストがランダムな場所にテレポートされます
  6. 幽霊はパックメン以外は何も食べられず、ポイントも獲得できません
  7. パックマンとして次のアイテムを食べると、次のポイントが得られます。
    1. ペレット:10
    2. パワーペレット:50
    3. フルーツ:100
    4. ゴースト:200

迷路

Pac-Men がn個ある場合、Primのアルゴリズムを使用して(サイズの小さい迷路が原因で)サイズの迷路が生成さsqrt(n)*10れ、既存の行き止まりを優先して完全に編組されます。さらに、この編組は端を越えて行うことができるため、上から下へ、および左から右へのいくつかの経路があります。sqrt(n)*10

あるだろう:

  1. 2n 幽霊
  2. 4n パワーペレット
  3. 2n フルーツ
  4. n 接続された隣人の正方形が空のスポットのパックメン。
  5. 残りのすべての空のスポットはペレットで満たされます

したがって、10人のプレイヤーがいる最初のマップは次のようになります(ゴースト=緑、ペレット=アクア、フルーツ=赤、パックマン=黄色):

迷路

入出力

ゲーム開始時に、マップのすべての正方形の壁を表す1行のキャラクターが与えられます。左上から始まり、右に移動し、次の行に折り返す各正方形について、壁の状況を表す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

簡単に言えば、北= 1、東= 2、南= 4、西= 8を足し合わせます。

その後、各ターン、現在位置と視線内のアイテムが与えられます(パックマンの場合、すべてのゴーストは相対位置から-5から+5までのすべての正方形を受け取ります)。あなたの視線は、あなたが最後に進んだ方向に基づいています。北に移動した場合、壁が視界を遮るまで、視界を遮る壁がなければ北西と北東の1つの正方形に加えて、すべての正方形が直接北、東、西に与えられます。移動しないことを選択した場合、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返されます。

したがって、正方形23,70にいた場合、北に移動しただけで、上の正方形は行き止まりであり、パワーペレットが含まれ、両側に壁があり、入力は次のようになります。

23,70X 22,70O

あなたの現在の広場でGは、あなたが幽霊であるPかどうか、あなたの広場に別のパックマンがあるかどうか、それ以外の場合はX

次に、STDOUT経由で次のアイテムを返します。

方向(North、East、outh SWest、またはXStay)を表す単一の文字。

ある方向に渡す前に、として任意の座標を渡すこともx,yでき、その正方形の壁は戻されます(上記のように)

プログラムはQ、STDINを介して渡されるまで継続的に実行する必要があります。プログラムは各ゲームで再起動されます。

STDINに渡されるもの以外のその他の情報(他のPac-Menのデータまたはホストプログラムが保持するデータを含む)にアクセスすることは許可されていません。

1000ミリ秒以内に移動を返さないと、プログラムが終了します(かなりまともなWin8マシンで実行されます)。最初の迷路レイアウトが与えられたときに、処理するために2秒が与えられます

ホストはPythonで記述され、ボットをテストするためのコードが間もなくリリースされます。

例外的なケース

  • 複数のパックメンが同じ場所にいる場合、正確に1つが無敵でない限り、現在の正方形の内容を取得しません。その場合、無敵のパックマンがペレットを受け取ります。
  • 幽霊に食べられたパックマンは他の場所にテレポートされません。2人のパックメンが正方形にいて、1人が無敵の場合、ゴーストはテレポートされます。
  • ゴーストとしてテレポートされると、1ターンの間移動できなくなります。ゴーストとしてプレイするときは、単にあなたのターンがスキップされます
  • 壁を移動しようとすると、「滞在」と解釈されます
  • 初期ゴーストのそれぞれは、ここで説明するように、次の変更を加えた4つの性格特性のいずれかを受け取ります。

    1. 記載されているバグは複製されません
    2. 彼らはすべて最初からアクティブになります
    3. 彼らはペレットを食べたプレイヤーにのみ脆弱です
    4. 彼らは散布から追跡に無期限に切り替えます。それぞれは切り替え前に一定のターン数を持ちます
    5. 追跡に切り替えると、追跡に最も近いパックマンを見つけ、追跡中にそのパックマンを追跡します。(近さの引き分けがある場合、パックマンは擬似ランダムに選択されます)
    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ポイント

dumbpac:4,880ポイント


9
+1。「Pacmen」という言葉を目にするのはこれが初めてです。14
半分

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
OK。コントローラーはWindowsとLinuxで動作するようになりましたが、ボットが応答しない場合はWindowsでフリーズします。
ネイサンメリル14

1
私は色覚異常で、パックメンに幽霊から見分けることができません。色を変更できますか?
ムープ14

回答:


8

GhostBuster-Python

マップ上のランダムスポットを選択し、A *アルゴリズムを使用して前方への最適なパスを見つけます。宛先に到達すると、新しい宛先を選択して続行します。ゴーストを回避しようとしますが、視野が限られているため、時々ゴーストに遭遇します。すでに訪れた場所を歩くのを避けます。

  • ゴーストのロジックを追加しました。近い(<8)ランダムポイントを選択し、パックメン以外のスコアを無視してそこに移動します
  • 無敵のロジックを追加
  • 正方形の調整されたポイント値
  • バグ(彼があまりにも優秀で、すべてのペレットを食べると、何らかの理由でゲームがフリーズする)

Sparrのコードを使用します。ロジックをありがとう。


Windows 7、Pythonツールを備えたVisual Studio。Linuxボックスで動作するはずです。

#!/usr/bin/env python

import os
import re
import sys
import math
import random

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

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

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

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

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

class Node(object):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    turns += 1

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

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

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

    if turns == 1:
        print 'X'
        continue

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

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

        if invincibility > 0:
            invincibility -=  1

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

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

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

    bestPath = aStar(currentNode, destinationNode)

    nextNode = bestPath[0]

    direction = currentNode.direction(nextNode)

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

    previousNode = currentNode

    print direction

8

近視眼的

このパックは、彼がそれらを食べることができない限り、隣接する幽霊を避け、隣接する果物またはペレットに移動し、最後の手段としてランダムに歩きます。

#!/usr/bin/env python

import os
import re
import sys
import math
import random

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

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

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

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

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

turn = 0
invincibility_over = 0
last_move = None

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

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

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

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

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

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

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

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

    turn += 1

6

回避者

パックマンとしてのすべてのゴースト、およびゴーストのときのすべてのパックマンを避けてください。可能な場合、独自の種類のいずれかを回避しようとし、可能であれば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を定義していない
ネイサンメリル14

ファイル "avoider.py"、行42、<モジュール> maze [int(cell [1])] [int(cell [0])] [1] = cell [2] TypeError: 'int'オブジェクトはサポートしていませんアイテムの割り当て
ネイサンメリル14

valid_directions.remove( 'W')ValueError:list.remove(x):xリストにない
ネイサンメリル14

@NathanMerrill今すぐ修正する必要があります。
es1024 14

4

物理学者、ハスケル

物理学者のパックマンは、ニュートンの万有引力の法則がゲームに勝つのに役立つと信じています。その後、ゲーム中に知っている他のすべてのオブジェクトにそれを適用します。物理学者は古く、記憶力が悪いため、5ラウンドでしか覚えることができません。ひどく、悪い記憶は、実際に彼がより良い得点するのに役立ちます。

この答えには2つのフィレがあります:

  • 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

パックマン.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

これを実行できません。Main' Expected 作成しようとすると、「ファイル名がモジュール名と一致しません:Saw Pacman」と表示されます。また、それを実行するには、作成するだけですか、それとも実行する必要がある別のコマンドがありますか?
ネイサンメリル14

@NathanMerrill最初に作成してから、physicist実行可能ファイルを実行する必要があります。編集、追加しましたcommand.txt
レイ14

作っています。リストしたエラーは、作成時にスローされます。また、物理学者ディレクトリにいると仮定します。command.txtのghc物理学者ではないでしょうか?
ネイサンメリル14

@NathanMerrillそれは奇妙です。WindowsでのGHCの異なる動作が原因である可能性があります。名前physicist.hsを変更してMain.hsも機能する場合があります。答えを更新しました。
レイ14

@NathanMerrillこれら2つのファイルを組み合わせましたか?それはうまくいきません。
レイ14

3

ダンパック

このペースは、迷路のレイアウトやゴースト、その他の要因に関係なく、ランダムに移動します。

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'])

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)

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(' ')]スローaValueError: invalid literal for int() with base 10: '8o'
ネイサンメリル

コントローラーは何を送信しましたか?毎回失敗しますか?ここで機能し、このステートメントはうまく機能するはずです。
レイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.