This commit is contained in:
2025-04-18 23:04:16 +00:00
parent 7a9acf9cb6
commit 817397ed44
13 changed files with 266 additions and 46 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+181
View File
@@ -95,3 +95,184 @@ class AI:
# return all the command objects.
return cmds
# Needs Seperate AI's for each faction
# AI for Zombies
def run_ai_zombies(self, faction_id, factions, cities, units, gmap):
cmds = []
# Generate Zombies (Typically Done Only At Start)
# Verify there are remaining zombie cities
if (len(cities['Zombies']) != 0):
cmd = BuildUnitCommand(faction_id,
cities['Zombies'][0].ID,
"Z")
cmds.append(cmd)
# Random moves (zombies)
my_units = units[faction_id]
for u in my_units:
rand_dir = random.choice(list(vec2.MOVES.keys()))
cmd = MoveUnitCommand(faction_id, u.ID, rand_dir)
cmds.append(cmd)
return cmds
# AI for Survivors
def run_ai_survivors(self, faction_id, factions, cities, units, gmap):
cmds = []
# Attempts to create new survivors via reproduction
my_cities = cities[faction_id]
city_indexes = list(range(len(my_cities)))
random.shuffle(city_indexes)
for ci in city_indexes:
cmd = BuildUnitCommand(faction_id,
my_cities[ci].ID,
"S")
cmds.append(cmd)
# Rare case
# If cured city is lost, and there are adequate survivors to make a new cured city
# one survivor city at random can produce a cured unit
if (len(cities['Cured']) == 0) and (len(units[faction_id]) >= 50):
my_cities = cities[faction_id]
city_indexes = list(range(len(my_cities)))
random.shuffle(city_indexes)
cmd = BuildUnitCommand("Cured",
my_cities[city_indexes].ID,
"C")
cmds.append(cmd)
# Survivors will also attempt to defend cured cities
totalCities = cities[faction_id] + cities['Cured']
# Loop through all of the survivors
for u in units[faction_id]:
# First checks to see if the survivor was given another tasking previously (i.e defend x city)
# If so, verify city had not fallen and move towards it
# If within 3 tiles of city, pick another target
if u.currentTarget in totalCities:
# 10% chance of random movement
if random.random() < 0.1:
rand_dir = random.choice(list(vec2.MOVES.keys()))
cmd = MoveUnitCommand(faction_id, u.ID, rand_dir)
cmds.append(cmd)
continue
# Target is a city, so move towards it
target = u.currentTarget
#Given current position as (x,y) and target as (x,y), calculate next move to get closer to city
vectorDirection = (target.pos.x - u.pos.x, target.pos.y - u.pos.y)
# X is larger, move that way first
if abs(vectorDirection[0]) > abs(vectorDirection[1]):
if vectorDirection[0] > 0:
cmd = MoveUnitCommand(faction_id, u.ID, 'E')
else:
cmd = MoveUnitCommand(faction_id, u.ID, 'W')
else:
if vectorDirection[1] > 0:
cmd = MoveUnitCommand(faction_id, u.ID, 'S')
else:
cmd = MoveUnitCommand(faction_id, u.ID, 'N')
cmds.append(cmd)
# Too close to city, so pick another target
if abs(u.pos.distance_man(target.pos)) <= 4:
if len(totalCities) != 0:
u.currentTarget = random.choice(totalCities)
# 2 Actions to prevent clumping
cmds.append(MoveUnitCommand(faction_id, u.ID, random.choice(list(vec2.MOVES.keys()))))
cmds.append(MoveUnitCommand(faction_id, u.ID, random.choice(list(vec2.MOVES.keys()))))
else: u.currentTarget = None
else:
# City has fallen, so clear target
# Establish new target (if available) and move randomly
if len(totalCities) != 0: u.currentTarget = random.choice(totalCities)
else: u.cuurrentTarget = None
rand_dir = random.choice(list(vec2.MOVES.keys()))
cmd = MoveUnitCommand(faction_id, u.ID, rand_dir)
cmds.append(cmd)
return cmds
# AI for Cured
def run_ai_cured(self, faction_id, factions, cities, units, gmap):
cmds = []
# Attempts to create new cured
my_cities = cities[faction_id]
city_indexes = list(range(len(my_cities)))
random.shuffle(city_indexes)
for ci in city_indexes:
cmd = BuildUnitCommand(faction_id,
my_cities[ci].ID,
"C")
cmds.append(cmd)
allCities = cities[faction_id] + cities['Zombies'] + cities['Survivors']
# Loop through all of the cured
for u in units[faction_id]:
# Once there are cured in the world, they will attempt to "infect" any cities on the map, one at a time
# Check if we are currently targeting a city
target = u.currentTarget
if target in allCities:
# 10% chance of random movement
if random.random() < 0.1:
rand_dir = random.choice(list(vec2.MOVES.keys()))
cmd = MoveUnitCommand(faction_id, u.ID, rand_dir)
cmds.append(cmd)
continue
#Given current position as (x,y) and target as (x,y), calculate next move to get closer to city
vectorDirection = (target.pos.x - u.pos.x, target.pos.y - u.pos.y)
# X is larger, move that way first
if abs(vectorDirection[0]) > abs(vectorDirection[1]):
if vectorDirection[0] > 0:
cmd = MoveUnitCommand(faction_id, u.ID, 'E')
else:
cmd = MoveUnitCommand(faction_id, u.ID, 'W')
else:
if vectorDirection[1] > 0:
cmd = MoveUnitCommand(faction_id, u.ID, 'S')
else:
cmd = MoveUnitCommand(faction_id, u.ID, 'N')
cmds.append(cmd)
if (target not in allCities) or (abs(u.pos.distance_man(target.pos)) <= 4):
# Pick first city that is not already Cured and target it
if len(allCities) != 0:
for city in allCities:
if city.faction_id != "Cured":
u.currentTarget = city
break
else: u.currentTarget = None
cmds.append(MoveUnitCommand(faction_id, u.ID, random.choice(list(vec2.MOVES.keys()))))
cmds.append(MoveUnitCommand(faction_id, u.ID, random.choice(list(vec2.MOVES.keys()))))
# No cities left to infect, so target remaining uncured units
if u.currentTarget == None or u.currentTarget.faction_id == "Cured":
for faction in factions:
if faction != faction_id:
for unit in units[faction]:
u.currentTarget = unit
break
else: u.currentTarget = None
rand_dir = random.choice(list(vec2.MOVES.keys()))
cmd = MoveUnitCommand(faction_id, u.ID, rand_dir)
cmds.append(cmd)
return cmds
+12 -10
View File
@@ -12,7 +12,9 @@
import cell_terrain
# Modified
# Open: -2 for Zombies
# Shadows: +4 for Zombies
class Cell:
def __init__(self, terrain):
self.terrain = terrain
@@ -23,19 +25,19 @@ class Cell:
match self.terrain:
case cell_terrain.Terrain.Open:
return "cornsilk2"
case cell_terrain.Terrain.Forest:
case cell_terrain.Terrain.Shadows:
return "cornsilk3"
def get_attack_mod(self):
def get_attack_mod(self, unitType):
match self.terrain:
case cell_terrain.Terrain.Open:
return 2
case cell_terrain.Terrain.Forest:
return 0
return -2 if unitType == "Z" else 0
case cell_terrain.Terrain.Shadows:
return 4 if unitType == "Z" else 0
def get_defense_mod(self):
def get_defense_mod(self, unitType):
match self.terrain:
case cell_terrain.Terrain.Open:
return 0
case cell_terrain.Terrain.Forest:
return 2
return -2 if unitType == "Z" else 0
case cell_terrain.Terrain.Shadows:
return +4 if unitType == "Z" else 0
+1 -1
View File
@@ -8,4 +8,4 @@ import enum
class Terrain(enum.Enum):
Open = 0
Forest = 1
Shadows = 1
+7 -1
View File
@@ -25,4 +25,10 @@ class Faction:
# ################################################################
def run_ai(self, factions, cities, units, gmap):
return self.ai.run_ai(self.ID, factions, cities, units, gmap)
match self.ID:
case "Zombies":
return self.ai.run_ai_zombies(self.ID, factions, cities, units, gmap)
case "Survivors":
return self.ai.run_ai_survivors(self.ID, factions, cities, units, gmap)
case "Cured":
return self.ai.run_ai_cured(self.ID, factions, cities, units, gmap)
+58 -23
View File
@@ -99,6 +99,7 @@ class Display:
def init_display(sw, sh):
pygame.init()
pygame.display.set_caption('Infection')
screen = pygame.display.set_mode((sw, sh))
clock = pygame.time.Clock()
display = Display(screen, clock)
@@ -124,7 +125,7 @@ def gen_factions(gmap):
# Allows one time generation of X zombies
# x = random.randint(4, 8) * 100
factions['Zombies'] = faction.Faction(
'Zombies', random.randint(8, 16) * 100,
'Zombies', random.randint(3, 16) * 100,
ai.AI(), 'darkgreen')
# Starts with normal amount of money
@@ -380,8 +381,8 @@ def RunCombat(attacker, defender, cmd, factions, unit_dict, cities, gmap):
def_roll = defender.roll(attacker.utype)
# Add terrain modifiers.
att_roll += att_cell.get_attack_mod()
def_roll += def_cell.get_defense_mod()
att_roll += att_cell.get_attack_mod(attacker.faction_id)
def_roll += def_cell.get_defense_mod(defender.faction_id)
# Damage health.
defender.health -= att_roll
@@ -491,11 +492,20 @@ class UnitDict:
def CheckForGameOver(cities):
def CheckForGameOver(cities, unit_dict):
faction_ids_with_cities = []
for c in cities:
if c.faction_id not in faction_ids_with_cities:
faction_ids_with_cities.append(c.faction_id)
# Verify all units are of same type
for unit in unit_dict.allUnits:
if unit.faction_id not in faction_ids_with_cities:
faction_ids_with_cities.append(unit.faction_id)
if len(faction_ids_with_cities) == 2 and "Survivors" in faction_ids_with_cities and "Cured" in faction_ids_with_cities:
return True, "Cured"
return len(faction_ids_with_cities) == 1, faction_ids_with_cities[0]
@@ -517,6 +527,8 @@ def GameLoop(display):
# - The map_cell_size given in the Display class above.
gmap = gen_game_map(40, 30)
maxY = None
factions = gen_factions(gmap)
# City gen is basically locked
cities = gen_cities(gmap)
@@ -547,8 +559,10 @@ def GameLoop(display):
speed = speed * 2
display.screen.fill("white")
display.screen.fill("cornsilk3")
game_over = [False, None]
if ticks >= speed:
ticks = 0
cities_by_faction = {}
@@ -562,11 +576,7 @@ def GameLoop(display):
RunAllCommands(commands, factions, unit_dict, cities, gmap)
turn += 1
game_over = CheckForGameOver(cities)
if game_over[0]:
print(f"Winning faction: {game_over[1]}")
display.run = False
game_over = CheckForGameOver(cities, unit_dict)
display.draw_map(gmap)
display.draw_cities(cities, factions)
@@ -575,26 +585,51 @@ def GameLoop(display):
# ###########################################3
# RIGHT_SIDE UI
display.draw_text(f"TURN {turn}", 805, 5, "black")
display.draw_text(f"{'Fctn':<5} {'C':>2} {'U':>3} {'M':>4}",
805, 25, "black")
y = 45
for fid, f in factions.items():
num_cities = 0
for c in cities:
if c.faction_id == fid:
num_cities += 1
display.draw_text(f"{fid:<5} {num_cities:>2} {len(unit_dict.unitsByFaction()[fid]):>3} {f.money:>4}",
805, y, "black")
y += 20
numZombies = len(unit_dict.unitsByFaction()["Zombies"])
numSurvivors = len(unit_dict.unitsByFaction()["Survivors"])
numCured = len(unit_dict.unitsByFaction()["Cured"])
totalUnits = len(unit_dict.allUnits)
infectionRate = numZombies / totalUnits * 100 if totalUnits > 0 else 0
cureRate = numCured / totalUnits * 100 if totalUnits > 0 else 0
display.draw_text("Infection Rate", 805, 45, "black")
display.draw_text(f"{infectionRate:.2f}%", 805, 65, "black")
display.draw_text("Cure Progress", 805, 85, "black")
display.draw_text(f"{cureRate:.2f}%", 805, 105, "black")
y = 145
display.draw_text(f"Cities", 805, y, "black")
y += 20
for city in cities:
display.draw_text(f"{city.ID}", 805, y, factions[city.faction_id].color)
y += 20
maxY = y
if game_over[0]:
display.draw_text(f"{game_over[1]} won", 805, maxY + 20, "black")
display.draw_text("Press any key to quit", 805, maxY + 40, "black")
pygame.display.flip()
key = None
while key == None:
for event in pygame.event.get():
if event.type == pygame.QUIT:
display.run = False
elif event.type == pygame.KEYDOWN:
key = event.key
print(f"Winning faction: {game_over[1]}")
display.run = False
pygame.display.flip()
def main():
random.seed(None)
display = init_display(1000, 600)
display = init_display(1100, 600)
GameLoop(display)
+1 -1
View File
@@ -14,7 +14,7 @@ import random
# algorithms here. If you want something fancier, you'd need
# to add them below and call them in game_map.py.
CELL_TERRAIN_PROBABILITY = {
cell_terrain.Terrain.Forest: 1,
cell_terrain.Terrain.Shadows: 1,
cell_terrain.Terrain.Open: 4
}
+6 -10
View File
@@ -15,8 +15,8 @@ import math
# C = CURED (EXTREMELY SLOW)
UNIT_COSTS = {
"Z": 100,
"S": 500,
"C": 2500
"S": 250,
"C": 2000
}
# UNIT_HEALATH is a constant dictionary that holds the starting health
@@ -28,7 +28,7 @@ UNIT_COSTS = {
# C = CURED (HIGH HEALTH)
UNIT_HEALTH = {
"Z": 5,
"Z": 6,
"S": 7,
"C": 7
}
@@ -75,6 +75,9 @@ class Unit:
# sight_radius: int - how far it sees
# NOT USED.
self.sight_radius = sight_radius
# Allows for persistent target (if applicable)
self.currentTarget = None
def __eq__(self, o):
return self.ID == o.ID and self.faction_id == o.faction_id
@@ -91,11 +94,4 @@ class Unit:
# rock-paper-scissors have max damage of 20. All other
# combinations are 10. Feel free to modify if you want.
def roll(self, op_utype):
if op_utype == 'R' and self.utype == 'P':
return random.randint(0, 20)
elif op_utype == 'P' and self.utype == 'S':
return random.randint(0, 20)
elif op_utype == 'S' and self.utype == 'R':
return random.randint(0, 20)
else:
return random.randint(0, 10)