-
Notifications
You must be signed in to change notification settings - Fork 0
/
unit.py
147 lines (114 loc) · 5.14 KB
/
unit.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
from collections import deque
from mock_object import PathObject
from path_finding import PathFinder
from movement_cost import diagonal_cost, linear_cost
from moves import adjacent_linear, bc19_9_radius, adjacent_octile
from potential_func import inert_repel
# TODO: Create subclasses
class Unit:
"""
Unit represents a movable entity with methods to calculate path,
potential, visible units etc. For the unit single source of truth
for the game world is game_map. Keeping a reference to the game_map
object eliminates a lot of message passing about game state. Game_map
object also makes it easy to use potential field based movement that
depends on the game state. mock attribute can be changed, however changes to it will not be persistent
over the turns as other units can empty and overwrite it.
Note: However the unit must not change static and active attributes of the game_map.
"""
def __init__(self, cur_pos, name, poten_func, next_moves, move_cost_func, sight_range):
self.name = name
self.cur_pos = cur_pos
self.poten_func = poten_func
self.next_moves = next_moves
self.move_cost_func = move_cost_func
self.sight_range = sight_range
self.game_map = None
self.path = None
self.dest = None
self.path_finder = None
def set_game_state(self, game_map):
self.game_map = game_map
self.path_finder = PathFinder(self.move_cost_func, self.move_cost_func, self.game_map.is_valid_point)
def find_path(self, dest):
self.dest = dest
self.path = deque(self.path_finder.find_path(self.next_moves, self.cur_pos, dest))
def update_pos(self):
if self.path:
self.cur_pos = self.path.popleft()
def copy(self, cur_pos):
if not cur_pos:
cur_pos = self.cur_pos
return Unit(cur_pos, self.name, self.poten_func, self.next_moves, self.move_cost_func, self.sight_range)
def next_pos(self):
return [self.cur_pos + move for move in self.next_moves]
def cur_sight(self):
return [self.cur_pos + move for move in self.sight_range]
def visible_from(self, other):
return self.cur_pos in other.cur_sight()
def can_see_point(self, other_point):
return other_point in self.cur_sight()
def can_see(self, other):
return other.cur_pos in self.cur_sight()
def poten_at(self, point):
return self.poten_func(self.cur_pos, point)
def __str__(self):
return "{}: {}".format(self.name, self.cur_pos)
def __repr__(self):
return "{}: {}".format(self.name, self.cur_pos)
class FormationUnit(Unit):
MAX_PREDICT = 5
def __init__(self, *args):
super().__init__(*args)
self.is_leader = None
self.formation = None
self.leader_path = None
self.path = None
self.game_map = None
self.at_objective = False # Temporary measure should be replace by concept of unit state
def copy(self, cur_pos):
if not cur_pos:
cur_pos = self.cur_pos
return FormationUnit(cur_pos, self.name, self.poten_func, self.next_moves, self.move_cost_func, self.sight_range)
def add_to_formation(self, formation, is_leader):
self.is_leader = is_leader
self.formation = formation
def set_dest(self, dest):
self.leader_path = deque(self.formation.init_dest(dest))
def find_path(self):
if len(self.leader_path) > FormationUnit.MAX_PREDICT:
future_leader_pos = self.leader_path[FormationUnit.MAX_PREDICT]
else:
future_leader_pos = self.leader_path[-1]
short_dest = self.formation.predict_pos_from(future_leader_pos)
if not short_dest:
self.path = deque([self.cur_pos])
else:
self.path = deque(self.formation.find_path(self.cur_pos, short_dest))
def update_pos(self):
if self.at_objective:
return self.cur_pos
if not self.path:
self.find_path()
self.update_formation()
self.leader_path.popleft()
if not self.leader_path: # reached objective when leader path is empty
self.at_objective = True
next_pos = self.path.popleft()
if self.game_map.is_valid_point(next_pos):
self.cur_pos = next_pos
else:
vis_path = [pos for pos in self.path if self.can_see_point(pos)]
self.game_map.mock = [PathObject(vis_path)]
self.cur_pos = self.path_finder.best_potential_step(self.game_map, self)
def update_formation(self):
leader_pos = self.leader_path[0]
units = self.game_map.active # temporary measure as all active units are part of formation
self.formation.update_units(units, leader_pos)
def __str__(self):
return "{} {}: {}".format(self.name, self.formation.index, self.cur_pos)
def __repr__(self):
return "{} {}: {}".format(self.name, self.formation.index, self.cur_pos)
# Sample units
SCOUT = Unit(None, "Scout", inert_repel, adjacent_linear(), linear_cost(), bc19_9_radius())
FORMATION = FormationUnit(None, "Formation", None, adjacent_octile, diagonal_cost(), bc19_9_radius())