-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathcustom_action.py
159 lines (140 loc) · 6.19 KB
/
custom_action.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
custom_action.py
standard_action.py reimplemented as an import from a clashroyalebuildabot install
"""
from clashroyalebuildabot.bot import Action
class CustomAction(Action):
score = None
@staticmethod
def _distance(x1, y1, x2, y2):
return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
def _calculate_spell_score(self, units, radius, min_to_hit):
"""
Calculate the score for a spell card (either fireball or arrows)
The score is defined as [A, B, C]
A is 1 if we'll hit `min_to_hit` or more units, 0 otherwise
B is the number of units we hit
C is the negative distance to the furthest unit
"""
score = [0, 0, 0]
for k, v in units['enemy'].items():
for unit in v['positions']:
tile_x, tile_y = unit['tile_xy']
# Assume the unit will move down a few spaces
tile_y -= 2
# Add 1 to the score if the spell will hit the unit
distance = self._distance(tile_x, tile_y, self.tile_x, self.tile_y)
if distance <= radius - 1:
score[1] += 1
score[2] = min(score[2], -distance)
# Set score[0] to 1 if we think we'll hit enough units
if score[1] >= min_to_hit:
score[0] = 1
return score
def _calculate_knight_score(self, state):
"""
Only play the knight if a ground troop is on our side of the battlefield
Play the knight in the center, vertically aligned with the troop
"""
score = [0] if state['numbers']['elixir']['number'] != 10 else [0.5]
for k, v in state['units']['enemy'].items():
for unit in v['positions']:
tile_x, tile_y = unit['tile_xy']
if self.tile_y < tile_y <= 14 and v['transport'] == 'ground':
if tile_x > 8 and self.tile_x == 9 or tile_x <= 8 and self.tile_x == 8:
score = [1, self.tile_y - tile_y]
return score
def _calculate_minions_score(self, state):
"""
Only play minions on top of enemy units
"""
score = [0] if state['numbers']['elixir']['number'] != 10 else [0.5]
for k, v in state['units']['enemy'].items():
for unit in v['positions']:
tile_x, tile_y = unit['tile_xy']
distance = self._distance(tile_x, tile_y, self.tile_x, self.tile_y)
if distance < 1:
score = [1, -distance]
return score
def _calculate_fireball_score(self, state):
"""
Only play fireball if at least 3 units will be hit
Try to hit as many units as possible
"""
return self._calculate_spell_score(state['units'], radius=2.5, min_to_hit=3)
def _calculate_arrows_score(self, state):
"""
Only play arrows if at least 5 units will be hit
Try to hit as many units as possible
"""
return self._calculate_spell_score(state['units'], radius=4, min_to_hit=5)
def _calculate_archers_score(self, state):
"""
Only play the archers if there is a troop on our side of the battlefield
Play the archers in the center, vertically aligned with the troop
"""
score = [0] if state['numbers']['elixir']['number'] != 10 else [0.5]
for k, v in state['units']['enemy'].items():
for unit in v['positions']:
tile_x, tile_y = unit['tile_xy']
if self.tile_y < tile_y <= 14:
if tile_x > 8 and self.tile_x == 10 or tile_x <= 8 and self.tile_x == 7:
score = [1, self.tile_y - tile_y]
return score
def _calculate_giant_score(self, state):
"""
Only place the giant when at 10 elixir
Place it as high up as possible
Try to target the lowest hp tower
"""
score = [0]
left_hp, right_hp = [state['numbers'][f'{direction}_enemy_princess_hp']['number']
for direction in ['left', 'right']]
if state['numbers']['elixir']['number'] == 10:
if self.tile_x == 3:
score = [1, self.tile_y, left_hp != -1, left_hp <= right_hp]
elif self.tile_x == 14:
score = [1, self.tile_y, right_hp != -1, right_hp <= left_hp]
return score
def _calculate_minipekka_score(self, state):
"""
Place minipekka on the bridge as high up as possible
Try to target the lowest hp tower
"""
left_hp, right_hp = [state['numbers'][f'{direction}_enemy_princess_hp']['number']
for direction in ['left', 'right']]
score = [0]
if self.tile_x == 3:
score = [1, self.tile_y, left_hp != -1, left_hp <= right_hp]
elif self.tile_x == 14:
score = [1, self.tile_y, right_hp != -1, right_hp <= left_hp]
return score
def _calculate_musketeer_score(self, state):
"""
Place musketeer at 5-6 tiles away from enemies
That should be just within her range
"""
score = [0]
for k, v in state['units']['enemy'].items():
for unit in v['positions']:
tile_x, tile_y = unit['tile_xy']
distance = self._distance(tile_x, tile_y, self.tile_x, self.tile_y)
if 5 < distance < 6:
score = [1]
elif distance < 5:
score = [0]
return score
def calculate_score(self, state):
name_to_score = {'knight': self._calculate_knight_score,
'minions': self._calculate_minions_score,
'fireball': self._calculate_fireball_score,
'giant': self._calculate_giant_score,
'minipekka': self._calculate_minipekka_score,
'musketeer': self._calculate_musketeer_score,
'arrows': self._calculate_arrows_score,
'archers': self._calculate_archers_score
}
score_function = name_to_score[self.name]
score = score_function(state)
self.score = score
return score