-
-
Save segfault87/12f4e29fe41a676d6e8087dce53cf4f5 to your computer and use it in GitHub Desktop.
xenosoz.py
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
#!/usr/bin/env python3 | |
import random | |
import json | |
import sys | |
import math | |
# 도도 파이터에 참가하기 위해서는 에이전트를 만들어서 제출해 주셔야 합니다. | |
# 에이전트는 사용자가 작성하는 인공지능 코드로서, 주어지는 현재 게임 상태를 바탕으로 | |
# 어떤 액션을 취할지를 결정하는 역할을 합니다. | |
# | |
# 액션 설명 | |
# - 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초를 넘어가면 탈락 처리됩니다. | |
COUNTER_DEGREE = range(1) | |
MINDSIZE = range(1, 8) | |
def random_sample_by_prob(probmap): | |
'''sample by softmax''' | |
z = sum(map(math.exp, probmap.values())) | |
seed = random.random() | |
agg = 0 | |
for key, prob in probmap.items(): | |
agg += math.exp(prob) | |
if seed * z < agg: | |
return key | |
#print('using fallback', file=sys.stderr) | |
return random.choice(list(probmap.keys())) # XXX: fallback | |
class defaultdict: | |
'''quick and dirty impl of defaultdict''' | |
def __init__(self, constructor): | |
self.constructor = constructor | |
self.data = dict() | |
def __getitem__(self, key): | |
if key not in self.data: | |
self.data[key] = self.constructor() | |
return self.data.__getitem__(key) | |
def __setitem__(self, *args, **kw): return self.data.__setitem__(*args, **kw) | |
def __iter__(self, *args, **kw): return self.data.__iter__(*args, **kw) | |
def keys(self, *args, **kw): return self.data.keys(*args, **kw) | |
def values(self, *args, **kw): return self.data.values(*args, **kw) | |
def items(self, *args, **kw): return self.data.items(*args, **kw) | |
class Counter: | |
'''quick and dirty impl of defaultdict''' | |
def __init__(self, iter=None): | |
self.data = dict() | |
if iter: | |
self.update(iter) | |
def __getitem__(self, key): | |
if key not in self.data: | |
return 0 | |
return self.data.__getitem__(key) | |
def update(self, iter): | |
for x in iter: | |
self[x] += 1 | |
def __setitem__(self, *args, **kw): return self.data.__setitem__(*args, **kw) | |
def __iter__(self, *args, **kw): return self.data.__iter__(*args, **kw) | |
def keys(self, *args, **kw): return self.data.keys(*args, **kw) | |
def values(self, *args, **kw): return self.data.values(*args, **kw) | |
def items(self, *args, **kw): return self.data.items(*args, **kw) | |
class State: | |
def __init__(self, minidist, minihealth, mee_action, you_action): | |
self.minidist = minidist | |
self.minihealth = minihealth | |
self.mee_action = mee_action | |
self.you_action = you_action | |
def flipped_side(self): | |
return State( | |
minidist=self.minidist, | |
minihealth=self.minihealth, | |
mee_action=self.you_action, | |
you_action=self.mee_action | |
) | |
def state_filters(): | |
yield lambda s: (s.you_action,) | |
yield lambda s: (s.mee_action,) | |
yield lambda s: (s.mee_action, s.you_action,) | |
yield lambda s: (s.minihealth,) | |
yield lambda s: (s.minihealth, s.you_action,) | |
yield lambda s: (s.minihealth, s.mee_action,) | |
yield lambda s: (s.minihealth, s.mee_action, s.you_action,) | |
yield lambda s: (s.minidist,) | |
yield lambda s: (s.minidist, s.you_action,) | |
yield lambda s: (s.minidist, s.mee_action,) | |
yield lambda s: (s.minidist, s.mee_action, s.you_action,) | |
yield lambda s: (s.minidist, s.minihealth,) | |
yield lambda s: (s.minidist, s.minihealth, s.you_action,) | |
yield lambda s: (s.minidist, s.minihealth, s.mee_action,) | |
yield lambda s: (s.minidist, s.minihealth, s.mee_action, s.you_action,) | |
class Agent: | |
def __init__(self): | |
self.history = [] | |
self.mee_last_action = None | |
self.prediction_scores = defaultdict(float) | |
self.prediction = None | |
self.mee_frequency = Counter() | |
self.you_frequency = Counter() | |
self.mee_you_frequency = Counter() | |
self.general_map_mee = defaultdict(Counter) | |
self.general_map_you = defaultdict(Counter) | |
self.actions = ('idle', 'forward', 'backward', 'punch', 'kick', 'crouch', 'jump', 'guard') | |
def send_action(self, what): | |
if what not in ('idle', 'forward', 'backward', 'punch', 'kick', | |
'crouch', 'jump', 'guard'): | |
raise ValueError(f'Unknown action type: {what}') | |
self.mee_last_action = what | |
sys.stdout.write(what + '\n') | |
sys.stdout.flush() | |
def read_status(self): | |
data = sys.stdin.readline() | |
while data: | |
yield json.loads(data) | |
data = sys.stdin.readline() | |
def random_action(self): | |
return random.choice(self.actions) | |
def uniform_prediction(self): | |
return Counter(self.actions) | |
def mee_prediction(self): | |
if self.mee_action: | |
return Counter([self.mee_action]) | |
return self.uniform_prediction() | |
def you_prediction(self): | |
if self.you_action: | |
return Counter([self.you_action]) | |
return self.uniform_prediction() | |
def general_predictions(self, general_map): | |
for flip_side in [False, True]: | |
for mindsize in MINDSIZE: | |
states = self.history[-mindsize:] | |
if flip_side: | |
states = [s.flipped_side() for s in states] | |
for flt in state_filters(): | |
ss = tuple(flt(s) for s in states) | |
yield general_map[ss] | |
def general_mee_predictions(self): | |
yield from self.general_predictions(self.general_map_mee) | |
def general_you_predictions(self): | |
yield from self.general_predictions(self.general_map_you) | |
def update_statistics(self): | |
# update history | |
state = State(minidist=self.minidist, | |
minihealth=self.minihealth, | |
mee_action=self.mee_action, | |
you_action=self.you_action) | |
self.history.append(state) | |
# update frequency table | |
self.mee_frequency.update([self.mee_action]) | |
self.you_frequency.update([self.you_action]) | |
mee_you_action = (self.mee_action, self.you_action) | |
self.mee_you_frequency.update([mee_you_action]) | |
# (state -> action) general map | |
for mindsize in range(1, 15): | |
states = self.history[-mindsize:] | |
for flt in state_filters(): | |
ss = tuple(flt(s) for s in states) | |
self.general_map_mee[ss].update(self.mee_action) | |
self.general_map_you[ss].update(self.you_action) | |
def level_1_predictions(self): | |
# prediction = (action -> count) | |
yield self.mee_prediction() | |
yield self.you_prediction() | |
yield self.mee_frequency | |
yield self.you_frequency | |
yield from self.general_mee_predictions() | |
yield from self.general_you_predictions() | |
def predictions(self): | |
'''level 2 predictions''' | |
yield self.uniform_prediction() | |
for level_1 in self.level_1_predictions(): | |
counter = level_1 | |
yield counter | |
for counter_degree in COUNTER_DEGREE: | |
counter = self.make_counter_for_prediction(counter) | |
yield counter | |
def update_prediction_scores(self): | |
predictions = list(self.predictions()) | |
for i, pred in enumerate(predictions): | |
if pred is None or sum(pred.values()) == 0: | |
continue | |
#pred = self.uniform_prediction() | |
z = sum(map(math.exp, pred.values())) | |
self.prediction_scores[i] += math.exp(pred[self.you_action]) / z | |
#self.prediction_scores[i] += (pred[self.you_action] == max(pred.values())) | |
def make_prediction(self): | |
predictions = list(self.predictions()) | |
agg = defaultdict(float) | |
eps = 0.001 | |
for i, pred in enumerate(predictions): | |
if i not in self.prediction_scores: | |
self.prediction_scores[i] = eps | |
best_score = max(self.prediction_scores.values()) | |
for i, pred in enumerate(predictions): | |
score = self.prediction_scores[i] | |
if score != best_score: | |
continue | |
if pred is None or sum(pred.values()) == 0: | |
pred = self.uniform_prediction() | |
z = sum(pred.values()) | |
for action, count in pred.items(): | |
agg[action] += (count / z) * score | |
return agg | |
def make_counter_for_prediction(self, prediction): | |
counter = defaultdict(float) | |
if self.distance >= 2: | |
counter['forward'] = sum(prediction.values()) | |
elif self.distance >= 1: | |
'''move forward if possible''' | |
risk = prediction['punch'] + prediction['kick'] | |
should_shake = (self.mee_health < self.you_health) * 0.2 | |
want_stable = 1. - should_shake | |
counter['punch'] = prediction['forward'] / 2 | |
counter['kick'] = prediction['forward'] / 2 | |
counter['idle'] = want_stable * risk | |
counter['forward'] = ( | |
prediction['idle'] + | |
prediction['backward'] + | |
prediction['crouch'] + | |
prediction['jump'] + | |
prediction['guard'] + | |
should_shake * risk | |
) | |
else: | |
counter['punch'] = ( | |
prediction['idle'] / 2 + | |
prediction['forward'] / 2 + | |
prediction['jump'] | |
) | |
counter['kick'] = ( | |
prediction['idle'] / 2 + | |
prediction['forward'] / 2 + | |
prediction['crouch'] | |
) | |
counter['forward'] = prediction['backward'] | |
counter['crouch'] = prediction['punch'] | |
counter['jump'] = prediction['kick'] | |
counter['guard'] = prediction['guard'] | |
return counter | |
def action_for_prediction(self, prediction): | |
counter = self.make_counter_for_prediction(prediction) | |
return random_sample_by_prob(counter) | |
def handle_status(self, status): | |
'''Heavy lifting for self.choose_action''' | |
self.mee_action = self.mee_last_action | |
self.you_action = str(status['opponent_action']).split('.')[-1] | |
self.mee_damage = status['taken_damage'] | |
self.you_damage = status['given_damage'] | |
if self.mee_action is not None and self.you_action is not None: | |
self.update_prediction_scores() | |
self.update_statistics() | |
self.match_records = status['match_records'] | |
self.distance = status['distance'] | |
self.minidist = min(self.distance, 2) | |
self.time_left = status['time_left'] | |
self.mee_health = status['health'] | |
self.you_health = status['opponent_health'] | |
self.minihealth = (self.mee_health > self.you_health) - (self.mee_health < self.you_health) | |
self.prediction = self.make_prediction() | |
def run(self): | |
for status in self.read_status(): | |
try: | |
self.handle_status(status) | |
action = self.action_for_prediction(self.prediction) | |
except Exception as e: | |
#raise e # debug | |
#print(e, file=sys.stderr) | |
action = self.random_action() | |
self.send_action(action) | |
agent = Agent() | |
agent.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment