Skip to content

Commit e88e04d

Browse files
committed
GH-2: Added old work starting to implement computer players.
1 parent fbfbf27 commit e88e04d

File tree

6 files changed

+346
-50
lines changed

6 files changed

+346
-50
lines changed

project/game/breadth_search.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Ben-Ryder 2019
2+
3+
"""
4+
Currently not true Breadth First Search Algorithm. It will not return a path to the target. Currently it can use a
5+
breadth first style search to find the nearest occurrence of a value in a 2d-array grid based on a start position.
6+
"""
7+
8+
9+
class BreadthSearch:
10+
def __init__(self, grid):
11+
self.grid = grid
12+
self.size = [len(self.grid), len(self.grid[0])]
13+
14+
def get_value(self, location):
15+
return self.grid[location[0]][location[1]]
16+
17+
def get_nearest(self, position, target_value):
18+
open_queue = [position]
19+
closed_queue = []
20+
21+
while len(open_queue) > 0:
22+
current_location = open_queue[0]
23+
24+
# See if current location is equal to the target value
25+
if self.get_value(current_location) == target_value and current_location != position:
26+
return current_location
27+
28+
# Add remaining neighbours to queue
29+
neighbours = self.get_neighbours(current_location)
30+
for location in neighbours:
31+
if location not in closed_queue and location not in open_queue and \
32+
location[0] in range(0, self.size[0]) and \
33+
location[1] in range(0, self.size[1]): # stops infinite expansion
34+
open_queue.append(location)
35+
36+
closed_queue.append(open_queue.pop(0)) # close the current location
37+
38+
return None # no occurrences of the value
39+
40+
def get_all(self, position, target_value):
41+
open_queue = [position]
42+
closed_queue = []
43+
occurrences = []
44+
45+
while len(open_queue) > 0:
46+
current_location = open_queue[0]
47+
48+
# See if current location is equal to the target value
49+
if self.get_value(current_location) == target_value:
50+
occurrences.append(current_location)
51+
52+
# Add remaining neighbours to queue
53+
neighbours = self.get_neighbours(current_location)
54+
for location in neighbours:
55+
if location not in closed_queue and location not in open_queue and \
56+
location[0] in range(0, self.size[0]) and \
57+
location[1] in range(0, self.size[1]): # stops infinite expansion
58+
open_queue.append(location)
59+
60+
closed_queue.append(open_queue.pop(0)) # close the current location
61+
62+
return occurrences
63+
64+
def get_neighbours(self, location): # CAN RETURN LOCATIONS OUT OF RANGE.
65+
neighbours = []
66+
for x in range(location[0] - 1, location[0] + 2):
67+
for y in range(location[1] - 1, location[1] + 2):
68+
neighbours.append([x, y])
69+
return neighbours

project/game/computer_player.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import random
2+
3+
import constants
4+
5+
import project.game.breadth_search as breadth_first
6+
import project.game.shortest_path as shortest_path
7+
8+
MAP_WALLS = ["s", "l", "m", "o"]
9+
10+
# SPOILERS!!
11+
# By reading this file, you can learn how the computer plays against you. This could make it easier to win against it
12+
# and thus spoil the strategy of the game a bit. You have been warned :)
13+
14+
15+
class Logic:
16+
def take_go(self, model):
17+
model.current_player.start_turn()
18+
19+
self.handle_cities(model)
20+
self.handle_units(model)
21+
22+
model.current_player.end_turn()
23+
24+
def handle_cities(self, model):
25+
for city in model.current_player.settlements:
26+
# Spawning Units
27+
affordable_units = [name for name, values in constants.UNIT_SPECS.items()
28+
if model.current_player.get_ap() - values["spawn_cost"] > 0]
29+
unit_choice = random.choice(affordable_units)
30+
model.try_spawn(unit_choice, city.get_position())
31+
32+
def handle_units(self, model):
33+
for unit in model.current_player.units:
34+
if False: # in city conquer it
35+
pass
36+
else:
37+
# breadth_search = breadth_first.BreadthSearch(model.world.get_format())
38+
#
39+
# nearest_city = breadth_search.get_nearest(unit.position, "c")
40+
# path = shortest_path.GridPath(
41+
# model.world.get_format(),
42+
# unit.position, nearest_city,
43+
# walls=MAP_WALLS
44+
# ).get_path()
45+
# print(path)
46+
47+
# See if unit can take a city
48+
if model.check_conquer(unit):
49+
model.conquer(unit.position)
50+
else:
51+
# Make Random Move
52+
all_moves = model.get_moves(unit)
53+
if len(all_moves) > 0:
54+
move = random.choice(all_moves)
55+
model.move_unit(move, unit)
56+
57+
# Make Random Attack (if possible)
58+
all_attacks = model.get_attacks(unit)
59+
if len(all_attacks) > 0:
60+
all_units = sorted(
61+
[model.get_unit(position) for position in all_attacks],
62+
key=lambda x: x.health)
63+
enemy_unit = all_units[0] # target the weakest enemy
64+
model.make_attack(unit, enemy_unit)
65+
66+
67+
"""
68+
aspects of controlling a player:
69+
70+
- spawning units
71+
- moving units
72+
- attacking units
73+
- conquering units
74+
75+
- upgrade cities
76+
77+
78+
Unit (that is spawned)
79+
if in city, conquer it.
80+
81+
else:
82+
83+
- if can move into city, do so.
84+
85+
shortest path can be used between units and the nearest city, so allways moving towards nearest city.
86+
(prioritise un-populated cities then the enemy).
87+
if can move towards "nearest" city, do it.
88+
89+
- if enemy in range, attack it.
90+
91+
Upgrade and Spawn
92+
keep a record of the players actions (ie. if they spawned units or/and upgraded a city)
93+
94+
try to match the number of units spawned (and type?).
95+
then, try to match city upgrades
96+
if left over ap:
97+
if big diffrence in ap per turn, apply upgrades only.
98+
else if big diffrence in units, spawn units.
99+
else
100+
randomly pick units, or upgrades
101+
apply these
102+
103+
"""

project/game/model.py

+43-49
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import constants
66

77
import project.game.calculations as calculations
8+
import project.game.computer_player as computer_player
89

910

1011
class Model:
@@ -14,8 +15,9 @@ def __init__(self, game_name, map_name, players): # only when creating new game
1415
self.map_name = map_name
1516
self.game_end = False
1617

17-
self.players = [Player(p["name"], p["colour"]) for p in players]
18+
self.players = [Player(p["name"], p["colour"], p["control"]) for p in players]
1819
self.current_player = self.players[0]
20+
self.computer_logic = computer_player.Logic()
1921

2022
self.world = World(self.map_name, self.players) # assigns settlements to players
2123

@@ -27,24 +29,46 @@ def all_units(self):
2729
def next_turn(self):
2830
self.current_player.end_turn()
2931

30-
# Getting new turn
32+
# Getting new human player turn
3133
if not self.is_winner(): # if more than 1 player alive
32-
self.current_player = self.get_next_player()
34+
self.cycle_player()
3335
else:
3436
self.game_end = True
3537

3638
self.current_player.start_turn()
3739

3840
def get_next_player(self):
3941
valid_choice = False
40-
player = self.current_player
42+
index = self.players.index(self.current_player)
43+
4144
while not valid_choice: # wont be infinite, as to be called at least two players are left.
42-
if self.players.index(player) < len(self.players) - 1:
43-
player = self.players[self.players.index(player) + 1]
45+
if index < len(self.players) - 1:
46+
index += 1
4447
else:
45-
player = self.players[0]
46-
valid_choice = not player.is_dead()
47-
return player
48+
index = 0
49+
50+
valid_choice = not self.players[index].is_dead() and self.players[index].get_control() == "human"
51+
52+
return self.players[index]
53+
54+
def cycle_player(self):
55+
valid_choice = False
56+
57+
while not valid_choice: # wont be infinite, as to be called at least two players are left.
58+
if self.players.index(self.current_player) < len(self.players) - 1:
59+
self.current_player = self.players[self.players.index(self.current_player) + 1]
60+
else:
61+
self.current_player = self.players[0]
62+
63+
# Computer takes go, then we go on to find next human player
64+
if self.current_player.get_control() == "computer":
65+
self.computer_logic.take_go(self) # of current player
66+
if self.current_player.is_dead():
67+
self.current_player.units = []
68+
69+
valid_choice = not self.current_player.is_dead() and self.current_player.get_control() == "human"
70+
71+
return self.current_player
4872

4973
def try_spawn(self, unit_type, position):
5074
if not self.get_unit(position):
@@ -145,13 +169,18 @@ def get_attacks(self, unit):
145169
attacks.append([x, y])
146170
return attacks
147171

172+
def computer_turn(self):
173+
pass
174+
148175

149176
class Player:
150177
""" Each player of the game, which holds their units, key values and links to settlements etc"""
151-
def __init__(self, name, colour):
178+
def __init__(self, name, colour, control):
152179
self.name = name
153180
self.colour = colour
154-
self.camera_focus = [None, None] # TODO: system to auto-scroll to spawn
181+
self.control = control
182+
183+
self.camera_focus = [None, None] # TODO: system to assign player 1 city (as "spawn") on creation
155184
self.show_minimap = False
156185

157186
self.units = []
@@ -162,22 +191,12 @@ def __init__(self, name, colour):
162191
self.dead = False
163192
self.max_score = self.ap
164193

165-
# self.wood = 0
166-
# self.stone = 0
167-
# self.metal = 0
168-
169-
# def add_wood(self, amount):
170-
# self.wood += amount
171-
#
172-
# def add_stone(self, amount):
173-
# self.stone += amount
174-
#
175-
# def add_metal(self, amount):
176-
# self.metal += amount
177-
178194
def get_name(self):
179195
return self.name
180196

197+
def get_control(self):
198+
return self.control
199+
181200
def is_dead(self):
182201
return self.dead
183202

@@ -258,38 +277,13 @@ class Tile:
258277
def __init__(self, tile_type, position):
259278
self.type = tile_type
260279
self.position = position
261-
# self.wood, self.stone, self.metal = constants.TILE_DATA[tile_type]
262280

263281
def get_type(self):
264282
return self.type
265283

266284
def get_position(self):
267285
return self.position
268286

269-
# def take_wood(self, amount=1): # defaults, left for future in case decide change.
270-
# if self.wood > 0:
271-
# self.wood = self.wood - amount
272-
# if self.wood < 0:
273-
# self.wood = 0 # ensures resource is fully used, but cant go negative.
274-
# return True
275-
# return False
276-
#
277-
# def take_stone(self, amount=1):
278-
# if self.stone > 0:
279-
# self.stone = self.stone - amount
280-
# if self.stone < 0:
281-
# self.stone = 0
282-
# return True
283-
# return False
284-
#
285-
# def take_metal(self, amount=1):
286-
# if self.metal > 0:
287-
# self.metal = self.metal - amount
288-
# if self.metal < 0:
289-
# self.metal = 0
290-
# return True
291-
# return False
292-
293287

294288
class City:
295289
def __init__(self, name, position):

0 commit comments

Comments
 (0)