Last active
August 19, 2018 10:58
-
-
Save segfault87/b9ccf17d682c5bcfa6a6b4bcae36856e to your computer and use it in GitHub Desktop.
도도 파이터(https://pycon-2018-dodo-fighter.spoqa.com) 에이전트 예제 코드
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import sys | |
# 도도 파이터에 참가하기 위해서는 에이전트를 만들어서 제출해 주셔야 합니다. | |
# 에이전트는 사용자가 작성하는 인공지능 코드로서, 주어지는 현재 게임 상태를 바탕으로 | |
# 어떤 액션을 취할지를 결정하는 역할을 합니다. | |
# | |
# 액션 설명 | |
# - idle - 아무것도 하지 않습니다. | |
# - forward - 앞으로 움직입니다. 상대가 바로 앞에 있을 경우 더 움직이지 않습니다. | |
# - backward - 뒤로 움직입니다. 처음 시작지점에서 세칸 이상 뒤로 갈 수 없습니다. | |
# - punch - 상단을 공격합니다. | |
# - kick - 하단을 공격합니다. | |
# - crouch - 상단 공격을 피합니다. | |
# - jump - 하단 공격을 피합니다. | |
# - guard - 공격을 방어합니다. 상하단 모두 방어할 수 있지만 약간의 데미지를 입습니다. | |
# | |
# 상태 설명 | |
# - distance - 상대방과 나와의 거리. 0일 경우에만 공격이 가능합니다. | |
# - time_left - 남은 시간 | |
# - health - 나의 체력 | |
# - opponent_health - 상대의 체력 | |
# - opponent_action - 지난 턴에서 상대의 액션 | |
# - given_damage - 직전 액션에서 내가 상대방에게 가한 데미지 | |
# - taken_damage - 직전 액션에서 상대방이 나에게 가한 데미지 | |
# - match_records - 지금까지의 경기 기록. 리스트 형식입니다. | |
# 예를 들어 [None, True, False]인 경우, 첫번째 경기는 무승부, | |
# 두번째 경기는 당신이, 세번째 경기는 상대방이 이겼다는 뜻입니다. | |
# | |
# 주의사항 | |
# - 같은 액션을 계속 반복하지 마세요. 공격력이 하락되는 페널티가 있습니다. | |
# - 상대의 공격을 회피하거나 막는데 성공하면 다음 공격에서 공격력 보너스가 있습니다. | |
# - 한 턴 내에서는 이동과 방어 동작이 공격 동작보다 우선합니다. | |
# 즉, P1이 공격을 하고 P2가 이동한다면 P2가 이동하는 액션을 우선 평가합니다. | |
# - 사용할 수 있는 모듈은 random, json, sys, math로 한정되어 있습니다. | |
# - 스크립트 실행 시간이 3초를 넘어가면 탈락 처리됩니다. | |
def action(what): | |
if what not in ('idle', 'forward', 'backward', 'punch', 'kick', | |
'crouch', 'jump', 'guard'): | |
raise ValueError(f'Unknown action type: {what}') | |
sys.stdout.write(what + '\n') | |
sys.stdout.flush() | |
def read_status(): | |
data = sys.stdin.readline() | |
while data: | |
yield json.loads(data) | |
data = sys.stdin.readline() | |
for status in read_status(): | |
distance = status['distance'] | |
time_left = status['time_left'] | |
health = status['health'] | |
opponent_health = status['opponent_health'] | |
opponent_action = status['opponent_action'] | |
given_damage = status['given_damage'] | |
taken_damage = status['taken_damage'] | |
match_records = status['match_records'] | |
# 아래 코드를 수정해 주세요! | |
# 주의할 점은, 한 루프 내에는 반드시 한 번의 action()만 호출되어야 합니다. | |
# 호출되지 않으면 스크립트가 강제종료되며, 여러번 호출되면 큐가 쌓여서 | |
# 의도하지 않은 결과가 나옵니다. | |
action('punch') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import argparse | |
import enum | |
import json | |
import random | |
import subprocess | |
import sys | |
import time | |
import typing | |
# 이 코드는 도도 파이터의 에이전트 코드를 로컬에서 평가하기 위한 스크립트입니다. | |
# 실제 도도 파이터의 데미지 계산 공식과는 차이가 있는 점을 유념해 주십시오. | |
# | |
# 사용 방법: python judge.py [1P] [2P] | |
# 이 때, 에이전트 스크립트는 쉘에서 직접 실행 가능한 상태여야 합니다. | |
# * 스크립트의 첫 줄에 `#!/usr/bin/env python3` 가 있어야 합니다. | |
# * 스크립트는 실행 권한이 있어야 합니다. | |
ROUND_TIME = 30 | |
HIT_POINT_RANGE = (8, 16) | |
HIT_POINT_GUARD_RANGE = (4, 8) | |
class Action(enum.Enum): | |
idle = 'idle' | |
forward = 'forward' | |
backward = 'backward' | |
punch = 'punch' | |
kick = 'kick' | |
crouch = 'crouch' | |
jump = 'jump' | |
guard = 'guard' | |
class Player: | |
def __init__(self, prochandle: subprocess.Popen, position: int): | |
self.prochandle = prochandle | |
self.health = 100 | |
self.last_action = None | |
self.last_inflicted_damage = 0 | |
self.position = position | |
def distance(self, opponent): | |
return abs(opponent.position - self.position) | |
def communicate(self, opponent, time_left: int) -> Action: | |
payload = { | |
'distance': self.distance(opponent), | |
'time_left': time_left, | |
'health': self.health, | |
'opponent_health': opponent.health, | |
'opponent_action': str(opponent.last_action), | |
'given_damage': self.last_inflicted_damage, | |
'taken_damage': opponent.last_inflicted_damage, | |
'match_records': [], | |
} | |
self.prochandle.stdin.write(json.dumps(payload).encode('utf-8')) | |
self.prochandle.stdin.write(b'\n') | |
self.prochandle.stdin.flush() | |
act = self.prochandle.stdout.readline().decode('utf-8').strip() | |
self.last_action = Action(act) | |
return self.last_action | |
def inflict_damage(self, opponent, damage: int): | |
opponent.health = max(0, opponent.health - damage) | |
self.last_inflicted_damage = damage | |
def __repr__(self) -> str: | |
return f'<Player position={self.position} health={self.health} action={self.last_action} damage={self.last_inflicted_damage}>' # noqa | |
def log(time: int, player: Player, action: Action, message: str): | |
print(f'{time} [{player.health}] {message}') | |
def fight_loop(p1, p2): | |
time_left = ROUND_TIME | |
while time_left >= 0: | |
try: | |
p1a = p1.communicate(p2, time_left) | |
except: | |
print('Invalid communication from p1. Assuming that p2 has won.') | |
raise | |
return p2 | |
try: | |
p2a = p2.communicate(p1, time_left) | |
except: | |
print('Invalid communication from p2. Assuming that p1 has won.') | |
raise | |
return p1 | |
p1.last_inflicted_damage = 0 | |
p2.last_inflicted_damage = 0 | |
p1_idle = True | |
p2_idle = True | |
if p1a is Action.forward: | |
if p2.position > p1.position: | |
p1.position += 1 | |
log(time_left, p1, p1a, 'P1은 앞으로 움직였다!') | |
else: | |
log(time_left, p1, p1a, 'P1은 앞으로 움직이려 했지만 더 갈 수 없다!') | |
p1_idle = False | |
elif p1a is Action.backward: | |
if p1.position >= -2: | |
p1.position -= 1 | |
log(time_left, p1, p1a, 'P1은 뒤로 움직였다!') | |
p1_idle = False | |
if p2a is Action.forward: | |
if p2.position > p1.position: | |
p2.position -= 1 | |
log(time_left, p2, p2a, 'P2는 앞으로 움직였다!') | |
else: | |
log(time_left, p2, p2a, 'P2는 앞으로 움직이려 했지만 더 갈 수 없다!') | |
p2_idle = False | |
elif p2a is Action.backward: | |
if p2.position <= 5: | |
p2.position += 1 | |
log(time_left, p2, p2a, 'P2는 뒤로 움직였다!') | |
p2_idle = False | |
if p1a is Action.punch: | |
if p1.distance(p2) > 0: | |
log(time_left, p1, p1a, 'P1의 펀치! 하지만 닿지 않는다!') | |
elif p2a is Action.crouch: | |
log(time_left, p2, p2a, 'P2는 숙였다!') | |
log(time_left, p1, p1a, 'P1의 펀치! 하지만 P2는 숙여서 피했다!') | |
elif p2a is Action.guard: | |
log(time_left, p2, p2a, 'P2는 가드를 올렸다!') | |
log(time_left, p1, p1a, 'P1의 펀치! 약간의 데미지를 주었다!') | |
p1.inflict_damage(p2, random.randrange(*HIT_POINT_GUARD_RANGE)) | |
else: | |
log(time_left, p1, p1a, 'P1의 펀치! 아프다!') | |
p1.inflict_damage(p2, random.randrange(*HIT_POINT_RANGE)) | |
p1_idle = False | |
elif p1a is Action.kick: | |
if p1.distance(p2) > 0: | |
log(time_left, p1, p1a, 'P1의 발차기! 하지만 닿지 않는다!') | |
elif p2a is Action.jump: | |
log(time_left, p2, p2a, 'P2의 점프!') | |
log(time_left, p1, p1a, 'P1의 발차기! 하지만 P2는 점프해서 피했다!') | |
elif p2a is Action.guard: | |
log(time_left, p2, p2a, 'P2는 가드를 올렸다!') | |
log(time_left, p1, p1a, 'P1의 발차기! 약간의 데미지를 주었다!') | |
p1.inflict_damage(p2, random.randrange(*HIT_POINT_GUARD_RANGE)) | |
else: | |
log(time_left, p1, p1a, 'P1의 발차기! 아프다!') | |
p1.inflict_damage(p2, random.randrange(*HIT_POINT_RANGE)) | |
p1_idle = False | |
if p2.health <= 0: | |
print('KO. P1 승리!') | |
return p1 | |
if p2a is Action.punch: | |
if p2.distance(p1) > 0: | |
log(time_left, p2, p2a, 'P2의 펀치! 하지만 닿지 않는다!') | |
elif p1a is Action.crouch: | |
log(time_left, p1, p1a, 'P1은 숙였다!') | |
log(time_left, p2, p2a, 'P2의 펀치! 하지만 P1은 숙여서 피했다!') | |
elif p1a is Action.guard: | |
log(time_left, p2, p2a, 'P1은 가드를 올렸다!') | |
log(time_left, p1, p1a, 'P2의 펀치! 약간의 데미지를 주었다!') | |
p2.inflict_damage(p1, random.randrange(*HIT_POINT_GUARD_RANGE)) | |
else: | |
log(time_left, p2, p2a, 'P1의 펀치! 아프다!') | |
p2.inflict_damage(p1, random.randrange(*HIT_POINT_RANGE)) | |
p2_idle = False | |
elif p2a is Action.kick: | |
if p2.distance(p1) > 0: | |
log(time_left, p2, p2a, 'P2의 발차기! 하지만 닿지 않는다!') | |
elif p1a is Action.jump: | |
log(time_left, p1, p1a, 'P1의 점프!') | |
log(time_left, p2, p2a, 'P2의 발차기! 하지만 P1은 점프해서 피했다!') | |
elif p1a is Action.guard: | |
log(time_left, p1, p1a, 'P1는 가드를 올렸다!') | |
log(time_left, p2, p2a, 'P2의 발차기! 약간의 데미지를 주었다!') | |
p2.inflict_damage(p1, random.randrange(*HIT_POINT_GUARD_RANGE)) | |
else: | |
log(time_left, p2, p2a, 'P2의 발차기! 아프다!') | |
p2.inflict_damage(p1, random.randrange(*HIT_POINT_RANGE)) | |
p2_idle = False | |
if p1.health <= 0: | |
print('KO. P2 승리!') | |
return p2 | |
if p1_idle: | |
log(time_left, p1, p1a, '아무 일도 없었다.') | |
if p2_idle: | |
log(time_left, p2, p2a, '아무 일도 없었다.') | |
time_left -= 1 | |
if p1.health == p2.health: | |
print('무승부!') | |
return None | |
elif p1.health > p2.health: | |
print('타임 오버. P1 승리!') | |
return p1 | |
else: | |
print('타임 오버. P2 승리!') | |
return p2 | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('p1', type=str) | |
parser.add_argument('p2', type=str) | |
args = parser.parse_args() | |
p1 = subprocess.Popen([args.p1], | |
stdout=subprocess.PIPE, | |
stdin=subprocess.PIPE) | |
p2 = subprocess.Popen([args.p2], | |
stdout=subprocess.PIPE, | |
stdin=subprocess.PIPE) | |
try: | |
winner = fight_loop(Player(p1, 0), Player(p2, 3)) | |
except KeyboardInterrupt: | |
p1.terminate() | |
p2.terminate() | |
if __name__ == '__main__': | |
main() |
fight_loop
에는 관련 로직이 없어 보이는데 혹시 공격력 보너스에 관련된 룰을 알 수 있을까요?
정확히 공개하면 제출물들의 성향이 optimize되는 경향이 있을 것 같아서 해당 부분은 제외했습니다. 양해 부탁드립니다.
소스 제출을 해보니 제출시간이 UTC시간으로 나오는데 코드 제출기한하고는 상관이 없나요?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
fight_loop
에는 관련 로직이 없어 보이는데 혹시 공격력 보너스에 관련된 룰을 알 수 있을까요?