Skip to content

Instantly share code, notes, and snippets.

@segfault87
Created October 18, 2018 04:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save segfault87/12f4e29fe41a676d6e8087dce53cf4f5 to your computer and use it in GitHub Desktop.
Save segfault87/12f4e29fe41a676d6e8087dce53cf4f5 to your computer and use it in GitHub Desktop.
xenosoz.py
#!/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