diff --git a/.idea/SGAI-DRHAT-Outbreak-Final.iml b/.idea/SGAI-DRHAT-Outbreak-Final.iml
new file mode 100644
index 0000000..fa7a615
--- /dev/null
+++ b/.idea/SGAI-DRHAT-Outbreak-Final.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..3dce9c6
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..dc9ea49
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..171a93e
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SGAI_MK3/Assets/.DS_Store b/Assets/.DS_Store
similarity index 100%
rename from SGAI_MK3/Assets/.DS_Store
rename to Assets/.DS_Store
diff --git a/Assets/Kill sound.mp3 b/Assets/Kill sound.mp3
new file mode 100644
index 0000000..0b32778
Binary files /dev/null and b/Assets/Kill sound.mp3 differ
diff --git a/Assets/Outbreak_title.png b/Assets/Outbreak_title.png
new file mode 100644
index 0000000..e227e9b
Binary files /dev/null and b/Assets/Outbreak_title.png differ
diff --git a/Assets/RedCross.png b/Assets/RedCross.png
new file mode 100644
index 0000000..d81533d
Binary files /dev/null and b/Assets/RedCross.png differ
diff --git a/Assets/bite.png b/Assets/bite.png
new file mode 100644
index 0000000..495e05f
Binary files /dev/null and b/Assets/bite.png differ
diff --git a/Assets/cure.png b/Assets/cure.png
new file mode 100644
index 0000000..754d796
Binary files /dev/null and b/Assets/cure.png differ
diff --git a/SGAI_MK3/Assets/govt.png b/Assets/govt.png
similarity index 100%
rename from SGAI_MK3/Assets/govt.png
rename to Assets/govt.png
diff --git a/Assets/kill.png b/Assets/kill.png
new file mode 100644
index 0000000..5b5fdef
Binary files /dev/null and b/Assets/kill.png differ
diff --git a/SGAI_MK3/Assets/map_background.jpeg b/Assets/map_background.jpeg
similarity index 100%
rename from SGAI_MK3/Assets/map_background.jpeg
rename to Assets/map_background.jpeg
diff --git a/Assets/person_normal.png b/Assets/person_normal.png
new file mode 100644
index 0000000..09afb34
Binary files /dev/null and b/Assets/person_normal.png differ
diff --git a/SGAI_MK3/Assets/person_vax.png b/Assets/person_vax.png
similarity index 100%
rename from SGAI_MK3/Assets/person_vax.png
rename to Assets/person_vax.png
diff --git a/Assets/person_zombie.png b/Assets/person_zombie.png
new file mode 100644
index 0000000..30ddfdb
Binary files /dev/null and b/Assets/person_zombie.png differ
diff --git a/SGAI_MK3/Assets/self_play.png b/Assets/self_play.png
similarity index 100%
rename from SGAI_MK3/Assets/self_play.png
rename to Assets/self_play.png
diff --git a/SGAI_MK3/Assets/theirturn.png b/Assets/theirturn.png
similarity index 100%
rename from SGAI_MK3/Assets/theirturn.png
rename to Assets/theirturn.png
diff --git a/SGAI_MK3/Assets/yourturn.png b/Assets/yourturn.png
similarity index 100%
rename from SGAI_MK3/Assets/yourturn.png
rename to Assets/yourturn.png
diff --git a/SGAI_MK3/Assets/zom.png b/Assets/zom.png
similarity index 100%
rename from SGAI_MK3/Assets/zom.png
rename to Assets/zom.png
diff --git a/Board.py b/Board.py
new file mode 100644
index 0000000..dd0b93b
--- /dev/null
+++ b/Board.py
@@ -0,0 +1,499 @@
+from State import State
+import random as rd
+from Person import Person
+from typing import List, Tuple
+from constants import *
+import pygame
+pygame.mixer.init()
+
+
+class Board:
+ #initializing variables
+ def __init__(
+ self,
+ dimensions: Tuple[int, int],
+ border: int,
+ cell_dimensions: Tuple[int, int],
+ player_role: Role,
+ ):
+ self.outrage = 0
+ self.anxiety = 0
+
+ self.rows = dimensions[0]
+ self.columns = dimensions[1]
+ self.display_border = border
+ self.display_cell_dimensions = cell_dimensions
+ self.player_role = player_role
+
+ if player_role == Role.government:
+ self.computer_role = Role.zombie
+ else:
+ self.computer_role = Role.government
+
+ self.population = 0 # total number of people and zombies
+ self.States = []
+ self.QTable = []
+ for s in range(dimensions[0] * dimensions[1]): # creates a 1d array of the board
+ self.States.append(State(None, s))
+ self.QTable.append([0] * 6)
+
+ self.actionToFunction = {
+ Action.move: self.move,
+ Action.heal: self.heal,
+ Action.bite: self.bite,
+ Action.kill: self.kill
+ }
+
+ def num_zombies(self) -> int: #number of zombies on the board, different than population
+ r = 0
+ for state in self.States:
+ if state.person != None:
+ if state.person.isZombie:
+ r += 1
+ return r
+
+ def act(self, oldstate: Tuple[int, int], givenAction: str): # takes in the cell and action and performs that using the actiontofunction
+ cell = self.toCoord(oldstate)
+ f = self.actionToFunction[givenAction](cell)
+ reward = self.States[oldstate].evaluate(givenAction, self)
+ if f[0] == False:
+ reward = 0
+ return [reward, f[1]]
+
+ def containsPerson(self, isZombie: bool): #checks if person is a person
+ for state in self.States:
+ if state.person is not None and state.person.isZombie == isZombie:
+ return True
+ return False
+
+ def get_possible_moves(self, action: Action, direction: Direction, role):
+ """
+ Get the coordinates of people (or zombies) that are able
+ to make the specified move.
+ @param action - the action to return possibilities for (options are Action.bite, Action.move, Action.heal, and Action.kil)
+ @param direction - the direction this action is heading (options are Direction.up, Direction.down, Direction.left, Direction.right)
+ @param role - either 'Zombie' or 'Government'; helps decide whether an action
+ is valid and which people/zombies it applies to
+ """
+ poss = []
+ B = self.clone(self.States, role)
+
+ if role == Role.zombie:
+ if not self.containsPerson(True):
+ return poss
+
+ for state in self.States:
+ if state.person is not None:
+ changed_states = False
+
+ if (
+ state.person.isZombie
+ and bool(B.actionToFunction[action](B.toCoord(state.location), direction).value)
+ ):
+ poss.append(B.toCoord(state.location))
+ changed_states = True
+
+ if changed_states:
+ # reset the states because it had to check the possible moves by moving the states
+ B.States = [
+ self.States[i].clone()
+ if self.States[i] != B.States[i]
+ else B.States[i]
+ for i in range(len(self.States))
+ ]
+
+ elif role == Role.government:
+ if not self.containsPerson(False):
+
+ return poss
+ for state in self.States:
+ if state.person is not None: #checks if the boxes are empty
+ changed_states = False
+ if (
+ not state.person.isZombie
+ and bool(B.actionToFunction[action](B.toCoord(state.location), direction).value)
+ ):
+ poss.append(B.toCoord(state.location))
+ changed_states = True
+
+ if changed_states:
+ # reset the states cuz it moved the board to check possibilities
+ B.States = [
+ self.States[i].clone()
+ if self.States[i] != B.States[i]
+ else B.States[i]
+ for i in range(len(self.States))
+ ]
+ return poss
+
+ def toCoord(self, i: int): # converts coord from 1d to 2d
+ return (int(i % self.columns), int(i / self.rows))
+
+ def toIndex(self, coordinates: Tuple[int, int]): # converts coord from 2d to 1d
+ return int(coordinates[1] * self.columns) + int(coordinates[0])
+
+ def isValidCoordinate(self, coordinates: Tuple[int, int]): #checks if the box is in the grid
+ return (
+ coordinates[1] < self.rows
+ and coordinates[1] >= 0
+ and coordinates[0] < self.columns
+ and coordinates[0] >= 0
+ )
+
+ def clone(self, L: List[State], role: Role): #creates a duplicate board
+
+ NB = Board(
+ (self.rows, self.columns),
+ self.display_border,
+ self.display_cell_dimensions,
+ self.player_role,
+ )
+ NB.States = [state.clone() for state in L]
+ NB.player_role = role
+ return NB
+
+ def isAdjacentTo(self, coord: Tuple[int, int], is_zombie: bool) -> bool: # returns adjacent coordinates containing the same type (so person if person etc)
+
+ ret = False
+ vals = [
+ (coord[0], coord[1] + 1),
+ (coord[0], coord[1] - 1),
+ (coord[0] + 1, coord[1]),
+ (coord[0] - 1, coord[1]),
+ ]
+ for coord in vals:
+ if (
+ self.isValidCoordinate(coord)
+ and self.States[self.toIndex(coord)].person is not None
+ and self.States[self.toIndex(coord)].person.isZombie == is_zombie
+ ):
+ ret = True
+ break
+
+ return ret
+
+ def getTargetCoords(self, coords: Tuple[int, int], direction: Direction) -> Tuple[int, int]:
+ if direction == Direction.up:
+ new_coords = (coords[0], coords[1] - 1)
+ print(f"going from {coords} to new coords {new_coords}")
+ elif direction == Direction.down:
+ new_coords = (coords[0], coords[1] + 1)
+ print(f"going from {coords} to new coords {new_coords}")
+ elif direction == Direction.left:
+ new_coords = (coords[0] - 1, coords[1])
+ print(f"going from {coords} to new coords {new_coords}")
+ elif direction == Direction.right:
+ new_coords = (coords[0] + 1, coords[1])
+ print(f"going from {coords} to new coords {new_coords}")
+ elif direction == Direction.self:
+ new_coords = coords
+
+ self.States[self.toIndex(coords)].person.facing = Direction
+
+ return new_coords
+
+ def move(self, coords: Tuple[int, int], direction: Direction) -> Result:
+ new_coords = self.getTargetCoords(coords, direction)
+ if direction == Direction.self: return Result.invalid
+ if not self.isValidCoordinate(new_coords): return Result.invalid
+
+ # Get the start and destination index (1D)
+ start_idx = self.toIndex(coords)
+ destination_idx = self.toIndex(new_coords)
+
+ # Check if the new coordinates are valid
+ if not self.isValidCoordinate(new_coords):
+ return Result.invalid
+ if(
+ self.States[start_idx].person.isZombie
+ and self.States[destination_idx].safeSpace
+ ):
+ return Result.invalid
+
+ # Check if the destination is currently occupied
+ if self.States[destination_idx].person is None:
+ #Execute Move
+ self.States[destination_idx].person = self.States[start_idx].person
+ self.States[start_idx].person = None
+ return Result.success
+ return Result.invalid
+
+ def QGreedyat(self, state_id):
+ biggest = self.QTable[state_id][0] * self.player_role
+ ind = 0
+ A = self.QTable[state_id]
+ i = 0
+ for qval in A:
+ if (qval * self.player_role) > biggest:
+ biggest = qval
+ ind = i
+ i += 1
+ return [ind, self.QTable[ind]] # action_index, qvalue
+
+ # picks the action for the move in the qtable, including a probability that it is randomized based on the learning rate
+ def choose_action(self, state_id: int, lr: float):
+ L = lr * 100
+ r = rd.randint(0, 100)
+ if r < L:
+ return self.QGreedyat(state_id)
+ else:
+ if self.player_role == Role.government: # Player is Govt
+ d = rd.randint(0, 4)
+ else:
+ d = rd.randint(0, 5)
+ while d != 4:
+ d = rd.randint(0, 4)
+ return d
+
+ # picks the person that it wants to move or use, also including a learning rate based probability, returning the index of the state
+ def choose_state(self, lr: float):
+ L = lr * 100
+ r = rd.randint(0, 100)
+ if r < L:
+ biggest = None
+ sid = None
+ for x in range(len(self.States)):
+ if self.States[x].person != None:
+ q = self.QGreedyat(x)
+ if biggest is None:
+ biggest = q[1]
+ sid = x
+ elif q[1] > biggest:
+ biggest = q[1]
+ sid = x
+ return self.QGreedyat(sid)
+ else:
+ if self.player_role == Role.government: # Player is Govt
+ d = rd.randint(0, len(self.States))
+ while self.States[d].person is None or self.States[d].person.isZombie:
+ d = rd.randint(0, len(self.States))
+ else:
+ d = rd.randint(0, len(self.States))
+ while (
+ self.States[d].person is None
+ or self.States[d].person.isZombie == False
+ ):
+ d = rd.randint(0, len(self.States))
+ return d
+
+ def bite(self, coords: Tuple[int, int], direction: Direction) -> Result:
+ target_coords = self.getTargetCoords(coords, direction)
+ if direction == Direction.self: return Result.invalid
+ if not self.isValidCoordinate(target_coords): return Result.invalid
+
+ # Get the start and destination index (1D)
+ start_idx = self.toIndex(coords)
+ target_idx = self.toIndex(target_coords)
+
+ #check if the orgin is valid
+ if (
+ self.States[start_idx].person is None
+ or not self.States[start_idx].person.isZombie
+ ):
+ return Result.invalid
+ if(
+ self.States[start_idx].person.isZombie
+ and self.States[target_idx].safeSpace
+ ):
+ return Result.invalid
+
+
+ # Check if the destination is valid
+ if (
+ self.States[target_idx].person is None
+ or self.States[target_idx].person.isZombie
+ or self.States[target_idx].safeSpace
+ ):
+ return Result.invalid
+
+ #calculate probability
+ chance = 100
+ target = self.States[target_idx].person
+ if target.isVaccinated:
+ chance = 15
+ elif target.wasVaccinated != target.wasCured:
+ chance = 75
+ elif target.wasVaccinated and target.wasCured:
+ chance = 50
+
+ # Execute Bite
+ r = rd.randint(0, 100)
+ if r < chance:
+ newTarget = target.clone()
+ newTarget.isZombie = True
+ newTarget.isVaccinated = False
+ self.States[target_idx].person = newTarget
+ return Result.success
+ return Result.failure
+
+ def heal(self, coords: Tuple[int, int], direction: Direction) -> Result:
+ """
+ the person at the stated coordinate heals the zombie to the person's stated direction
+ If no person is selected, then return [False, None]
+ if a person is vaccined, then return [True, index]
+ """
+ target_coords = self.getTargetCoords(coords, direction)
+ if not self.isValidCoordinate(target_coords): return Result.invalid
+
+ # Get the start and destination index (1D)
+ start_idx = self.toIndex(coords)
+ target_idx = self.toIndex(target_coords)
+
+ #check if the orgin is valid
+ if (
+ self.States[start_idx].person is None
+ or self.States[start_idx].person.hasMed == False
+ or self.States[start_idx].person.isZombie
+ or self.States[start_idx].safeSpace
+ ):
+ return Result.invalid
+
+
+ # Check if the destination is valid
+ if (
+ self.States[target_idx].person is None
+ ):
+ return Result.invalid
+
+ #probability of heal vs failed heal
+ if self.States[target_idx].person.isZombie:
+ chance = 50
+ else:
+ chance = 100
+
+ r = rd.randint(0, 100)
+ self.States[start_idx].person.hasMed = False
+ if r < chance:
+ #implement heal
+ newTarget = self.States[target_idx].person.clone()
+ newTarget.isZombie = False
+ newTarget.wasCured = True
+ newTarget.isVaccinated = True
+ newTarget.turnsVaccinated = 1
+ self.States[target_idx].person = newTarget
+
+ if chance == 50:
+ self.anxiety -= 6
+ else:
+ self.anxiety -= 1
+
+ else:
+ #implement failed heal
+ self.bite(target_coords, reverse_dir[direction])
+ return Result.failure
+ return Result.success
+
+ def kill(self, coords: Tuple[int, int], direction: Direction) -> Result:
+ target_coords = self.getTargetCoords(coords, direction)
+ if direction == Direction.self: return Result.invalid
+ if not self.isValidCoordinate(target_coords): return Result.invalid
+
+ # Get the start and destination index (1D)
+ start_idx = self.toIndex(coords)
+ target_idx = self.toIndex(target_coords)
+
+ #check if the orgin is valid
+ if (
+ self.States[start_idx].person is None
+ or self.States[start_idx].person.isZombie
+ or self.States[start_idx].safeSpace
+ ):
+ return Result.invalid
+
+
+ # Check if the destination is valid
+ if (
+ self.States[target_idx].person is None
+ or not self.States[target_idx].person.isZombie
+ ):
+ return Result.invalid
+
+ # Execute Kill
+ self.States[target_idx].person = None
+ KILL_SOUND.play()
+ self.outrage += 0.5 * (100 - self.anxiety)
+
+ return Result.success
+
+ def med(self):
+ for idx in range(len(self.States)):
+ state = self.States[idx]
+ if(
+ state.safeSpace == True
+ and state.person is not None
+ ):
+ state.person.hasMed = True
+ self.States[idx] = state
+
+ #gets all the locations of people or zombies on the board (this can be used to count them as well)
+ def get_possible_states(self, role_number: int):
+ indexes = []
+ i = 0
+ for state in self.States:
+ if state.person != None:
+ if role_number == 1 and state.person.isZombie == False:
+ indexes.append(i)
+ elif role_number == -1 and state.person.isZombie:
+ indexes.append(i)
+ i += 1
+ return indexes
+
+ # runs each choice for the qlearning algorithm
+ def step(self, role_number: int, learningRate: float):
+ P = self.get_possible_states(role_number) #gets all the relevent players
+ r = rd.uniform(0, 1)
+ if r < learningRate: # 50% chance of this happening
+ rs = rd.randrange(0, len(self.States) - 1)
+ if role_number == 1:
+ while (
+ self.States[rs].person is not None
+ and self.States[rs].person.isZombie
+ ): #picks a relevent person, but idk why its not via all the possible people
+ rs = rd.randrange(0, len(self.States) - 1) #and instead searches all the states again
+ else:
+ while (
+ self.States[rs].person is not None
+ and self.States[rs].person.isZombie == False #same thing but for zombie
+ ):
+ rs = rd.randrange(0, len(self.States) - 1)
+
+ # random state and value
+ # old_value = QTable[state][acti]
+ # next_max = np.max(QTable[next_state])
+ # new_value = (1 - alpha) * old_value + alpha * (reward + gamma * next_max)
+ # QTable[state][acti] = new_value
+
+ #adds the people into the grid
+ def populate(self):
+
+ self.anxiety = 0
+ self.outrage = 0
+
+ #make between 7 and boardsize/3 people
+ allppl = rd.sample(range(len(self.States)), rd.randint(7, ((self.rows * self.columns) / 3)))
+ for state in range(len(self.States)):
+ self.States[state].person = None
+ if state in allppl:
+ self.States[state].person = Person(False)
+ self.population += 1
+
+ #turn half the humans into zombies
+ allzombs = rd.sample(range(len(allppl)), len(allppl)//4)
+ for person in allzombs:
+ self.States[allppl[person]].person.isZombie = True
+
+ #add two safe spaces
+ noZombieInSafe = False
+ while not noZombieInSafe:
+ allsafes = rd.sample(range(len(self.States)), rd.randint(1, (self.rows*self.columns)//15))
+ for state in range(len(self.States)):
+ if (
+ self.States[state].person is not None
+ and self.States[state].person.isZombie
+ ):
+ continue
+ else:
+ noZombieInSafe = True
+
+ if state in allsafes:
+ self.States[state].safeSpace = True
diff --git a/Constants.py b/Constants.py
new file mode 100644
index 0000000..aad47b0
--- /dev/null
+++ b/Constants.py
@@ -0,0 +1,41 @@
+import enum
+import pygame
+import os
+pygame.mixer.init()
+
+
+ROWS = 6
+COLUMNS = 6
+BORDER = 150 # Number of pixels to offset grid to the top-left side
+CELL_DIMENSIONS = (100, 100) # Number of pixels (x,y) for each cell
+SELF_PLAY = True # whether or not a human will be playing
+KILL_SOUND = pygame.mixer.Sound(os.path.join('Assets', 'Kill sound.mp3'))
+
+class Action(enum.Enum):
+ move = 1
+ bite = 2
+ heal = 3
+ kill = 4
+
+class Direction(enum.Enum):
+ self = 0
+ up = 1
+ down = 2
+ left = 3
+ right = 4
+
+class Role(enum.Enum):
+ government = 0
+ zombie = 1
+
+class Result(enum.Enum):
+ invalid = 0
+ success = 1
+ failure = 2
+
+reverse_dir = {
+ Direction.up: Direction.down,
+ Direction.down: Direction.up,
+ Direction.left: Direction.right,
+ Direction.right: Direction.left
+}
diff --git a/SGAI_MK3/Person.py b/Person.py
similarity index 95%
rename from SGAI_MK3/Person.py
rename to Person.py
index 7c75b28..43adbc4 100644
--- a/SGAI_MK3/Person.py
+++ b/Person.py
@@ -1,13 +1,17 @@
import random as rd
+from constants import Direction
+
class Person:
def __init__(self, iz: bool):
+ self.facing = Direction.up
self.isZombie = iz
self.wasVaccinated = False
self.turnsVaccinated = 0
self.isVaccinated = False
self.wasCured = False
+ self.hasMed = False
def clone(self):
ret = Person(self.isZombie)
diff --git a/PygameFunctions.py b/PygameFunctions.py
new file mode 100644
index 0000000..9f5d5b5
--- /dev/null
+++ b/PygameFunctions.py
@@ -0,0 +1,352 @@
+from typing import Tuple
+import pygame
+from constants import *
+from Board import Board
+from math import tanh
+
+
+# constants
+BACKGROUND = "#DDC2A1"
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+CELL_COLOR = (233, 222, 188)
+SAFE_COLOR = (93, 138, 168)
+LINE_WIDTH = 5
+GAME_WINDOW_DIMENSIONS = (1200, 800)
+RESET_MOVE_COORDS = (800, 600)
+RESET_MOVE_DIMS = (200, 50)
+
+# Initialize pygame
+screen = pygame.display.set_mode(GAME_WINDOW_DIMENSIONS)
+pygame.display.set_caption("Outbreak!")
+pygame.font.init()
+font = pygame.font.SysFont("Impact", 30)
+pygame.display.set_caption("Outbreak!")
+screen.fill(BACKGROUND)
+
+
+def get_action(GameBoard: Board, pixel_x: int, pixel_y: int):
+ """
+ Get the action that the click represents.
+ If the click was on the heal or kill button, returns Action.heal or Action.kill respectively
+ Else, returns the board coordinates of the click (board_x, board_y) if valid
+ Return None otherwise
+ """
+ # Check if the user clicked on the "heal" icon, return "heal" if so
+
+ heal_bite_check = pixel_x >= 900 and pixel_x <= 1100 and pixel_y > 190 and pixel_y < 301
+ kill_check = pixel_x >= 800 and pixel_x <= 900 and pixel_y > 199 and pixel_y < 301
+ Med_check = pixel_x >= 800 and pixel_x <= 900 and pixel_y > 301 and pixel_y < 401
+ reset_move_check = (
+ pixel_x >= RESET_MOVE_COORDS[0]
+ and pixel_x <= RESET_MOVE_COORDS[0] + RESET_MOVE_DIMS[0]
+ and pixel_y >= RESET_MOVE_COORDS[1]
+ and pixel_y <= RESET_MOVE_COORDS[1] + RESET_MOVE_DIMS[1]
+ )
+ board_x = int((pixel_x - 150) / 100)
+ board_y = int((pixel_y - 150) / 100)
+ move_check = (
+ board_x >= 0
+ and board_x < GameBoard.columns
+ and board_y >= 0
+ and board_y < GameBoard.rows
+ )
+ board_coords = (int((pixel_x - 150) / 100), int((pixel_y - 150) / 100))
+
+ if heal_bite_check:
+ if GameBoard.player_role == Role.government:
+ return Action.heal
+ else:
+ return Action.bite
+ elif Med_check:
+ return "Distrb Med"
+ elif kill_check:
+ return Action.kill
+ elif reset_move_check:
+ return "reset move"
+ elif move_check:
+ return board_coords
+ return None
+
+
+def run(GameBoard: Board):
+ """
+ Draw the screen and return any events.
+ """
+ screen.fill(BACKGROUND)
+ build_grid(GameBoard) # Draw the grid
+
+ # Draw the heal icon
+ if GameBoard.player_role == Role.government:
+ display_image(screen, "Assets/cure.png", GameBoard.display_cell_dimensions, (950, 200))
+ display_image(screen, "Assets/kill.png", GameBoard.display_cell_dimensions, (800, 200))
+ display_image(screen, "Assets/RedCross.png", GameBoard.display_cell_dimensions, (800, 300))
+ else:
+ display_image(screen, "Assets/bite.png", GameBoard.display_cell_dimensions, (950, 200))
+ #Draw the kill button slightly to the left of heal
+ display_people(GameBoard)
+ display_reset_move_button()
+ screen.blit(font.render(f"public outrage: {int(GameBoard.outrage)} %", True, WHITE), (10, 10))
+ screen.blit(font.render(f"public anxiety: {int(GameBoard.anxiety)} %", True, WHITE), (10, 40))
+ return pygame.event.get()
+
+
+def disp_title_screen():
+ """
+ Displays a basic title screen with title, start button, and quit button
+ """
+ start_text = font.render('START', True, WHITE)
+ quit_text = font.render('QUIT', True, WHITE)
+ screen.fill(BACKGROUND)
+ #Draw title
+ display_image(screen, "Assets/Outbreak_title.png", (1048, 238), (76, 100))
+ #Check if the user has clicked either start or quit
+ while True:
+ mouse = pygame.mouse.get_pos()
+ #Draw the start and quit buttons (They might need a little bit more work at some point, they're not centered well)
+ pygame.draw.rect(screen,BLACK,[500,350,200,100])
+ pygame.draw.rect(screen,BLACK,[500,500,200,100])
+ screen.blit(start_text, (560, 375))
+ screen.blit(quit_text, (570, 525))
+ for i in pygame.event.get():
+ if i.type == pygame.MOUSEBUTTONDOWN:
+ if 500 <= mouse[0] <= 700 and 350 <= mouse[1] <= 450:
+ return True
+ elif 500 <= mouse[0] <= 700 and 500 <= mouse[1] <= 600:
+ pygame.display.quit()
+ break
+ pygame.display.update()
+
+def display_safe_space(GameBoard):
+ """
+ Creates a blue rectangle at every safe space state
+ """
+ for state in GameBoard.States:
+ if state.safeSpace:
+ coords = (
+ int(GameBoard.toCoord(state.location)[0]) * GameBoard.display_cell_dimensions[0]
+ + GameBoard.display_border,
+ int(GameBoard.toCoord(state.location)[1]) * GameBoard.display_cell_dimensions[1]
+ + GameBoard.display_border,
+ )
+ #draw a rectangle of dimensions 100x100 at the coordinates created above
+ pygame.draw.rect(screen, SAFE_COLOR, pygame.Rect(coords[0], coords[1], 100, 100))
+
+
+def display_reset_move_button():
+ rect = pygame.Rect(
+ RESET_MOVE_COORDS[0],
+ RESET_MOVE_COORDS[1],
+ RESET_MOVE_DIMS[0],
+ RESET_MOVE_DIMS[1],
+ )
+ pygame.draw.rect(screen, BLACK, rect)
+ screen.blit(font.render("Reset move?", True, WHITE), RESET_MOVE_COORDS)
+
+
+def display_image(
+ screen: pygame.Surface,
+ itemStr: str,
+ dimensions: Tuple[int, int],
+ position: Tuple[int, int],
+):
+ """
+ Draw an image on the screen at the indicated position.
+ """
+ v = pygame.image.load(itemStr).convert_alpha()
+ v = pygame.transform.scale(v, dimensions)
+ screen.blit(v, position)
+
+
+def build_grid(GameBoard: Board):
+ """
+ Draw the grid on the screen.
+ """
+
+ grid_width = GameBoard.columns * GameBoard.display_cell_dimensions[0]
+ grid_height = GameBoard.rows * GameBoard.display_cell_dimensions[1]
+ # left
+ pygame.draw.rect(
+ screen,
+ BLACK,
+ [
+ GameBoard.display_border - LINE_WIDTH,
+ GameBoard.display_border - LINE_WIDTH,
+ LINE_WIDTH,
+ grid_height + (2 * LINE_WIDTH),
+ ],
+ )
+ # right
+ pygame.draw.rect(
+ screen,
+ BLACK,
+ [
+ GameBoard.display_border + grid_width,
+ GameBoard.display_border - LINE_WIDTH,
+ LINE_WIDTH,
+ grid_height + (2 * LINE_WIDTH),
+ ],
+ )
+ # bottom
+ pygame.draw.rect(
+ screen,
+ BLACK,
+ [
+ GameBoard.display_border - LINE_WIDTH,
+ GameBoard.display_border + grid_height,
+ grid_width + (2 * LINE_WIDTH),
+ LINE_WIDTH,
+ ],
+ )
+ # top
+ pygame.draw.rect(
+ screen,
+ BLACK,
+ [
+ GameBoard.display_border - LINE_WIDTH,
+ GameBoard.display_border - LINE_WIDTH,
+ grid_width + (2 * LINE_WIDTH),
+ LINE_WIDTH,
+ ],
+ )
+ # Fill the inside wioth the cell color
+ pygame.draw.rect(
+ screen,
+ CELL_COLOR,
+ [GameBoard.display_border, GameBoard.display_border, grid_width, grid_height],
+ )
+ #Draw the safe space so that it is under the lines
+ display_safe_space(GameBoard)
+ # Draw the vertical lines
+ i = GameBoard.display_border + GameBoard.display_cell_dimensions[0]
+ while i < GameBoard.display_border + grid_width:
+ pygame.draw.rect(
+ screen, BLACK, [i, GameBoard.display_border, LINE_WIDTH, grid_height]
+ )
+ i += GameBoard.display_cell_dimensions[0]
+ # Draw the horizontal lines
+ i = GameBoard.display_border + GameBoard.display_cell_dimensions[1]
+ while i < GameBoard.display_border + grid_height:
+ pygame.draw.rect(
+ screen, BLACK, [GameBoard.display_border, i, grid_width, LINE_WIDTH]
+ )
+ i += GameBoard.display_cell_dimensions[1]
+
+
+def display_people(GameBoard: Board):
+ """
+ Draw the people (government, vaccinated, and zombies) on the grid.
+ """
+ for x in range(len(GameBoard.States)):
+ if GameBoard.States[x].person != None:
+ p = GameBoard.States[x].person
+ char = "Assets/person_normal.png"
+ if p.isZombie:
+ char = "Assets/person_zombie.png"
+ elif p.isVaccinated:
+ char = "Assets/person_normal.png"
+ coords = (
+ int(x % GameBoard.rows) * GameBoard.display_cell_dimensions[0]
+ + GameBoard.display_border
+ + 35,
+ int(x / GameBoard.columns) * GameBoard.display_cell_dimensions[1]
+ + GameBoard.display_border
+ + 20,
+ )
+ display_image(screen, char, (35, 60), coords)
+ if p.hasMed == True:
+ display_image(screen, "Assets/RedCross.png", (20, 20), (coords[0]+25, coords[1]))
+
+#Creates buttons that allow the player to quit or restart
+def display_win_screen():
+ restart_text = font.render('PLAY AGAIN', True, WHITE)
+ quit_text = font.render('QUIT', True, WHITE)
+ screen.fill(BACKGROUND)
+ screen.blit(
+ font.render("You win!", True, WHITE),
+ (500, 350),
+ )
+ screen.blit(
+ font.render("There were no possible moves for the computer.", True, WHITE),
+ (500, 400),
+ )
+
+ while True:
+ mouse = pygame.mouse.get_pos()
+ pygame.draw.rect(screen,BLACK,[500,450,200,100])
+ pygame.draw.rect(screen,BLACK,[500,600,200,100])
+ screen.blit(restart_text, (550, 475))
+ screen.blit(quit_text, (570, 625))
+ for i in pygame.event.get():
+ if i.type == pygame.MOUSEBUTTONDOWN:
+ if 500 <= mouse[0] <= 700 and 450 <= mouse[1] <= 550:
+ return True
+ elif 500 <= mouse[0] <= 700 and 600 <= mouse[1] <= 700:
+ return False
+ break
+ pygame.display.update()
+
+ # catch quit event
+ while True:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return
+
+#similar code, just for a loss case
+def display_lose_screen():
+ restart_text = font.render('PLAY AGAIN', True, WHITE)
+ quit_text = font.render('QUIT', True, WHITE)
+
+ screen.fill(BACKGROUND)
+ screen.blit(
+ font.render("You lose!", True, WHITE),
+ (500, 350),
+ )
+ screen.blit(
+ font.render("You had no possible moves...", True, WHITE),
+ (500, 400),
+ )
+
+ while True:
+ mouse = pygame.mouse.get_pos()
+ pygame.draw.rect(screen,BLACK,[500,450,200,100])
+ pygame.draw.rect(screen,BLACK,[500,600,200,100])
+ screen.blit(restart_text, (550, 475))
+ screen.blit(quit_text, (570, 625))
+ for i in pygame.event.get():
+ if i.type == pygame.MOUSEBUTTONDOWN:
+ if 500 <= mouse[0] <= 700 and 450 <= mouse[1] <= 550:
+ return True
+ elif 500 <= mouse[0] <= 700 and 600 <= mouse[1] <= 700:
+ return False
+ break
+ pygame.display.update()
+
+ # catch quit event
+ while True:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return
+
+#gets the reward for a certain action
+def get_reward(action):
+ if action == Action.move:
+ return 10
+ elif action == Action.heal:
+ return 1000
+ elif action == Action.kill:
+ return 100
+ elif action == Action.bite:
+ return -100
+
+def direction(coord1: Tuple[int, int], coord2: Tuple[int, int]):
+ if coord1 == coord2:
+ return Direction.self
+ elif coord2[1] > coord1[1]:
+ return Direction.down
+ elif coord2[1] < coord1[1]:
+ return Direction.up
+ elif coord2[0] > coord1[0]:
+ return Direction.right
+ elif coord2[0] < coord1[0]:
+ return Direction.left
diff --git a/README.md b/README.md
index 17b4fdf..331f4ec 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# SGAI - Outbreak
+# SGAI - DRHAT - Outbreak
This is the repository that Beaverworks' SGAI 2022 will be using to understand
serious games and reinforcement learning.
-## Team Courtney II
+## Team Courtney I - DR. HAT!
The best team out there.
## How to run
@@ -12,19 +12,24 @@ You must open the folder SGAI_MK3 with vscode. Then, you can
run main.py from VS Code.
### cmd line version
First, `cd ./SGAI_MK3`. Then, `python main.py`
+### PyCharm version
+Download the zip file and open SGAI-DRHAT-Outbreak. Ensure that numpy and pygame are installed, then run main.py.
## How to play
+This game implements a KILL/CURE option, where you choose if you want to kill a zombie or cure it.
+Pay attention to the public outrage and public anxiety, as if they get too high, you lose.
There are basic moves:
- Move - click on a person that you control and a square next to them.
If the square isn't occupied, the person will move to that square.
-- Bite - If you are playing as a zombie,
-you can click the bite button and then a person next to a zombie
-to turn the person into a zombie. NOTE: THIS DOES NOT ALWAYS SUCCEED
-BECAUSE GAME MECHANICS MAKE IT SO THAT THERE IS A CHANCE THAT BITING WILL
-FAIL. THIS IS NOT A BUG. THIS IS A GAME MECHANIC.
+- Bite - If you are playing as a zombie,
+you can click the bite button and a zombie and then a person.
+to turn the person into a zombie.
- Heal - If you are playing as the government, you
-can click the cure button and a person or zombie.
-If you clicked a zombie, the zombie will become a person again
-(this is curing). If you clicked a person, the person will become
-vaccinated (this is vaccination). Vaccination lasts for 5 turns and
-gives 100% immunity to being zombified.
\ No newline at end of file
+can click the cure button and a person (the healer) and then a zombie (the healee).
+There is 50% chance that the zombie will be healed and become a person again
+(this is curing). If the zombie is not healed, the healer may become a zombie.
+If they are healed, they now have 100% immunity to being zombified.
+- Kill - If you are playing as the government, you can click the kill button and a person (the killer) and then a zombie (the victim).
+Killing ensures that the zombie goes away with no chance of infection, however, it spikes the public's outrage, so choose carefully.
+- Vaccinate - In order to vaccinate a human, you must move into a "safe space". Safe spaces are slate blue on the board. Clicking on the red plus symbol will vaccinate everyone in a safe space. There can be up to two safe spaces, and up to one person in each safe space.
+
diff --git a/SGAI_MK3/.DS_Store b/SGAI_MK3/.DS_Store
deleted file mode 100644
index 59beab6..0000000
Binary files a/SGAI_MK3/.DS_Store and /dev/null differ
diff --git a/SGAI_MK3/Assets/bite.png b/SGAI_MK3/Assets/bite.png
deleted file mode 100644
index 5fa384a..0000000
Binary files a/SGAI_MK3/Assets/bite.png and /dev/null differ
diff --git a/SGAI_MK3/Assets/cure.jpeg b/SGAI_MK3/Assets/cure.jpeg
deleted file mode 100644
index b50956d..0000000
Binary files a/SGAI_MK3/Assets/cure.jpeg and /dev/null differ
diff --git a/SGAI_MK3/Assets/person_normal.png b/SGAI_MK3/Assets/person_normal.png
deleted file mode 100644
index 2309f02..0000000
Binary files a/SGAI_MK3/Assets/person_normal.png and /dev/null differ
diff --git a/SGAI_MK3/Assets/person_zombie.png b/SGAI_MK3/Assets/person_zombie.png
deleted file mode 100644
index 3701546..0000000
Binary files a/SGAI_MK3/Assets/person_zombie.png and /dev/null differ
diff --git a/SGAI_MK3/Board.py b/SGAI_MK3/Board.py
deleted file mode 100644
index d041d30..0000000
--- a/SGAI_MK3/Board.py
+++ /dev/null
@@ -1,362 +0,0 @@
-from State import State
-import random as rd
-from Person import Person
-from typing import List, Tuple
-from constants import *
-
-
-class Board:
- def __init__(
- self,
- dimensions: Tuple[int, int],
- player_role: str,
- ):
- self.rows = dimensions[0]
- self.columns = dimensions[1]
- self.player_role = player_role
- self.player_num = ROLE_TO_ROLE_NUM[player_role]
- self.population = 0
- self.States = []
- self.QTable = []
- for s in range(dimensions[0] * dimensions[1]):
- self.States.append(State(None, s))
- self.QTable.append([0] * 6)
-
- self.actionToFunction = {
- "moveUp": self.moveUp,
- "moveDown": self.moveDown,
- "moveLeft": self.moveLeft,
- "moveRight": self.moveRight,
- "heal": self.heal,
- "bite": self.bite,
- }
-
- def num_zombies(self) -> int:
- r = 0
- for state in self.States:
- if state.person != None:
- if state.person.isZombie:
- r += 1
- return r
-
- def act(self, oldstate: Tuple[int, int], givenAction: str):
- cell = self.toCoord(oldstate)
- f = self.actionToFunction[givenAction](cell)
- reward = self.States[oldstate].evaluate(givenAction, self)
- if f[0] == False:
- reward = 0
- return [reward, f[1]]
-
- def containsPerson(self, isZombie: bool):
- for state in self.States:
- if state.person is not None and state.person.isZombie == isZombie:
- return True
- return False
-
- def get_possible_moves(self, action: str, role: str):
- """
- Get the coordinates of people (or zombies) that are able
- to make the specified move.
- @param action - the action to return possibilities for (options are 'bite', 'moveUp', 'moveDown','moveLeft', 'moveRight', and 'heal')
- @param role - either 'Zombie' or 'Government'; helps decide whether an action
- is valid and which people/zombies it applies to
- """
- poss = []
- B = self.clone(self.States, role)
-
- if role == "Zombie":
- if not self.containsPerson(True):
- return poss
- for idx in range(len(self.States)):
- state = self.States[idx]
- if state.person is not None:
- changed_states = False
-
- if (
- action == "bite"
- and not state.person.isZombie
- and self.isAdjacentTo(self.toCoord(idx), True)
- ):
- # if the current space isn't a zombie and it is adjacent
- # a space that is a zombie
- poss.append(B.toCoord(idx))
- changed_states = True
- elif (
- action != "bite"
- and state.person.isZombie
- and B.actionToFunction[action](B.toCoord(idx))[0]
- ):
- poss.append(B.toCoord(idx))
- changed_states = True
-
- if changed_states:
- # reset the states
- B.States = [
- self.States[i].clone()
- if self.States[i] != B.States[i]
- else B.States[i]
- for i in range(len(self.States))
- ]
-
- elif role == "Government":
- if not self.containsPerson(False):
- return poss
- for idx in range(len(self.States)):
- state = self.States[idx]
- if state.person is not None:
- changed_states = False
- if action == "heal" and (
- state.person.isZombie or not state.person.isVaccinated
- ):
- poss.append(B.toCoord(idx))
- changed_states = True
- elif (
- action != "heal"
- and not state.person.isZombie
- and B.actionToFunction[action](B.toCoord(idx))[0]
- ):
- poss.append(B.toCoord(idx))
- changed_states = True
-
- if changed_states:
- # reset the states
- B.States = [
- self.States[i].clone()
- if self.States[i] != B.States[i]
- else B.States[i]
- for i in range(len(self.States))
- ]
- return poss
-
- def toCoord(self, i: int):
- return (int(i % self.columns), int(i / self.rows))
-
- def toIndex(self, coordinates: Tuple[int, int]):
- return int(coordinates[1] * self.columns) + int(coordinates[0])
-
- def isValidCoordinate(self, coordinates: Tuple[int, int]):
- return (
- coordinates[1] < self.rows
- and coordinates[1] >= 0
- and coordinates[0] < self.columns
- and coordinates[0] >= 0
- )
-
- def clone(self, L: List[State], role: str):
- NB = Board(
- (self.rows, self.columns),
- self.player_role,
- )
- NB.States = [state.clone() for state in L]
- NB.player_role = role
- return NB
-
- def isAdjacentTo(self, coord: Tuple[int, int], is_zombie: bool) -> bool:
- ret = False
- vals = [
- (coord[0], coord[1] + 1),
- (coord[0], coord[1] - 1),
- (coord[0] + 1, coord[1]),
- (coord[0] - 1, coord[1]),
- ]
- for coord in vals:
- if (
- self.isValidCoordinate(coord)
- and self.States[self.toIndex(coord)].person is not None
- and self.States[self.toIndex(coord)].person.isZombie == is_zombie
- ):
- ret = True
- break
-
- return ret
-
- def move(
- self, from_coords: Tuple[int, int], new_coords: Tuple[int, int]
- ) -> Tuple[bool, int]:
- """
- Check if the move is valid.
- If valid, then implement the move and return [True, destination_idx]
- If invalid, then return [False, None]
- If the space is currently occupied, then return [False, destination_idx]
- """
- # Get the start and destination index (1D)
- start_idx = self.toIndex(from_coords)
- destination_idx = self.toIndex(new_coords)
-
- # Check if the new coordinates are valid
- if not self.isValidCoordinate(new_coords):
- return [False, destination_idx]
-
- # Check if the destination is currently occupied
- if self.States[destination_idx].person is None:
- self.States[destination_idx].person = self.States[start_idx].person
- self.States[start_idx].person = None
- return [True, destination_idx]
- return [False, destination_idx]
-
- def moveUp(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- new_coords = (coords[0], coords[1] - 1)
- return self.move(coords, new_coords)
-
- def moveDown(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- new_coords = (coords[0], coords[1] + 1)
- return self.move(coords, new_coords)
-
- def moveLeft(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- new_coords = (coords[0] - 1, coords[1])
- return self.move(coords, new_coords)
-
- def moveRight(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- new_coords = (coords[0] + 1, coords[1])
- return self.move(coords, new_coords)
-
- def QGreedyat(self, state_id: int):
- biggest = self.QTable[state_id][0] * self.player_num
- ind = 0
- A = self.QTable[state_id]
- i = 0
- for qval in A:
- if (qval * self.player_num) > biggest:
- biggest = qval
- ind = i
- i += 1
- return [ind, self.QTable[ind]] # action_index, qvalue
-
- def choose_action(self, state_id: int, lr: float):
- L = lr * 100
- r = rd.randint(0, 100)
- if r < L:
- return self.QGreedyat(state_id)
- else:
- if self.player_num == 1: # Player is Govt
- d = rd.randint(0, 4)
- else:
- d = rd.randint(0, 5)
- while d != 4:
- d = rd.randint(0, 4)
- return d
-
- def choose_state(self, lr: float):
- L = lr * 100
- r = rd.randint(0, 100)
- if r < L:
- biggest = None
- sid = None
- for x in range(len(self.States)):
- if self.States[x].person != None:
- q = self.QGreedyat(x)
- if biggest is None:
- biggest = q[1]
- sid = x
- elif q[1] > biggest:
- biggest = q[1]
- sid = x
- return self.QGreedyat(sid)
- else:
- if self.player_num == -1: # Player is Govt
- d = rd.randint(0, len(self.States))
- while self.States[d].person is None or self.States[d].person.isZombie:
- d = rd.randint(0, len(self.States))
- else:
- d = rd.randint(0, len(self.States))
- while (
- self.States[d].person is None
- or self.States[d].person.isZombie == False
- ):
- d = rd.randint(0, len(self.States))
- return d
-
- def bite(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- i = self.toIndex(coords)
- if (
- self.States[i].person is None
- or self.States[i].person.isZombie
- or not self.isAdjacentTo(coords, True)
- ):
- return [False, None]
- self.States[i].person.get_bitten()
- return [True, i]
-
- def heal(self, coords: Tuple[int, int]) -> Tuple[bool, int]:
- """
- Cures or vaccinates the person at the stated coordinates.
- If there is a zombie there, the person will be cured.
- If there is a person there, the person will be vaccinated
- If no person is selected, then return [False, None]
- if a person is vaccined, then return [True, index]
- """
- i = self.toIndex(coords)
- if self.States[i].person is None:
- return [False, None]
- p = self.States[i].person
-
- if p.isZombie:
- p.get_cured()
- else:
- p.get_vaccinated()
- return [True, i]
-
- def get_possible_states(self, role_number: int):
- indexes = []
- i = 0
- for state in self.States:
- if state.person != None:
- if role_number == 1 and state.person.isZombie == False:
- indexes.append(i)
- elif role_number == -1 and state.person.isZombie:
- indexes.append(i)
- i += 1
- return indexes
-
- def step(self, role_number: int, learningRate: float):
- P = self.get_possible_states(role_number)
- r = rd.uniform(0, 1)
- if r < learningRate:
- rs = rd.randrange(0, len(self.States) - 1)
- if role_number == 1:
- while (
- self.States[rs].person is not None
- and self.States[rs].person.isZombie
- ):
- rs = rd.randrange(0, len(self.States) - 1)
- else:
- while (
- self.States[rs].person is not None
- and self.States[rs].person.isZombie == False
- ):
- rs = rd.randrange(0, len(self.States) - 1)
-
- # random state and value
- # old_value = QTable[state][acti]
- # next_max = np.max(QTable[next_state])
- # new_value = (1 - alpha) * old_value + alpha * (reward + gamma * next_max)
- # QTable[state][acti] = new_value
-
- def populate(self):
- total = rd.randint(7, ((self.rows * self.columns) / 3))
- poss = []
- for x in range(len(self.States)):
- r = rd.randint(0, 100)
- if r < 60 and self.population < total:
- p = Person(False)
- self.States[x].person = p
- self.population = self.population + 1
- poss.append(x)
- else:
- self.States[x].person = None
- used = []
- for x in range(4):
- s = rd.randint(0, len(poss) - 1)
- while s in used:
- s = rd.randint(0, len(poss) - 1)
- self.States[poss[s]].person.isZombie = True
- used.append(s)
-
- def update(self):
- """
- Update each of the states;
- This method should be called at the end of each round
- (after player and computer have each gone once)
- """
- for state in self.States:
- state.update()
diff --git a/SGAI_MK3/PygameFunctions.py b/SGAI_MK3/PygameFunctions.py
deleted file mode 100644
index 328cde1..0000000
--- a/SGAI_MK3/PygameFunctions.py
+++ /dev/null
@@ -1,245 +0,0 @@
-from typing import List, Tuple
-import pygame
-from constants import *
-from Board import Board
-
-
-# Initialize pygame
-screen = pygame.display.set_mode(GAME_WINDOW_DIMENSIONS)
-pygame.display.set_caption("Outbreak!")
-pygame.font.init()
-font = pygame.font.SysFont("Comic Sans", 20)
-screen.fill(BACKGROUND)
-
-
-def get_action(GameBoard: Board, pixel_x: int, pixel_y: int):
- """
- Get the action that the click represents.
- If the click was on the heal button, returns "heal"
- Else, returns the board coordinates of the click (board_x, board_y) if valid
- Return None otherwise
- """
- # Check if the user clicked on the "heal" or "bite" icon, return "heal" or "bite" if so
- heal_bite_check = (
- pixel_x >= CURE_BITE_COORDS[0]
- and pixel_x <= CURE_BITE_COORDS[0] + CURE_BITE_DIMS[0]
- and pixel_y >= CURE_BITE_COORDS[1]
- and pixel_y <= CURE_BITE_COORDS[1] + CURE_BITE_DIMS[1]
- )
- reset_move_check = (
- pixel_x >= RESET_MOVE_COORDS[0]
- and pixel_x <= RESET_MOVE_COORDS[0] + RESET_MOVE_DIMS[0]
- and pixel_y >= RESET_MOVE_COORDS[1]
- and pixel_y <= RESET_MOVE_COORDS[1] + RESET_MOVE_DIMS[1]
- )
- board_x = int((pixel_x - MARGIN) / CELL_DIMENSIONS[0])
- board_y = int((pixel_y - MARGIN) / CELL_DIMENSIONS[1])
- move_check = (
- board_x >= 0
- and board_x < GameBoard.columns
- and board_y >= 0
- and board_y < GameBoard.rows
- )
-
- if heal_bite_check:
- if GameBoard.player_role == "Government":
- return "heal"
- return "bite"
- elif reset_move_check:
- return "reset move"
- elif move_check:
- return board_x, board_y
- return None
-
-
-def run(GameBoard: Board):
- """
- Draw the screen and return any events.
- """
- screen.fill(BACKGROUND)
- build_grid(GameBoard) # Draw the grid
- # Draw the heal icon
- if GameBoard.player_role == "Government":
- display_image(screen, "Assets/cure.jpeg", CURE_BITE_DIMS, CURE_BITE_COORDS)
- else:
- display_image(screen, "Assets/bite.png", CURE_BITE_DIMS, CURE_BITE_COORDS)
- display_people(GameBoard)
- display_reset_move_button()
- return pygame.event.get()
-
-
-def display_reset_move_button():
- rect = pygame.Rect(
- RESET_MOVE_COORDS[0],
- RESET_MOVE_COORDS[1],
- RESET_MOVE_DIMS[0],
- RESET_MOVE_DIMS[1],
- )
- pygame.draw.rect(screen, BLACK, rect)
- screen.blit(font.render("Reset move?", True, WHITE), RESET_MOVE_COORDS)
-
-
-def display_image(
- screen: pygame.Surface,
- itemStr: str,
- dimensions: Tuple[int, int],
- position: Tuple[int, int],
-):
- """
- Draw an image on the screen at the indicated position.
- """
- v = pygame.image.load(itemStr).convert_alpha()
- v = pygame.transform.scale(v, dimensions)
- screen.blit(v, position)
-
-
-def build_grid(GameBoard: Board):
- """
- Draw the grid on the screen.
- """
- grid_width = GameBoard.columns * CELL_DIMENSIONS[0]
- grid_height = GameBoard.rows * CELL_DIMENSIONS[1]
- # left
- pygame.draw.rect(
- screen,
- BLACK,
- [
- MARGIN - LINE_WIDTH,
- MARGIN - LINE_WIDTH,
- LINE_WIDTH,
- grid_height + (2 * LINE_WIDTH),
- ],
- )
- # right
- pygame.draw.rect(
- screen,
- BLACK,
- [
- MARGIN + grid_width,
- MARGIN - LINE_WIDTH,
- LINE_WIDTH,
- grid_height + (2 * LINE_WIDTH),
- ],
- )
- # bottom
- pygame.draw.rect(
- screen,
- BLACK,
- [
- MARGIN - LINE_WIDTH,
- MARGIN + grid_height,
- grid_width + (2 * LINE_WIDTH),
- LINE_WIDTH,
- ],
- )
- # top
- pygame.draw.rect(
- screen,
- BLACK,
- [
- MARGIN - LINE_WIDTH,
- MARGIN - LINE_WIDTH,
- grid_width + (2 * LINE_WIDTH),
- LINE_WIDTH,
- ],
- )
- # Fill the inside wioth the cell color
- pygame.draw.rect(
- screen,
- CELL_COLOR,
- [MARGIN, MARGIN, grid_width, grid_height],
- )
-
- # Draw the vertical lines
- i = MARGIN + CELL_DIMENSIONS[0]
- while i < MARGIN + grid_width:
- pygame.draw.rect(screen, BLACK, [i, MARGIN, LINE_WIDTH, grid_height])
- i += CELL_DIMENSIONS[0]
- # Draw the horizontal lines
- i = MARGIN + CELL_DIMENSIONS[1]
- while i < MARGIN + grid_height:
- pygame.draw.rect(screen, BLACK, [MARGIN, i, grid_width, LINE_WIDTH])
- i += CELL_DIMENSIONS[1]
-
-
-def display_people(GameBoard: Board):
- """
- Draw the people (government, vaccinated, and zombies) on the grid.
- """
- for x in range(len(GameBoard.States)):
- if GameBoard.States[x].person != None:
- p = GameBoard.States[x].person
- char = "Assets/" + IMAGE_ASSETS[0]
- if p.isVaccinated:
- char = "Assets/" + IMAGE_ASSETS[1]
- elif p.isZombie:
- char = "Assets/" + IMAGE_ASSETS[2]
- coords = (
- int(x % GameBoard.rows) * CELL_DIMENSIONS[0] + MARGIN + 35,
- int(x / GameBoard.columns) * CELL_DIMENSIONS[1] + MARGIN + 20,
- )
- display_image(screen, char, (35, 60), coords)
-
-
-def display_cur_move(cur_move: List):
- # Display the current action
- screen.blit(
- font.render("Your move is currently:", True, WHITE),
- CUR_MOVE_COORDS,
- )
- screen.blit(
- font.render(f"{cur_move}", True, WHITE),
- (
- CUR_MOVE_COORDS[0],
- CUR_MOVE_COORDS[1] + font.size("Your move is currently:")[1] * 2,
- ),
- )
-
-
-def display_win_screen():
- screen.fill(BACKGROUND)
- screen.blit(
- font.render("You win!", True, WHITE),
- (500, 350),
- )
- screen.blit(
- font.render("There were no possible moves for the computer.", True, WHITE),
- (500, 400),
- )
- pygame.display.update()
-
- # catch quit event
- while True:
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- return
-
-
-def display_lose_screen():
- screen.fill(BACKGROUND)
- screen.blit(
- font.render("You lose!", True, WHITE),
- (500, 350),
- )
- screen.blit(
- font.render("You had no possible moves...", True, WHITE),
- (500, 400),
- )
- pygame.display.update()
-
- # catch quit event
- while True:
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- return
-
-
-def direction(coord1: Tuple[int, int], coord2: Tuple[int, int]):
- if coord2[1] > coord1[1]:
- return "moveDown"
- elif coord2[1] < coord1[1]:
- return "moveUp"
- elif coord2[0] > coord1[0]:
- return "moveRight"
- elif coord2[0] < coord1[0]:
- return "moveLeft"
diff --git a/SGAI_MK3/constants.py b/SGAI_MK3/constants.py
deleted file mode 100644
index 52440c6..0000000
--- a/SGAI_MK3/constants.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Constants
-ROWS = 6
-COLUMNS = 6
-ACTION_SPACE = ["moveUp", "moveDown", "moveLeft", "moveRight", "heal", "bite"]
-
-# Player role variables
-ROLE_TO_ROLE_NUM = {"Government": 1, "Zombie": -1}
-ROLE_TO_ROLE_BOOLEAN = {"Government": False, "Zombie": True}
-
-# Pygame constants
-BACKGROUND = "#DDC2A1"
-BLACK = (0, 0, 0)
-WHITE = (255, 255, 255)
-CELL_COLOR = (233, 222, 188)
-LINE_WIDTH = 5
-IMAGE_ASSETS = [
- "person_normal.png",
- "person_vax.png",
- "person_zombie.png",
-]
-GAME_WINDOW_DIMENSIONS = (1200, 800)
-RESET_MOVE_COORDS = (800, 600)
-RESET_MOVE_DIMS = (200, 50)
-CURE_BITE_COORDS = (950, 200)
-CURE_BITE_DIMS = (200, 200)
-CELL_DIMENSIONS = (100, 100) # number of pixels (x, y) for each cell
-CUR_MOVE_COORDS = (800, 400)
-MARGIN = 150 # Number of pixels to offset grid to the top-left side
diff --git a/SGAI_MK3/main.py b/SGAI_MK3/main.py
index 49990b4..e0e8b15 100644
--- a/SGAI_MK3/main.py
+++ b/SGAI_MK3/main.py
@@ -1,13 +1,21 @@
+#from msilib.schema import Class
import pygame
from Board import Board
import PygameFunctions as PF
import random as rd
from constants import *
+import time
+
+# Player role variables
+player_role = Role.government # Valid options are Role.government and Role.zombie
+roleToRoleNum = {Role.government: 1, Role.zombie: -1}
+
+#initialize sound effect
+
+
-SELF_PLAY = True # whether or not a human will be playing
-player_role = "Zombie" # Valid options are "Government" and "Zombie"
# Create the game board
-GameBoard = Board((ROWS, COLUMNS), player_role)
+GameBoard = Board((ROWS, COLUMNS), BORDER, CELL_DIMENSIONS, player_role)
GameBoard.populate()
# Self play variables
@@ -23,90 +31,110 @@
running = True
take_action = []
playerMoved = False
+font = pygame.font.SysFont("Comic Sans", 20)
+
while running:
P = PF.run(GameBoard)
-
if SELF_PLAY:
- if not playerMoved:
- if not GameBoard.containsPerson(False):
- PF.display_lose_screen()
- running = False
- continue
- # Event Handling
- for event in P:
- if event.type == pygame.MOUSEBUTTONUP:
- x, y = pygame.mouse.get_pos()
- action = PF.get_action(GameBoard, x, y)
- if action == "heal" or action == "bite":
- # only allow healing by itself (prevents things like ['move', (4, 1), 'heal'])
- if len(take_action) == 0:
- take_action.append(action)
- elif action == "reset move":
- take_action = []
- elif action is not None:
- idx = GameBoard.toIndex(action)
- # action is a coordinate
- if idx < (GameBoard.rows * GameBoard.columns) and idx > -1:
- if "move" not in take_action and len(take_action) == 0:
- # make sure that the space is not an empty space or a space of the opposite team
- # since cannot start a move from those invalid spaces
- if (
- GameBoard.States[idx].person is not None
- and GameBoard.States[idx].person.isZombie
- == ROLE_TO_ROLE_BOOLEAN[player_role]
- ):
- take_action.append("move")
- else:
- continue
-
- # don't allow duplicate cells
- if action not in take_action:
- take_action.append(action)
- if event.type == pygame.QUIT:
- running = False
-
- PF.display_cur_move(take_action)
-
- # Action handling
- if len(take_action) > 1:
- if take_action[0] == "move":
- if len(take_action) > 2:
- directionToMove = PF.direction(take_action[1], take_action[2])
- result = GameBoard.actionToFunction[directionToMove](
- take_action[1]
- )
- if result[0] is not False:
- playerMoved = True
- take_action = []
-
- elif take_action[0] == "heal" or take_action[0] == "bite":
- result = GameBoard.actionToFunction[take_action[0]](take_action[1])
- if result[0] is not False:
- playerMoved = True
+ if not GameBoard.containsPerson(bool(player_role.value)):
+ PF.display_lose_screen()
+ running = False
+ continue
+ # Event Handling
+ for event in P:
+ if event.type == pygame.MOUSEBUTTONUP:
+ x, y = pygame.mouse.get_pos()
+ action = PF.get_action(GameBoard, x, y)
+
+ if(
+ type(action) == Action
+ and len(take_action) == 0
+ ):
+ # only allow healing by itself (prevents things like ['move', (4, 1), 'heal'])
+ take_action.append(action)
+
+ elif action == "reset move":
take_action = []
+
+ elif type(action) is tuple:
+ idx = GameBoard.toIndex(action)
+ # action is a coordinate
+ if idx < (GameBoard.rows * GameBoard.columns) and idx > -1:
+ if Action.move not in take_action and len(take_action) == 0:
+ # make sure that the space is not an empty space or a space of the opposite team
+ # since cannot start a move from those invalid spaces
+ if (
+ GameBoard.States[idx].person is not None
+ and GameBoard.States[idx].person.isZombie
+ == bool(player_role.value)
+ ):
+ take_action.append(Action.move)
+ else:
+ continue
+
+ # don't allow duplicate cells
+ if action not in take_action:
+ take_action.append(action)
+ if event.type == pygame.QUIT:
+ running = False
- # Computer turn
- else:
+ # Display the current action
+ PF.screen.blit(
+ font.render("Your move is currently:", True, PF.WHITE),
+ (800, 400),
+ )
+ PF.screen.blit(font.render(f"{take_action}", True, PF.WHITE), (800, 450))
+
+ # Action handling
+ if len(take_action) > 2:
+ directionToMove = PF.direction(take_action[1], take_action[2])
+ print("Implementing", take_action[0], "to", directionToMove)
+ result = GameBoard.actionToFunction[take_action[0]](take_action[1], directionToMove)
+ print(f"did it succeed? {result[0]}")
+ if result[0] is not False:
+ playerMoved = True
+ take_action = []
+
+ if playerMoved:
+ # Intermission
+ PF.run(GameBoard)
+ pygame.display.update()
+ time.sleep(0.1)
+ print("Enemy turn")
+
+ # Computer turn
playerMoved = False
take_action = []
- # Make a list of all possible actions that the computer can take
- possible_actions = [
- ACTION_SPACE[i]
- for i in range(6)
- if (i != 4 and player_role == "Government")
- or (i != 5 and player_role == "Zombie")
- ]
+ if player_role == Role.government:
+ possible_actions = [Action.move, Action.bite]
+ computer_role = Role.zombie
+ else:
+ possible_actions = [Action.move, Action.heal, Action.kill]
+ computer_role = Role.government
+
possible_move_coords = []
+ #Cycles through actions
while len(possible_move_coords) == 0 and len(possible_actions) != 0:
- action = possible_actions.pop(rd.randint(0, len(possible_actions) - 1))
- possible_move_coords = GameBoard.get_possible_moves(
- action, "Government" if player_role == "Zombie" else "Zombie"
- )
+ possible_direction = [member for name, member in Direction.__members__.items()]
+ action = rd.choice(possible_actions)
+ #cycles through directions
+ while len(possible_move_coords) == 0 and len(possible_direction) != 0:
+ direction = rd.choice(possible_direction)
+ possible_direction.remove(direction)
+ possible_move_coords = GameBoard.get_possible_moves(
+ action, direction, computer_role
+ )
+ possible_actions.remove(action)
+ print("possible actions is", possible_actions)
# no valid moves, player wins
- if len(possible_actions) == 0 and len(possible_move_coords) == 0:
+ if (
+ len(possible_actions) == 0
+ and len(possible_direction) == 0
+ and len(possible_move_coords) == 0
+ ):
PF.display_win_screen()
running = False
continue
@@ -115,14 +143,15 @@
move_coord = rd.choice(possible_move_coords)
# Implement the selected action
- GameBoard.actionToFunction[action](move_coord)
+ print("action chosen is", action)
+ print("move start coord is", move_coord)
+
+ GameBoard.actionToFunction[action](move_coord, direction)
- # update the board's states
- GameBoard.update()
+ print("stopping")
# Update the display
pygame.display.update()
- pygame.time.wait(75)
else:
if epochs_ran % 100 == 0:
@@ -145,10 +174,10 @@
if biggest is None:
biggest = exp
i = x
- elif biggest < exp and player_role == "Government":
+ elif biggest < exp and player_role == Role.government:
biggest = exp
i = x
- elif biggest > exp and player_role != "Government":
+ elif biggest > exp and player_role != Role.government:
biggest = exp
i = x
state = GameBoard.QTable[i]
@@ -156,10 +185,10 @@
j = 0
ind = 0
for v in state:
- if v > b and player_role == "Government":
+ if v > b and player_role == Role.government:
b = v
ind = j
- elif v < b and player_role != "Government":
+ elif v < b and player_role != Role.government:
b = v
ind = j
j += 1
@@ -180,7 +209,7 @@
take_action = []
print("Enemy turn")
ta = ""
- if player_role == "Government":
+ if player_role == Role.government:
r = rd.randint(0, 5)
while r == 4:
r = rd.randint(0, 5)
@@ -188,7 +217,7 @@
else:
r = rd.randint(0, 4)
ta = ACTION_SPACE[r]
- poss = GameBoard.get_possible_moves(ta, "Zombie")
+ poss = GameBoard.get_possible_moves(ta, Role.zombie)
if len(poss) > 0:
r = rd.randint(0, len(poss) - 1)
diff --git a/SGAI_MK3/State.py b/State.py
similarity index 60%
rename from SGAI_MK3/State.py
rename to State.py
index 48a8cf9..eb98789 100644
--- a/SGAI_MK3/State.py
+++ b/State.py
@@ -1,24 +1,18 @@
-from typing import Tuple
from Person import Person
-import math
-
class State:
- def __init__(self, p: Person, i) -> None:
+ def __init__(self, p: Person, i, safeSpace = False) -> None:
self.person = p
self.location = i
+ self.safeSpace = safeSpace
pass
- def distance(self, GameBoard, other_location: int):
- first_coord = GameBoard.toCoord(self.location)
- second_coord = GameBoard.toCoord(other_location)
- a = second_coord[0] - first_coord[0]
- b = second_coord[1] - first_coord[1]
- a = a * a
- b = b * a
- return math.pow(int(a + b), 0.5)
+ def distance(self, other_id): # gets the distance between two states
+ first_coord = self.toCoord(self.location)
+ second_coord = self.toCoord(other_id)
+ return (float)((second_coord[1] - first_coord[1])**2 + (second_coord[0] - first_coord[0])**2)**0.5
- def nearest_zombie(self, GameBoard):
+ def nearest_zombie(self, GameBoard): #pretty self explanatory
smallest_dist = 100
for state in GameBoard.States:
if state.person != None:
@@ -28,7 +22,7 @@ def nearest_zombie(self, GameBoard):
smallest_dist = d
return smallest_dist
- def evaluate(self, action: str, GameBoard):
+ def evaluate(self, action: str, GameBoard): # decides on the reward for a specific action based on what the board is like (for q learning)
reward = 0
reward += self.nearest_zombie(GameBoard) - 3
if action == "heal":
@@ -42,18 +36,19 @@ def evaluate(self, action: str, GameBoard):
reward = reward + int(5 * (2 + chance))
return reward
- def adjacent(self, GameBoard):
+ def adjacent(self, GameBoard): # returns the four adjacent boxes that are in bounds
newCoord = GameBoard.toCoord(self.location)
- moves = [
+ print(newCoord)
+ moves = [ #puts all four adjacent locations into moves
(newCoord[0], newCoord[1] - 1),
(newCoord[0], newCoord[1] + 1),
(newCoord[0] - 1, newCoord[1]),
(newCoord[0] + 1, newCoord[1]),
]
- remove = []
+ remove = [] #creates the ones to remove
for i in range(4):
move = moves[i]
- if (
+ if ( #removes all illigal options
move[0] < 0
or move[0] > GameBoard.columns
or move[1] < 0
@@ -65,23 +60,15 @@ def adjacent(self, GameBoard):
moves.pop(r)
return moves
- def clone(self):
+ def clone(self): #clones the state (for the purpose of moving people and zombies)
if self.person is None:
return State(self.person, self.location)
return State(self.person.clone(), self.location)
- def __eq__(self, __o: object) -> bool:
+ def __eq__(self, __o: object) -> bool: # compares if two states are the same, not just the same person but also the same location
if type(__o) == State:
return self.person == __o.person and self.location == __o.location
return False
- def __ne__(self, __o: object) -> bool:
+ def __ne__(self, __o: object) -> bool: # same as over but not equals
return not self == __o
-
- def update(self):
- """
- If this has a person, update the person within.
- """
- if self.person is None:
- return
- self.person.update()
diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..aad47b0
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,41 @@
+import enum
+import pygame
+import os
+pygame.mixer.init()
+
+
+ROWS = 6
+COLUMNS = 6
+BORDER = 150 # Number of pixels to offset grid to the top-left side
+CELL_DIMENSIONS = (100, 100) # Number of pixels (x,y) for each cell
+SELF_PLAY = True # whether or not a human will be playing
+KILL_SOUND = pygame.mixer.Sound(os.path.join('Assets', 'Kill sound.mp3'))
+
+class Action(enum.Enum):
+ move = 1
+ bite = 2
+ heal = 3
+ kill = 4
+
+class Direction(enum.Enum):
+ self = 0
+ up = 1
+ down = 2
+ left = 3
+ right = 4
+
+class Role(enum.Enum):
+ government = 0
+ zombie = 1
+
+class Result(enum.Enum):
+ invalid = 0
+ success = 1
+ failure = 2
+
+reverse_dir = {
+ Direction.up: Direction.down,
+ Direction.down: Direction.up,
+ Direction.left: Direction.right,
+ Direction.right: Direction.left
+}
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..cdc5f8f
--- /dev/null
+++ b/main.py
@@ -0,0 +1,266 @@
+import pygame
+from Board import Board
+import PygameFunctions as PF
+import random as rd
+from constants import *
+import time
+
+# Player role variables
+player_role = Role.government # Valid options are Role.government and Role.zombie
+roleToRoleNum = {Role.government: 1, Role.zombie: -1}
+
+# Create the game board
+GameBoard = Board((ROWS, COLUMNS), BORDER, CELL_DIMENSIONS, player_role)
+GameBoard.populate()
+
+# Self play variables
+alpha = 0.1
+gamma = 0.6
+epsilon = 0.1
+epochs = 1000
+epochs_ran = 0
+Original_Board = GameBoard.clone(GameBoard.States, GameBoard.player_role)
+
+
+# Initialize variables
+running = True
+take_action = []
+playerMoved = False
+font = pygame.font.SysFont("Comic Sans", 20)
+start = False
+
+#Initial player score
+player_score = 0
+
+
+while running:
+ #displays the main menu until user hits start or quit
+ if start == False:
+ try:
+ start = PF.disp_title_screen()
+ #Throws exception when user quits program using in-game button
+ except pygame.error:
+ print("Game closed by user")
+ break
+ elif start == True:
+ P = PF.run(GameBoard)
+ if SELF_PLAY:
+ if(
+ not GameBoard.containsPerson(bool(player_role.value))
+ or GameBoard.outrage >= 100
+ ):
+ running = PF.display_lose_screen()
+ for state in GameBoard.States:
+ state.person = None
+ state.safeSpace = False
+ GameBoard.populate()
+ start = False
+ continue
+ # Event Handling
+ for event in P:
+ if event.type == pygame.MOUSEBUTTONUP:
+ x, y = pygame.mouse.get_pos()
+ action = PF.get_action(GameBoard, x, y)
+
+ if(action == "Distrb Med"):
+ take_action.append("Distrb Med")
+ time.sleep(0.1)
+ GameBoard.med()
+ take_action = []
+
+ elif(
+ type(action) == Action
+ and len(take_action) == 0
+ ):
+ # only allow healing by itself (prevents things like ['move', (4, 1), 'heal'])
+ take_action.append(action)
+
+ elif action == "reset move":
+ take_action = []
+
+ elif type(action) is tuple:
+ idx = GameBoard.toIndex(action)
+ # action is a coordinate
+ if idx < (GameBoard.rows * GameBoard.columns) and idx > -1:
+ if Action.move not in take_action and len(take_action) == 0:
+ # make sure that the space is not an empty space or a space of the opposite team
+ # since cannot start a move from those invalid spaces
+ if (
+ GameBoard.States[idx].person is not None
+ and GameBoard.States[idx].person.isZombie
+ == bool(player_role.value)
+ ):
+ take_action.append(Action.move)
+ else:
+ continue
+
+
+ take_action.append(action)
+ if event.type == pygame.QUIT:
+ running = False
+
+ # Display the current action
+ PF.screen.blit(
+ font.render("Your move is currently:", True, PF.WHITE),
+ (800, 400),
+ )
+ PF.screen.blit(font.render(f"{take_action}", True, PF.WHITE), (800, 450))
+
+
+
+ # Action handling
+ if len(take_action) > 2:
+ directionToMove = PF.direction(take_action[1], take_action[2])
+ print("Implementing", take_action[0], "to", directionToMove)
+ result = GameBoard.actionToFunction[take_action[0]](take_action[1], directionToMove)
+ print(f"did it succeed? {result}")
+
+ if result == Result.success:
+ player_score += PF.get_reward(take_action[0])
+ #if it succeeds, the player gets a reward corresponding to their action
+
+ if result != Result.invalid:
+ playerMoved = True
+ take_action = []
+ #Display the player's current score
+ PF.screen.blit(font.render("Score: " + str(player_score), True, PF.WHITE),(900,500))
+
+ if playerMoved:
+ # Intermission
+ PF.run(GameBoard)
+ pygame.display.update()
+ time.sleep(0.1)
+ print("Enemy turn")
+
+ # Computer turn
+ playerMoved = False
+ take_action = []
+
+ if player_role == Role.government:
+ possible_actions = [Action.move, Action.bite]
+ computer_role = Role.zombie
+ else:
+ possible_actions = [Action.move, Action.heal, Action.kill]
+ computer_role = Role.government
+
+ possible_move_coords = []
+ #Cycles through actions
+ while len(possible_move_coords) == 0 and len(possible_actions) != 0:
+ possible_direction = [member for name, member in Direction.__members__.items()]
+ action = rd.choice(possible_actions)
+ #cycles through directions
+ while len(possible_move_coords) == 0 and len(possible_direction) != 0:
+ direction = rd.choice(possible_direction)
+ possible_direction.remove(direction)
+ possible_move_coords = GameBoard.get_possible_moves(
+ action, direction, computer_role
+ )
+ possible_actions.remove(action)
+ print("possible actions is", possible_actions)
+
+ # no valid moves, player wins
+ #Displays two buttons and allows the player to play again on a new randomized map
+ if (
+ len(possible_actions) == 0
+ and len(possible_direction) == 0
+ and len(possible_move_coords) == 0
+ ):
+ running = PF.display_win_screen()
+ for state in GameBoard.States:
+ state.person = None
+ state.safeSpace = False
+ GameBoard.populate()
+ start = False
+ continue
+
+ # Select the destination coordinates
+ move_coord = rd.choice(possible_move_coords)
+
+ # Implement the selected action
+ print("action chosen is", action)
+ print("move start coord is", move_coord)
+ print(GameBoard.actionToFunction[action](move_coord, direction))
+ print("stopping")
+
+ # Update the display
+ pygame.display.update()
+ pygame.time.wait(75)
+
+ else:
+ if epochs_ran % 100 == 0:
+ print("Board Reset!")
+ GameBoard = Original_Board # reset environment
+ for event in P:
+ i = 0
+ r = rd.uniform(0.0, 1.0)
+ st = rd.randint(0, len(GameBoard.States) - 1)
+ state = GameBoard.QTable[st]
+
+ if r < gamma:
+ while GameBoard.States[st].person is None:
+ st = rd.randint(0, len(GameBoard.States) - 1)
+ else:
+ biggest = None
+ for x in range(len(GameBoard.States)):
+ arr = GameBoard.QTable[x]
+ exp = sum(arr) / len(arr)
+ if biggest is None:
+ biggest = exp
+ i = x
+ elif biggest < exp and player_role == Role.government:
+ biggest = exp
+ i = x
+ elif biggest > exp and player_role != Role.government:
+ biggest = exp
+ i = x
+ state = GameBoard.QTable[i]
+ b = 0
+ j = 0
+ ind = 0
+ for v in state:
+ if v > b and player_role == Role.government:
+ b = v
+ ind = j
+ elif v < b and player_role != Role.government:
+ b = v
+ ind = j
+ j += 1
+ action_to_take = ACTION_SPACE[ind]
+ old_qval = b
+ old_state = i
+
+ # Update
+ # Q(S, A) = Q(S, A) + alpha[R + gamma * max_a Q(S', A) - Q(S, A)]
+ reward = GameBoard.act(old_state, action_to_take)
+ ns = reward[1]
+ NewStateAct = GameBoard.QGreedyat(ns)
+ NS = GameBoard.QTable[ns][NewStateAct[0]]
+ # GameBoard.QTable[i] = GameBoard.QTable[i] + alpha * (reward[0] + gamma * NS) - GameBoard.QTable[i]
+ if GameBoard.num_zombies() == 0:
+ print("winCase")
+
+ take_action = []
+ print("Enemy turn")
+ ta = ""
+ if player_role == Role.government:
+ r = rd.randint(0, 5)
+ while r == 4:
+
+ r = rd.randint(0, 5)
+ while r == 4:
+ r = rd.randint(0, 5)
+ ta = ACTION_SPACE[r]
+ else:
+ r = rd.randint(0, 4)
+ ta = ACTION_SPACE[r]
+ poss = GameBoard.get_possible_moves(ta, Role.zombie)
+
+ if len(poss) > 0:
+ r = rd.randint(0, len(poss) - 1)
+ a = poss[r]
+ GameBoard.actionToFunction[ta](a)
+ if GameBoard.num_zombies() == GameBoard.population:
+ print("loseCase")
+ if event.type == pygame.QUIT:
+ running = False
+pygame.display.quit()