mirror of
https://github.com/peter-tanner/Algorithms-Agents-and-Artificial-Intelligence-project-final.git
synced 2024-11-30 09:00:17 +08:00
New repositiory containing only code and no report, supporting files or
files copyrighted like the project specification
This commit is contained in:
commit
522671e4c2
493
.gitignore
vendored
Normal file
493
.gitignore
vendored
Normal file
|
@ -0,0 +1,493 @@
|
|||
test/
|
||||
ZZZ_ignore/
|
||||
snapshots/
|
||||
*.bin
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
~.*.docx
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
## Core latex/pdflatex auxiliary files:
|
||||
*.aux
|
||||
*.lof
|
||||
*.log
|
||||
*.lot
|
||||
*.fls
|
||||
*.out
|
||||
*.toc
|
||||
*.fmt
|
||||
*.fot
|
||||
*.cb
|
||||
*.cb2
|
||||
.*.lb
|
||||
|
||||
## Intermediate documents:
|
||||
*.dvi
|
||||
*.xdv
|
||||
*-converted-to.*
|
||||
# these rules might exclude image files for figures etc.
|
||||
# *.ps
|
||||
# *.eps
|
||||
# *.pdf
|
||||
|
||||
## Generated if empty string is given at "Please type another file name for output:"
|
||||
.pdf
|
||||
|
||||
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.blg
|
||||
*-blx.aux
|
||||
*-blx.bib
|
||||
*.run.xml
|
||||
|
||||
## Build tool auxiliary files:
|
||||
*.fdb_latexmk
|
||||
*.synctex
|
||||
*.synctex(busy)
|
||||
*.synctex.gz
|
||||
*.synctex.gz(busy)
|
||||
*.pdfsync
|
||||
|
||||
## Build tool directories for auxiliary files
|
||||
# latexrun
|
||||
latex.out/
|
||||
|
||||
## Auxiliary and intermediate files from other packages:
|
||||
# algorithms
|
||||
*.alg
|
||||
*.loa
|
||||
|
||||
# achemso
|
||||
acs-*.bib
|
||||
|
||||
# amsthm
|
||||
*.thm
|
||||
|
||||
# beamer
|
||||
*.nav
|
||||
*.pre
|
||||
*.snm
|
||||
*.vrb
|
||||
|
||||
# changes
|
||||
*.soc
|
||||
|
||||
# comment
|
||||
*.cut
|
||||
|
||||
# cprotect
|
||||
*.cpt
|
||||
|
||||
# elsarticle (documentclass of Elsevier journals)
|
||||
*.spl
|
||||
|
||||
# endnotes
|
||||
*.ent
|
||||
|
||||
# fixme
|
||||
*.lox
|
||||
|
||||
# feynmf/feynmp
|
||||
*.mf
|
||||
*.mp
|
||||
*.t[1-9]
|
||||
*.t[1-9][0-9]
|
||||
*.tfm
|
||||
|
||||
#(r)(e)ledmac/(r)(e)ledpar
|
||||
*.end
|
||||
*.?end
|
||||
*.[1-9]
|
||||
*.[1-9][0-9]
|
||||
*.[1-9][0-9][0-9]
|
||||
*.[1-9]R
|
||||
*.[1-9][0-9]R
|
||||
*.[1-9][0-9][0-9]R
|
||||
*.eledsec[1-9]
|
||||
*.eledsec[1-9]R
|
||||
*.eledsec[1-9][0-9]
|
||||
*.eledsec[1-9][0-9]R
|
||||
*.eledsec[1-9][0-9][0-9]
|
||||
*.eledsec[1-9][0-9][0-9]R
|
||||
|
||||
# glossaries
|
||||
*.acn
|
||||
*.acr
|
||||
*.glg
|
||||
*.glo
|
||||
*.gls
|
||||
*.glsdefs
|
||||
*.lzo
|
||||
*.lzs
|
||||
*.slg
|
||||
*.slo
|
||||
*.sls
|
||||
|
||||
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
|
||||
# *.ist
|
||||
|
||||
# gnuplot
|
||||
*.gnuplot
|
||||
*.table
|
||||
|
||||
# gnuplottex
|
||||
*-gnuplottex-*
|
||||
|
||||
# gregoriotex
|
||||
*.gaux
|
||||
*.glog
|
||||
*.gtex
|
||||
|
||||
# htlatex
|
||||
*.4ct
|
||||
*.4tc
|
||||
*.idv
|
||||
*.lg
|
||||
*.trc
|
||||
*.xref
|
||||
|
||||
# hyperref
|
||||
*.brf
|
||||
|
||||
# knitr
|
||||
*-concordance.tex
|
||||
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
|
||||
# *.tikz
|
||||
*-tikzDictionary
|
||||
|
||||
# listings
|
||||
*.lol
|
||||
|
||||
# luatexja-ruby
|
||||
*.ltjruby
|
||||
|
||||
# makeidx
|
||||
*.idx
|
||||
*.ilg
|
||||
*.ind
|
||||
|
||||
# minitoc
|
||||
*.maf
|
||||
*.mlf
|
||||
*.mlt
|
||||
*.mtc[0-9]*
|
||||
*.slf[0-9]*
|
||||
*.slt[0-9]*
|
||||
*.stc[0-9]*
|
||||
|
||||
# minted
|
||||
_minted*
|
||||
*.pyg
|
||||
|
||||
# morewrites
|
||||
*.mw
|
||||
|
||||
# newpax
|
||||
*.newpax
|
||||
|
||||
# nomencl
|
||||
*.nlg
|
||||
*.nlo
|
||||
*.nls
|
||||
|
||||
# pax
|
||||
*.pax
|
||||
|
||||
# pdfpcnotes
|
||||
*.pdfpc
|
||||
|
||||
# sagetex
|
||||
*.sagetex.sage
|
||||
*.sagetex.py
|
||||
*.sagetex.scmd
|
||||
|
||||
# scrwfile
|
||||
*.wrt
|
||||
|
||||
# svg
|
||||
svg-inkscape/
|
||||
|
||||
# sympy
|
||||
*.sout
|
||||
*.sympy
|
||||
sympy-plots-for-*.tex/
|
||||
|
||||
# pdfcomment
|
||||
*.upa
|
||||
*.upb
|
||||
|
||||
# pythontex
|
||||
*.pytxcode
|
||||
pythontex-files-*/
|
||||
|
||||
# tcolorbox
|
||||
*.listing
|
||||
|
||||
# thmtools
|
||||
*.loe
|
||||
|
||||
# TikZ & PGF
|
||||
*.dpth
|
||||
*.md5
|
||||
*.auxlock
|
||||
|
||||
# titletoc
|
||||
*.ptc
|
||||
|
||||
# todonotes
|
||||
*.tdo
|
||||
|
||||
# vhistory
|
||||
*.hst
|
||||
*.ver
|
||||
|
||||
# easy-todo
|
||||
*.lod
|
||||
|
||||
# xcolor
|
||||
*.xcp
|
||||
|
||||
# xmpincl
|
||||
*.xmpi
|
||||
|
||||
# xindy
|
||||
*.xdy
|
||||
|
||||
# xypic precompiled matrices and outlines
|
||||
*.xyc
|
||||
*.xyd
|
||||
|
||||
# endfloat
|
||||
*.ttt
|
||||
*.fff
|
||||
|
||||
# Latexian
|
||||
TSWLatexianTemp*
|
||||
|
||||
## Editors:
|
||||
# WinEdt
|
||||
*.bak
|
||||
*.sav
|
||||
|
||||
# Texpad
|
||||
.texpadtmp
|
||||
|
||||
# LyX
|
||||
*.lyx~
|
||||
|
||||
# Kile
|
||||
*.backup
|
||||
|
||||
# gummi
|
||||
.*.swp
|
||||
|
||||
# KBibTeX
|
||||
*~[0-9]*
|
||||
|
||||
# TeXnicCenter
|
||||
*.tps
|
||||
|
||||
# auto folder when using emacs and auctex
|
||||
./auto/*
|
||||
*.el
|
||||
|
||||
# expex forward references with \gathertags
|
||||
*-tags.tex
|
||||
|
||||
# standalone packages
|
||||
*.sta
|
||||
|
||||
# Makeindex log files
|
||||
*.lpz
|
||||
|
||||
# xwatermark package
|
||||
*.xwm
|
||||
|
||||
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
|
||||
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
||||
# Uncomment the next line to have this generated file ignored.
|
||||
#*Notes.bib
|
78
agents/abstract_influencer_agent.py
Normal file
78
agents/abstract_influencer_agent.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from abc import ABC, abstractmethod
|
||||
import pickle
|
||||
import shelve
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import etc.gamestate as gs
|
||||
from etc.messages import MESSAGE_UNDEFINED, Message
|
||||
from etc.util import NoCopyShelf, RecursiveDict
|
||||
from agents.action_state import ActionState
|
||||
|
||||
|
||||
class AbstractInfluencerAgent(ABC):
|
||||
last_message: Message
|
||||
|
||||
# Number of red opinions [int]
|
||||
# Number of blue opinions [int]
|
||||
# Number of red followers [int]
|
||||
# Blue energy [int - convert from continuous float to discrete]
|
||||
state_action_lut: Dict[int, Dict[int, Dict[int, Dict[int, Dict[int, List[Union[int, float]]]]]]]
|
||||
__state_action_lut: NoCopyShelf
|
||||
|
||||
short_term_mem: List[ActionState]
|
||||
|
||||
def __init__(self, state_action_lut_path: str) -> None:
|
||||
self.last_message = MESSAGE_UNDEFINED
|
||||
self.__state_action_lut = NoCopyShelf.open(
|
||||
state_action_lut_path,
|
||||
protocol=pickle.HIGHEST_PROTOCOL,
|
||||
writeback=True # Yes this makes it less performant, but it will be more readable.
|
||||
)
|
||||
# Create data for new shelve
|
||||
try:
|
||||
self.__state_action_lut["data"]
|
||||
except KeyError:
|
||||
self.__state_action_lut["data"] = RecursiveDict()
|
||||
self.state_action_lut = self.__state_action_lut["data"]
|
||||
|
||||
self.short_term_mem = []
|
||||
|
||||
@abstractmethod
|
||||
def influence(self, state: "gs.GameState", *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def smart_influence(self, state: "gs.GameState") -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def choices() -> List[Tuple]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_short_term_mem(self, old_state: "gs.GameState", resulting_state: "gs.GameState") -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_lut(self) -> None:
|
||||
pass
|
||||
|
||||
def __update_lut_rating__(self, action_state: ActionState, rating: float) -> None:
|
||||
# Number of red opinions [int]
|
||||
# Number of blue opinions [int]
|
||||
# Number of red followers [int]
|
||||
# Blue energy [int - convert from continuous float to discrete]
|
||||
# Action
|
||||
previous_state_ratings: List[int, float] = self.state_action_lut[action_state.n_red_opinion_bin][action_state.n_blue_opinion_bin][
|
||||
action_state.n_red_followers_bin][action_state.blue_energy_bin]
|
||||
action = action_state.action.id
|
||||
if (type(previous_state_ratings[action]) != list):
|
||||
previous_state_ratings[action] = [0, 0.0]
|
||||
previous_state_ratings[action][0] += 1
|
||||
previous_state_ratings[action][1] += rating
|
||||
|
||||
def close_lut(self) -> None:
|
||||
self.__state_action_lut.close()
|
||||
|
||||
def sync_lut(self) -> None:
|
||||
self.__state_action_lut.sync()
|
75
agents/action_state.py
Normal file
75
agents/action_state.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from typing import Union
|
||||
from etc.messages import Message
|
||||
import etc.gamestate as gs
|
||||
|
||||
# Keep the following in short-term memory, for each potency option used.
|
||||
# STATE VARIABLES
|
||||
# Number of red opinions [int]
|
||||
# Number of blue opinions [int]
|
||||
# Number of red followers [int]
|
||||
# Blue energy [int - convert from continuous float to discrete]
|
||||
#
|
||||
# PARAMETERS IN CALCULATION
|
||||
# Round number
|
||||
# Change in red opinion
|
||||
# Change in blue opinion
|
||||
# Change in red followers
|
||||
# Change in blue energy
|
||||
# Used to update state_action_lut at end of game by the heuristic,
|
||||
# For blue:
|
||||
# ((Change in blue opinions) - (Change in red opinions) - (Change in Red followers) - (Change in blue energy)) * (Rounds to win)
|
||||
# Red has the opposite heuristic
|
||||
# There, learning. Are ya happy?????
|
||||
|
||||
|
||||
class ActionState:
|
||||
BINS = 5
|
||||
|
||||
# STATE (digitized from continuous)
|
||||
n_blue_opinion_bin: int
|
||||
n_red_opinion_bin: int
|
||||
n_red_followers_bin: int
|
||||
blue_energy_bin: int
|
||||
|
||||
iteration: int
|
||||
|
||||
# ACTION
|
||||
action: Message
|
||||
|
||||
# RESULTING STATE
|
||||
change_n_blue_opinion: int
|
||||
change_n_red_opinion: int
|
||||
change_n_red_followers: int
|
||||
change_blue_energy: int
|
||||
|
||||
# Assume value is between 0 and range.
|
||||
@staticmethod
|
||||
def bin(value: Union[float, int], bins: int, range: Union[float, int]) -> int:
|
||||
clamp_value = max(min(value, range), 0)
|
||||
return int(clamp_value / (range / (bins - 1)))
|
||||
|
||||
def __init__(self, action: Message, start_state: "gs.GameState", next_state: "gs.GameState") -> None:
|
||||
start_n_red_opinion, start_n_blue_opinion = start_state.count_majority()
|
||||
next_n_red_opinion, next_n_blue_opinion = next_state.count_majority()
|
||||
green_population = start_state.n_green_agents
|
||||
max_energy = start_state.blue_agent.initial_energy
|
||||
|
||||
# STATE
|
||||
self.n_red_opinion_bin = ActionState.bin(start_n_red_opinion, ActionState.BINS, green_population)
|
||||
self.n_blue_opinion_bin = ActionState.bin(start_n_blue_opinion, ActionState.BINS, green_population)
|
||||
self.n_red_followers_bin = ActionState.bin(start_state.red_agent.red_followers, ActionState.BINS, green_population)
|
||||
self.blue_energy_bin = ActionState.bin(start_state.blue_agent.blue_energy, ActionState.BINS, max_energy)
|
||||
|
||||
# ACTION
|
||||
self.iteration = start_state.iteration
|
||||
self.action = action
|
||||
self.change_n_blue_opinion = next_n_blue_opinion - start_n_blue_opinion
|
||||
self.change_n_red_opinion = next_n_red_opinion - start_n_red_opinion
|
||||
self.change_n_red_followers = next_state.red_agent.red_followers - start_state.red_agent.red_followers
|
||||
self.change_blue_energy = next_state.blue_agent.blue_energy - start_state.blue_agent.blue_energy
|
||||
pass
|
||||
|
||||
# Relative to the blue agent - invert for red agent.
|
||||
def rate_state_action(self, round_end: int, lost: bool) -> float:
|
||||
local_effect = self.change_n_blue_opinion + self.change_n_red_opinion + self.change_blue_energy + self.change_n_red_followers
|
||||
return (-1 if lost else 1) * (round_end - self.iteration) * local_effect
|
140
agents/blue.py
Normal file
140
agents/blue.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
|
||||
import math
|
||||
from random import Random
|
||||
import statistics
|
||||
from typing import List, Tuple
|
||||
import etc.gamestate as gs
|
||||
from agents.green import GreenAgent
|
||||
from agents.gray import GrayAgent
|
||||
from etc.messages import BLUE_MESSAGES, MESSAGE_BLUE_SPY, MESSAGE_UNDEFINED, Message, Opinion
|
||||
from agents.abstract_influencer_agent import AbstractInfluencerAgent
|
||||
from etc.util import RecursiveDict
|
||||
from agents.action_state import ActionState
|
||||
from play_config import BLUE_AGENT_LUT_PATH
|
||||
|
||||
|
||||
class BlueAgent(AbstractInfluencerAgent):
|
||||
# Energy level for blue team
|
||||
blue_energy: float
|
||||
initial_energy: float
|
||||
blue_count: int
|
||||
rand: Random
|
||||
|
||||
# Learn from gray agents
|
||||
gray_mem: List[Opinion]
|
||||
|
||||
# Number of red opinions [int]
|
||||
# Number of blue opinions [int]
|
||||
# Number of red followers [int]
|
||||
# Blue energy [int - convert from continuous float to discrete]
|
||||
# Action
|
||||
|
||||
def __init__(self, initial_energy: float, rand: Random) -> None:
|
||||
super().__init__(BLUE_AGENT_LUT_PATH)
|
||||
self.blue_energy = initial_energy
|
||||
self.initial_energy = initial_energy
|
||||
self.blue_count = 0
|
||||
self.rand = rand
|
||||
|
||||
self.gray_mem = [Opinion.BLUE, Opinion.RED] # Assume 0.5 probability of spy initially.
|
||||
|
||||
# > For a blue agent, the options will consist of a) - 10 correction messages (Please
|
||||
# > come up with some fictitious messages), uncertainty value and associated energy loss
|
||||
#
|
||||
# > blue team can push a counter-narrative and interact with green team members.
|
||||
# > However, if they invest too much by interacting with a high certainty,
|
||||
#
|
||||
# > blue team is changing the opinion of the green team members
|
||||
# Interpreting this as blue team being able to affect the uncertainty of green
|
||||
# nodes, NOT interacting implying being able to directly switch them to blue
|
||||
# by calling person.attempt_switch(Opinion.BLUE)
|
||||
# It is stated that blue can "interact with green team members", so I am
|
||||
# interpreting this as interacting/influencing ALL green members to vote, not
|
||||
# just those with a certain opinion. TO compensate the potency of blue
|
||||
# Messages will be halved.
|
||||
|
||||
@staticmethod
|
||||
def blue_action(person: GreenAgent, message: Message) -> None:
|
||||
if person.polarity == Opinion.RED:
|
||||
person.uncertainty += message.potency
|
||||
|
||||
# > the blue team is changing the opinion of the green team members. Blue team
|
||||
# > also has an option to let a grey agent in the green network. That agent
|
||||
# > can be thought of as a life line, where blue team gets another chance of
|
||||
# > interaction without losing “energy”. However, the grey agent can be a
|
||||
# > spy from the red team
|
||||
# So blue team can directly attempt to make people's opinions switch at the
|
||||
# cost of fuel (Unlike red team, which can only decrease red people's certainty
|
||||
# but not directly cause people to switch to red team), or use a spy.
|
||||
|
||||
def influence(self, gs: "gs.GameState", message: Message) -> None:
|
||||
self.last_message = message
|
||||
if message == MESSAGE_BLUE_SPY:
|
||||
gray: GrayAgent = self.rand.choice(gs.gray_agents)
|
||||
gray.gray_action(gs)
|
||||
|
||||
# Remember the action
|
||||
self.gray_mem.append(gray.polarity)
|
||||
else:
|
||||
for person in gs.green_agents.nodes(data=False):
|
||||
self.blue_energy -= message.cost
|
||||
obj_person: GreenAgent = gs.get_green_agent(person)
|
||||
BlueAgent.blue_action(obj_person, message)
|
||||
|
||||
def choices() -> List[Tuple]:
|
||||
choices = [x for x in BLUE_MESSAGES]
|
||||
choices.append(MESSAGE_BLUE_SPY)
|
||||
return choices
|
||||
|
||||
def gray_mean(self) -> float:
|
||||
return statistics.mean([1 if x == Opinion.BLUE else 0 for x in self.gray_mem])
|
||||
|
||||
def choose_gray(self) -> bool:
|
||||
return self.rand.uniform(0.0, 1.0) < self.gray_mean()
|
||||
|
||||
# Random process - used to balance the game
|
||||
def dumb_influence(self, state: "gs.GameState") -> None:
|
||||
self.influence(state, state.rand.choice(BlueAgent.choices()))
|
||||
|
||||
def smart_influence(self, state: "gs.GameState") -> None:
|
||||
if self.choose_gray():
|
||||
self.influence(state, MESSAGE_BLUE_SPY)
|
||||
return
|
||||
|
||||
red_opinion, blue_opinion = state.count_majority()
|
||||
n_red_opinion_bin = ActionState.bin(red_opinion, ActionState.BINS, state.n_green_agents)
|
||||
n_blue_opinion_bin = ActionState.bin(blue_opinion, ActionState.BINS, state.n_green_agents)
|
||||
n_red_followers_bin = ActionState.bin(state.red_agent.red_followers, ActionState.BINS, state.n_green_agents)
|
||||
blue_energy_bin = ActionState.bin(state.blue_agent.blue_energy, ActionState.BINS, state.blue_agent.initial_energy)
|
||||
|
||||
previous_rating: RecursiveDict[List[int, float]] = self.state_action_lut[n_red_opinion_bin][n_blue_opinion_bin][
|
||||
n_red_followers_bin][blue_energy_bin]
|
||||
|
||||
n_samples = 1
|
||||
largest_rating = -math.inf
|
||||
largest_action = -1
|
||||
for option in previous_rating:
|
||||
option_rating_fractional = previous_rating[option]
|
||||
rating = option_rating_fractional[1] / option_rating_fractional[0] # Average
|
||||
if rating > largest_rating:
|
||||
largest_rating = rating
|
||||
largest_action = option
|
||||
n_samples += option_rating_fractional[0]
|
||||
|
||||
message: Message
|
||||
# Use 1/sqrt(x)
|
||||
if self.rand.uniform(0.0, 1.0) < 1 / math.sqrt(n_samples):
|
||||
message = state.rand.choice(BLUE_MESSAGES)
|
||||
else:
|
||||
message = BLUE_MESSAGES[largest_action]
|
||||
self.influence(state, message)
|
||||
|
||||
def update_lut(self, terminal_state: "gs.GameState") -> None:
|
||||
blue_winner = terminal_state.winner == gs.Winner.BLUE_WIN or terminal_state.winner == gs.Winner.RED_NO_FOLLOWERS
|
||||
for action_state in self.short_term_mem:
|
||||
rating = action_state.rate_state_action(terminal_state.iteration, blue_winner)
|
||||
self.__update_lut_rating__(action_state, rating)
|
||||
|
||||
def update_short_term_mem(self, start_state: "gs.GameState", next_state: "gs.GameState") -> None:
|
||||
if self.last_message != MESSAGE_BLUE_SPY:
|
||||
self.short_term_mem.append(ActionState(self.last_message, start_state, next_state))
|
39
agents/gray.py
Normal file
39
agents/gray.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
from random import Random
|
||||
from typing import List
|
||||
import agents.blue as blue
|
||||
from agents.green import GreenAgent
|
||||
import agents.red as red
|
||||
from etc.messages import Opinion
|
||||
import etc.gamestate as gs
|
||||
from etc.messages import Message, BLUE_MESSAGES, RED_MESSAGES
|
||||
|
||||
|
||||
class GrayAgent:
|
||||
polarity: Opinion
|
||||
weights: List[float]
|
||||
rand: Random
|
||||
|
||||
def __init__(self, polarity: Opinion, rand: Random) -> None:
|
||||
self.polarity = polarity
|
||||
self.rand = rand
|
||||
|
||||
def choose_message(self) -> Message:
|
||||
if self.polarity == Opinion.BLUE:
|
||||
return self.rand.choices(BLUE_MESSAGES, weights=self.weights, k=1)[0]
|
||||
else:
|
||||
return self.rand.choices(RED_MESSAGES, weights=self.weights, k=1)[0]
|
||||
|
||||
def gray_action(self, state: "gs.GameState"):
|
||||
# TODO: There is no reason for gray not to play the highest potency message
|
||||
# as it has no penalty for playing high potency messages.
|
||||
if self.polarity == Opinion.BLUE:
|
||||
message: Message = BLUE_MESSAGES[4]
|
||||
for person in state.green_agents.nodes(data=False):
|
||||
obj_person: GreenAgent = state.green_agents.nodes[person]['data']
|
||||
blue.BlueAgent.blue_action(obj_person, message)
|
||||
else:
|
||||
message: Message = RED_MESSAGES[4]
|
||||
for person in state.green_agents.nodes(data=False):
|
||||
obj_person: GreenAgent = state.green_agents.nodes[person]['data']
|
||||
red.RedAgent.red_action(obj_person, message)
|
42
agents/green.py
Normal file
42
agents/green.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
from random import Random
|
||||
from typing import Tuple
|
||||
from etc.custom_float import Uncertainty
|
||||
from etc.messages import Opinion
|
||||
|
||||
OPINION_INFLUENCE = 0.2
|
||||
|
||||
|
||||
class GreenAgent:
|
||||
uncertainty: Uncertainty
|
||||
polarity: Opinion
|
||||
following_red: bool
|
||||
rand: Random
|
||||
|
||||
def __init__(self, uncertainty: float, polarity: Opinion, rand: Random) -> None:
|
||||
self.uncertainty = uncertainty
|
||||
self.polarity = polarity
|
||||
self.following_red = True
|
||||
self.rand = rand
|
||||
|
||||
def attempt_swap(self):
|
||||
# > To make it simple, the more positive a value is the more uncertain the
|
||||
# > agent is and the more negative the value is the more certain the agent is
|
||||
# Source: Help3001
|
||||
if self.rand.uniform(Uncertainty.UNCERTAINTY_MIN, Uncertainty.UNCERTAINTY_MAX) < self.uncertainty:
|
||||
self.polarity = Opinion.opposite(self.polarity)
|
||||
|
||||
def attempt_switch(self, polarity: Opinion) -> Opinion:
|
||||
# > To make it simple, the more positive a value is the more uncertain the
|
||||
# > agent is and the more negative the value is the more certain the agent is
|
||||
# Source: Help3001
|
||||
if self.rand.uniform(Uncertainty.UNCERTAINTY_MIN, Uncertainty.UNCERTAINTY_MAX) < self.uncertainty:
|
||||
self.polarity = polarity
|
||||
return polarity
|
||||
return Opinion.UNDEFINED
|
||||
|
||||
def unfollow_red(self):
|
||||
self.following_red = False
|
||||
|
||||
def clone(self) -> "GreenAgent":
|
||||
return GreenAgent(self.uncertainty.clone(), self.polarity, self.rand)
|
102
agents/red.py
Normal file
102
agents/red.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
|
||||
# Game Agents
|
||||
import math
|
||||
from random import Random
|
||||
from typing import List, Tuple
|
||||
from etc.messages import MESSAGE_UNDEFINED, RED_MESSAGES, Message, Opinion
|
||||
from agents.green import GreenAgent
|
||||
import etc.gamestate as gs
|
||||
from agents.abstract_influencer_agent import AbstractInfluencerAgent
|
||||
from agents.action_state import ActionState
|
||||
from etc.util import RecursiveDict
|
||||
from play_config import RED_AGENT_LUT_PATH
|
||||
|
||||
|
||||
class RedAgent(AbstractInfluencerAgent):
|
||||
# > A highly potent message may result in losing followers i.e., as compared
|
||||
# > to the last round fewer green team members will be able to interact with
|
||||
# > the red team agent
|
||||
# Assume everyone is a follower at the start
|
||||
red_followers: int # Number of green agents listening to red team
|
||||
red_count: int # Number of green agents with Opinion.RED
|
||||
rand: Random
|
||||
|
||||
def __init__(self, initial_followers: int, rand: Random) -> None:
|
||||
super().__init__(RED_AGENT_LUT_PATH)
|
||||
self.red_followers = initial_followers
|
||||
self.red_count = 0
|
||||
self.rand = rand
|
||||
|
||||
# > However, a potent message may decrease the uncertainity of opinion among
|
||||
# > people who are already under the influence of the red team
|
||||
@staticmethod
|
||||
def red_action(person: GreenAgent, message: Message):
|
||||
if person.following_red and person.polarity == Opinion.RED:
|
||||
person.uncertainty -= message.potency
|
||||
|
||||
def influence(self, state: "gs.GameState", message: Message) -> None:
|
||||
# No need to deepcopy() since each interaction only affects each person once.
|
||||
self.last_message = message
|
||||
for person in state.green_agents.nodes(data=False):
|
||||
obj_person: GreenAgent = state.get_green_agent(person)
|
||||
# > A highly potent message may result in losing followers i.e., as compared
|
||||
# > to the last round fewer green team members will be able to interact
|
||||
# > with the red team agent
|
||||
if self.rand.random() < message.cost:
|
||||
if obj_person.following_red:
|
||||
obj_person.unfollow_red()
|
||||
self.red_followers -= 1
|
||||
|
||||
# > However, a potent message may decrease the uncertainity of opinion among
|
||||
# > people who are already under the influence of the red team
|
||||
else:
|
||||
RedAgent.red_action(obj_person, message)
|
||||
|
||||
# Random process
|
||||
def dumb_influence(self, state: "gs.GameState") -> None:
|
||||
self.influence(state, state.rand.choice(RED_MESSAGES))
|
||||
|
||||
def smart_influence(self, state: "gs.GameState") -> None:
|
||||
red_opinion, blue_opinion = state.count_majority()
|
||||
n_red_opinion_bin = ActionState.bin(red_opinion, ActionState.BINS, state.n_green_agents)
|
||||
n_blue_opinion_bin = ActionState.bin(blue_opinion, ActionState.BINS, state.n_green_agents)
|
||||
n_red_followers_bin = ActionState.bin(state.red_agent.red_followers, ActionState.BINS, state.n_green_agents)
|
||||
blue_energy_bin = ActionState.bin(state.blue_agent.blue_energy, ActionState.BINS, state.blue_agent.initial_energy)
|
||||
|
||||
previous_rating: RecursiveDict[List[int, float]] = self.state_action_lut[n_red_opinion_bin][n_blue_opinion_bin][
|
||||
n_red_followers_bin][blue_energy_bin]
|
||||
|
||||
n_samples = 1
|
||||
largest_rating = -math.inf
|
||||
largest_action = 0
|
||||
for option in previous_rating:
|
||||
option_rating_fractional = previous_rating[option]
|
||||
rating = option_rating_fractional[1] / option_rating_fractional[0] # Average
|
||||
if rating > largest_rating:
|
||||
largest_rating = rating
|
||||
largest_action = option
|
||||
n_samples += option_rating_fractional[0]
|
||||
|
||||
message: Message
|
||||
# Use 1/sqrt(x) - Central-limit theorem
|
||||
if self.rand.uniform(0.0, 1.0) < 1 / math.sqrt(n_samples):
|
||||
message = state.rand.choice(RED_MESSAGES)
|
||||
else:
|
||||
message = RED_MESSAGES[largest_action]
|
||||
self.influence(state, message)
|
||||
|
||||
def choices() -> List[Tuple]:
|
||||
return [(x) for x in RED_MESSAGES]
|
||||
|
||||
def update_short_term_mem(self, start_state: "gs.GameState", next_state: "gs.GameState") -> None:
|
||||
self.short_term_mem.append(ActionState(self.last_message, start_state, next_state))
|
||||
|
||||
# ((Change in blue opinions) - (Change in red opinions) - (Change in Red followers) - (Change in blue energy)) * (Rounds to win)
|
||||
# Red has the opposite heuristic
|
||||
# There, learning. Are ya happy?????
|
||||
def update_lut(self, terminal_state: "gs.GameState") -> None:
|
||||
blue_winner = terminal_state.winner == gs.Winner.BLUE_WIN or terminal_state.winner == gs.Winner.RED_NO_FOLLOWERS
|
||||
for action_state in self.short_term_mem:
|
||||
# Since this is the red agent, assign negative to blue wins.
|
||||
rating = -action_state.rate_state_action(terminal_state.iteration, blue_winner)
|
||||
self.__update_lut_rating__(action_state, rating)
|
9
agents/state_action.txt
Normal file
9
agents/state_action.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
State variables:
|
||||
Number of red opinions
|
||||
Number of blue opinions
|
||||
Number of red followers
|
||||
Blue energy
|
||||
Actions:
|
||||
The 5 messages
|
||||
Evaluation of state-action pair:
|
||||
((Change in red opinions) - (Change in blue opinions)) * (Rounds to win)
|
65
etc/custom_float.py
Normal file
65
etc/custom_float.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
|
||||
|
||||
# > Sometimes people say that we would like to just model
|
||||
# > it by 01.
|
||||
# > Okay, They're very uncertain.
|
||||
# > Would be zero and very certain would be won.
|
||||
# > Okay, you can do that.
|
||||
# > Okay.
|
||||
# > So it depends upon your understanding.
|
||||
# > All right?
|
||||
# > Yes.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
|
||||
# Custom float class which clamps at ends
|
||||
class Uncertainty(float):
|
||||
UNCERTAINTY_MIN = 0.0
|
||||
UNCERTAINTY_MAX = 1.0
|
||||
UNCERTAINTY_THRESHOLD = 0.2 # Threshold at which a person is "convinced" by their opinion and can be counted as either "voting" or "not voting"
|
||||
|
||||
def __init__(self, value: float):
|
||||
float.__init__(value)
|
||||
if value.imag != 0.0:
|
||||
raise ValueError("Must be real")
|
||||
if value > Uncertainty.UNCERTAINTY_MAX or value < Uncertainty.UNCERTAINTY_MIN:
|
||||
raise ValueError("Outside of range")
|
||||
|
||||
def clamp(__x):
|
||||
return max(min(__x, Uncertainty.UNCERTAINTY_MAX), Uncertainty.UNCERTAINTY_MIN)
|
||||
|
||||
def short_init(__x) -> "Uncertainty":
|
||||
return Uncertainty(Uncertainty.clamp(__x))
|
||||
|
||||
def __add__(self, __x) -> "Uncertainty":
|
||||
return Uncertainty.short_init(self.real + __x)
|
||||
|
||||
def __sub__(self, __x) -> "Uncertainty":
|
||||
return Uncertainty.short_init(self.real - __x)
|
||||
|
||||
def __rsub__(self, __x) -> "Uncertainty":
|
||||
return Uncertainty.short_init(__x - self.real)
|
||||
|
||||
def __radd__(self, __x) -> "Uncertainty":
|
||||
return self.__add__(__x)
|
||||
|
||||
def __mul__(self, __x) -> "Uncertainty":
|
||||
return Uncertainty.short_init(self.real * __x)
|
||||
|
||||
def certainty(self) -> float:
|
||||
return Uncertainty.UNCERTAINTY_MAX - self.real + Uncertainty.UNCERTAINTY_MIN
|
||||
|
||||
def clone(self) -> "Uncertainty":
|
||||
return Uncertainty(self.real)
|
||||
|
||||
|
||||
# > All right, So what you need to do is that
|
||||
# > after every interaction, if the opinion changes, then you need
|
||||
# > to change uncertainty value as well.
|
||||
# > All right, So, um, and this is the tricky part
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
#
|
||||
#
|
||||
# > the project we have just
|
||||
# > two opinions, and you need to come up with a
|
||||
# > way to change the uncertainty.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
296
etc/gamestate.py
Normal file
296
etc/gamestate.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
from enum import Enum
|
||||
from math import atan, pi
|
||||
import matplotlib.pyplot as plt
|
||||
from etc.custom_float import Uncertainty
|
||||
from etc.messages import Opinion
|
||||
from copy import deepcopy
|
||||
from random import Random
|
||||
from typing import List, Tuple
|
||||
import networkx as nx
|
||||
|
||||
import agents.red as red
|
||||
import agents.blue as blue
|
||||
import agents.gray as gray
|
||||
import agents.green as green
|
||||
from play_config import DAYS_UNTIL_ELECTION, ENABLE_GRAPHICS, INITIAL_BLUE_ENERGY, INITIAL_UNCERTAINTY_RANGE
|
||||
|
||||
|
||||
class GameState:
|
||||
iteration: int
|
||||
winner: "Winner"
|
||||
iterations_left: int
|
||||
|
||||
n_green_agents: int
|
||||
p_green_agents: float
|
||||
green_agents: nx.Graph
|
||||
uncertainty_interval: Tuple[Uncertainty]
|
||||
|
||||
gray_agents: List["gray.GrayAgent"]
|
||||
|
||||
red_agent: "red.RedAgent"
|
||||
blue_agent: "blue.BlueAgent"
|
||||
rand: Random
|
||||
|
||||
# Visualization stuff
|
||||
graphics: bool
|
||||
viz = {}
|
||||
|
||||
# > At the start of the game, you need an initialise function.
|
||||
def __init__(self,
|
||||
green_agents: Tuple[int, float, float],
|
||||
gray_agents: Tuple[int, float],
|
||||
uncertainty_interval: Tuple[Uncertainty],
|
||||
seed: int, graphics: bool = ENABLE_GRAPHICS) -> None:
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
green_agents (Tuple[int, float, float]): (number of green agents, probability of connection, probability of initial opinion = blue)
|
||||
gray_agents (Tuple[int, float]): (number of gray agents, probability of red spy)
|
||||
uncertainty_interval (Tuple[float]): _description_
|
||||
seed (int): seed for Random in game
|
||||
"""
|
||||
self.graphics = graphics
|
||||
|
||||
self.iteration = 0
|
||||
self.winner = Winner.NO_WINNER
|
||||
|
||||
self.rand = Random()
|
||||
self.rand.seed(seed)
|
||||
self.uncertainty_interval = uncertainty_interval
|
||||
|
||||
self.n_green_agents = green_agents[0]
|
||||
self.p_green_agents = green_agents[1]
|
||||
self.green_agents = nx.erdos_renyi_graph(self.n_green_agents, self.p_green_agents)
|
||||
|
||||
# Generate gray agents
|
||||
self.gray_agents = []
|
||||
for x in range(0, int(gray_agents[0] * gray_agents[1])):
|
||||
self.gray_agents.append(gray.GrayAgent(Opinion.BLUE, self.rand))
|
||||
for x in range(int(gray_agents[0] * gray_agents[1]), gray_agents[0]):
|
||||
self.gray_agents.append(gray.GrayAgent(Opinion.RED, self.rand))
|
||||
|
||||
# > You have to assign one or the other opinion to green.
|
||||
# > Percentage of agents (green) who want to vote in the election, at the start of the [From pdf]
|
||||
|
||||
# > The other variable is uncertainity, for your given interval
|
||||
# > (say it was -0.5, 0.5), you need to generate a sequence of random
|
||||
# > numbers and assign and then assign green agents a number from that
|
||||
# > sequence. For say m green agents, you need m numbers, and there
|
||||
# > can be duplicates!
|
||||
# Negative numbers can still be "more positive" than lower negative numbers...
|
||||
# so translating the interval should have NO EFFECT on the probabilities
|
||||
# So we can assume that the uncertainty is in [-1.0,1.0] but this
|
||||
# uncertainty interval is only for initialization stage?
|
||||
for person in self.green_agents.nodes(data=False):
|
||||
uncertainty: Uncertainty = Uncertainty(self.rand.uniform(uncertainty_interval[0], uncertainty_interval[1]))
|
||||
polarity = Opinion.RED
|
||||
if self.rand.random() < green_agents[2]:
|
||||
polarity = Opinion.BLUE
|
||||
self.green_agents.nodes[person]['data'] = green.GreenAgent(uncertainty, polarity, self.rand)
|
||||
|
||||
self.blue_agent = blue.BlueAgent(INITIAL_BLUE_ENERGY, self.rand)
|
||||
self.red_agent = red.RedAgent(self.n_green_agents, self.rand)
|
||||
self.red_agent.red_count, self.blue_agent.blue_count = self.count_majority()
|
||||
|
||||
# Visualization
|
||||
|
||||
self.viz['pos'] = nx.nx_pydot.graphviz_layout(self.green_agents, prog="fdp")
|
||||
|
||||
self.iterations_left = DAYS_UNTIL_ELECTION
|
||||
|
||||
@staticmethod
|
||||
def short_init(green_agents: Tuple[int, float], gray_agents: Tuple[int, float]) -> "GameState":
|
||||
return GameState(green_agents, gray_agents, INITIAL_UNCERTAINTY_RANGE, None)
|
||||
|
||||
def get_green_agent(self, person: int) -> green.GreenAgent:
|
||||
return self.green_agents.nodes[person]['data']
|
||||
|
||||
def close(self) -> None:
|
||||
self.blue_agent.update_lut(self)
|
||||
self.blue_agent.close_lut()
|
||||
self.red_agent.update_lut(self)
|
||||
self.red_agent.close_lut()
|
||||
|
||||
def green_round(self: "GameState") -> "GameState":
|
||||
next_green_graph: nx.Graph = deepcopy(self.green_agents)
|
||||
for person in self.green_agents.nodes(data=False):
|
||||
for connection in self.green_agents.neighbors(person):
|
||||
# Test nodes
|
||||
obj_person: "green.GreenAgent" = self.get_green_agent(person).clone()
|
||||
obj_connection: "green.GreenAgent" = self.get_green_agent(connection).clone()
|
||||
interaction_result = Opinion.UNDEFINED # = obj_person.interact(obj_connection)
|
||||
|
||||
# > green team's turn: all agents who have a connection between them
|
||||
# > can can have an interaction, the one who is more certain about
|
||||
# > their opinion may affect the other green.
|
||||
# Source: Help3001
|
||||
# > This equation is for this particular paper because they have
|
||||
# > the opinion on that.
|
||||
# > The opinions are a real number, okay?
|
||||
# > And they are on a scale of minus one to
|
||||
# > plus one
|
||||
# > for your project.
|
||||
# > You will also have to write an equation, you have
|
||||
# > to come up with your own equation.
|
||||
# > Okay, so that is the part of your project.
|
||||
# > That equation does not have to be an overly complicated
|
||||
# > equation.
|
||||
# > Could be a really small equation.
|
||||
# > Uh, simple equation.
|
||||
# > You can you could be updating.
|
||||
# > Um, uh, the uncertainties by a simple, uh, you know,
|
||||
# > a small value.
|
||||
# > Okay, so just think over it.
|
||||
# > How would you like to do that?
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
# For context:
|
||||
# > Now, in the project, you had, like, two opinions.
|
||||
# > Okay, uh, to vote or not to vote.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
next_: green.GreenAgent
|
||||
if obj_person.uncertainty < obj_connection.uncertainty:
|
||||
# Switch the other agent
|
||||
# next_connection = next_state.green_agents.nodes[connection]['data']
|
||||
next_connection: green.GreenAgent = next_green_graph.nodes[connection]['data']
|
||||
interaction_result = next_connection.attempt_switch(obj_person.polarity)
|
||||
# Rationale: The more certain an agent is the better they are at arguing,
|
||||
# which causes the target to be more certain about their decision to switch,
|
||||
# although not completely uncertain since they just switched.
|
||||
if not interaction_result == Opinion.UNDEFINED:
|
||||
next_connection.uncertainty -= obj_person.uncertainty.certainty() * green.OPINION_INFLUENCE
|
||||
else:
|
||||
# Switch self
|
||||
next_person: green.GreenAgent = next_green_graph.nodes[person]['data']
|
||||
interaction_result = next_person.attempt_switch(obj_connection.polarity)
|
||||
if not interaction_result == Opinion.UNDEFINED:
|
||||
next_person.uncertainty -= obj_connection.uncertainty.certainty() * green.OPINION_INFLUENCE
|
||||
|
||||
# Update totals
|
||||
# if interaction_result == Opinion.RED:
|
||||
# self.red_agent.red_count += 1
|
||||
# self.blue_agent.blue_count -= 1
|
||||
# elif interaction_result == Opinion.BLUE:
|
||||
# self.red_agent.red_count -= 1
|
||||
# self.blue_agent.blue_count += 1
|
||||
self.green_agents = next_green_graph
|
||||
self.red_agent.red_count, self.blue_agent.blue_count = self.count_majority()
|
||||
|
||||
#
|
||||
# Visualization
|
||||
#
|
||||
|
||||
def draw_green_network(self) -> None:
|
||||
if not self.graphics:
|
||||
return
|
||||
labels = {}
|
||||
colors = []
|
||||
for node in self.green_agents.nodes():
|
||||
agent = self.get_green_agent(node)
|
||||
|
||||
label = "F" if agent.following_red else ""
|
||||
labels[node] = label
|
||||
|
||||
if agent.polarity == Opinion.RED:
|
||||
colors.append((agent.uncertainty.certainty(), 0, 0))
|
||||
else:
|
||||
colors.append((0, 0, agent.uncertainty.certainty()))
|
||||
nx.draw(self.green_agents, node_color=colors, labels=labels, pos=self.viz['pos'], node_size=70)
|
||||
plt.savefig("green_graph.png", dpi=600)
|
||||
plt.close()
|
||||
|
||||
def count_majority(self) -> Tuple[int, int]:
|
||||
red_count: int = 0
|
||||
blue_count: int = 0
|
||||
for _, person in self.green_agents.nodes(data="data"):
|
||||
person: green.GreenAgent
|
||||
if person.uncertainty < Uncertainty.UNCERTAINTY_THRESHOLD:
|
||||
if person.polarity == Opinion.BLUE:
|
||||
blue_count += 1
|
||||
elif person.polarity == Opinion.RED:
|
||||
red_count += 1
|
||||
return red_count, blue_count
|
||||
|
||||
def determine_majority(self) -> "Winner":
|
||||
if self.red_agent.red_count > self.n_green_agents * 0.9:
|
||||
return Winner.RED_WIN
|
||||
elif self.blue_agent.blue_count > self.n_green_agents * 0.9:
|
||||
return Winner.BLUE_WIN
|
||||
return Winner.NO_WINNER
|
||||
|
||||
def update_winner(self) -> None:
|
||||
# > In order for the Red agent to win (i.e., a higher number of green agents with
|
||||
# > opinion “not vote”, and an uncertainty less than 0 (which means they are
|
||||
# > pretty certain about their choice))
|
||||
#
|
||||
# > In order for the Blue agent to win (i.e., a higher number of green agents with
|
||||
# > opinion “vote”, and an uncertainty less than 0 (which means they are pretty
|
||||
# > certain about their choice))
|
||||
#
|
||||
# Higher than what? Assuming it means 50% of the population, otherwise one team can
|
||||
# win from the start of the game just by comparing to the same metric but
|
||||
# with the other team.
|
||||
majority = self.determine_majority()
|
||||
if not majority == Winner.NO_WINNER:
|
||||
self.winner = majority
|
||||
return
|
||||
|
||||
# Blue agent loss:
|
||||
# > If they expend all their energy, the game will end
|
||||
# Source: Help3001
|
||||
# > - blue agent dead
|
||||
if self.blue_agent.blue_energy <= 0:
|
||||
self.winner = Winner.BLUE_NO_ENERGY
|
||||
return
|
||||
|
||||
# Red agent loss:
|
||||
# > - red agent lost all followers
|
||||
if self.red_agent.red_followers <= 0:
|
||||
self.winner = Winner.RED_NO_FOLLOWERS
|
||||
return
|
||||
|
||||
self.winner = Winner.NO_WINNER
|
||||
return
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""
|
||||
{self.iteration=}, {self.winner=}
|
||||
{self.red_agent.red_followers=}, [{self.blue_agent.blue_energy=}, gray_mean={self.blue_agent.gray_mean()} {self.blue_agent.gray_mem=}]
|
||||
{self.red_agent.red_count=}, {self.blue_agent.blue_count=}
|
||||
{str(self.red_agent.last_message)} || {str(self.blue_agent.last_message)}
|
||||
"""
|
||||
|
||||
def print_gamestate_pretty(self) -> str:
|
||||
return f"""
|
||||
--- ROUND {self.iteration} ---
|
||||
Population statistics
|
||||
- People voting (Blue) {self.blue_agent.blue_count}
|
||||
- People not voting (Red) {self.red_agent.red_count}
|
||||
Agent state
|
||||
- Blue energy remaining: {self.blue_agent.blue_energy}
|
||||
- Red subscribers: {self.red_agent.red_followers}
|
||||
Last move
|
||||
- Red: {self.red_agent.last_message.message}
|
||||
- Blue: {self.blue_agent.last_message.message}
|
||||
- Last gray outcome: {"UNDEFINED" if len(self.blue_agent.gray_mem) == 2 else self.blue_agent.gray_mem[-1]}
|
||||
"""
|
||||
|
||||
|
||||
class Winner(Enum):
|
||||
NO_WINNER = 0
|
||||
BLUE_WIN = 1
|
||||
RED_WIN = 2
|
||||
BLUE_NO_ENERGY = 3
|
||||
RED_NO_FOLLOWERS = 4
|
||||
|
||||
def red_won(winner: "Winner"):
|
||||
return winner in [Winner.BLUE_NO_ENERGY or Winner.RED_WIN]
|
||||
|
||||
def blue_won(winner: "Winner"):
|
||||
return winner in [Winner.RED_NO_FOLLOWERS or Winner.RED_WIN]
|
||||
|
||||
def winner_opinion(winner: "Winner"):
|
||||
if Winner.red_won(winner):
|
||||
return Opinion.RED
|
||||
elif Winner.blue_won(winner):
|
||||
return Opinion.BLUE
|
||||
return Opinion.UNDEFINED
|
92
etc/messages.py
Normal file
92
etc/messages.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
from enum import Enum
|
||||
from math import nan
|
||||
from typing import Union, overload
|
||||
from etc.custom_float import Uncertainty
|
||||
|
||||
from play_config import INITIAL_UNCERTAINTY_RANGE
|
||||
|
||||
|
||||
class Opinion(Enum):
|
||||
RED = 0
|
||||
BLUE = 1
|
||||
UNDEFINED = 2
|
||||
|
||||
def opposite(opinion: "Opinion"):
|
||||
return Opinion.RED if opinion == Opinion.BLUE else Opinion.BLUE
|
||||
|
||||
def __str__(self) -> str:
|
||||
return {
|
||||
Opinion.RED: "RED",
|
||||
Opinion.BLUE: "BLUE",
|
||||
Opinion.UNDEFINED: "?",
|
||||
}[self]
|
||||
|
||||
|
||||
# > Sometimes people say that we would like to just model
|
||||
# > it by 01.
|
||||
# > Okay, They're very uncertain.
|
||||
# > Would be zero and very certain would be won.
|
||||
# > Okay, you can do that.
|
||||
# > Okay.
|
||||
# > So it depends upon your understanding.
|
||||
# > All right?
|
||||
# > Yes.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
|
||||
INFLUENCE_FACTOR = 1.0
|
||||
|
||||
|
||||
class Message():
|
||||
id: int
|
||||
potency: Uncertainty
|
||||
cost: float
|
||||
message: str
|
||||
|
||||
def __init__(self, id: int, message: str, potency: float, cost: float) -> None:
|
||||
self.id = id
|
||||
self.cost = cost
|
||||
self.potency = Uncertainty(potency)
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.potency=}, {self.cost=}, {self.message}"
|
||||
|
||||
|
||||
MESSAGE_UNDEFINED = Message(0, "UNDEFINED", 0.0, 0.0)
|
||||
|
||||
|
||||
# > So why did I mention that you need to have
|
||||
# > five levels or 10 levels?
|
||||
# > It was for simplicity, because this is how we normally
|
||||
# > start the project.
|
||||
# > If you just want five or 10 levels, that's fine.
|
||||
# > I'm not going to detect points for that.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
#
|
||||
# > And then what else you need to have is for
|
||||
# > every uncertainty value, either you need to have this in
|
||||
# > a table, or you just need to define it by
|
||||
# > an equation.
|
||||
# Source: CITS3001 - 13 Sep 2022, 11:00 - Lecture
|
||||
# Using a lookup-table
|
||||
|
||||
mul1 = 1
|
||||
potencymul1 = 0.05
|
||||
RED_MESSAGES = [
|
||||
Message(0, "Red message (low)", potencymul1 * 0.1, 0.1 / mul1),
|
||||
Message(1, "Red message (medlow)", potencymul1 * 0.15, 0.15 / mul1),
|
||||
Message(2, "Red message (med)", potencymul1 * 0.2, 0.2 / mul1),
|
||||
Message(3, "Red message (highmed)", potencymul1 * 0.25, 0.25 / mul1),
|
||||
Message(4, "Red message (high)", potencymul1 * 0.3, 0.3 / mul1),
|
||||
]
|
||||
|
||||
MESSAGE_BLUE_SPY = Message(0, "Gray spy - chance of highest red OR blue message, at no cost", nan, 0.0)
|
||||
mul2 = 1
|
||||
potencymul2 = 0.05
|
||||
BLUE_MESSAGES = [
|
||||
Message(0, "Blue message (low)", potencymul2 * 0.5 * 0.1, 0.1 / mul2),
|
||||
Message(1, "Blue message (medlow)", potencymul2 * 0.5 * 0.15, 0.15 / mul2),
|
||||
Message(2, "Blue message (med)", potencymul2 * 0.5 * 0.2, 0.2 / mul2),
|
||||
Message(3, "Blue message (highmed)", potencymul2 * 0.5 * 0.25, 0.25 / mul2),
|
||||
Message(4, "Blue message (high)", potencymul2 * 0.5 * 0.3, 0.3 / mul2),
|
||||
]
|
25
etc/util.py
Normal file
25
etc/util.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from shelve import DbfilenameShelf
|
||||
from play_config import ENABLE_DIAGNOSTIC_MESSAGES
|
||||
|
||||
|
||||
def debug_print(*args):
|
||||
if ENABLE_DIAGNOSTIC_MESSAGES:
|
||||
print(*args)
|
||||
|
||||
|
||||
class RecursiveDict(dict):
|
||||
# defaultdict but smaller
|
||||
def __missing__(self, key):
|
||||
value = self[key] = type(self)()
|
||||
return value
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return self
|
||||
|
||||
|
||||
class NoCopyShelf(DbfilenameShelf):
|
||||
def __deepcopy__(self, memo):
|
||||
return self
|
||||
|
||||
def open(filename, flag='c', protocol=None, writeback=False):
|
||||
return NoCopyShelf(filename, flag, protocol, writeback)
|
31
play_config.py
Normal file
31
play_config.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import sys
|
||||
from typing import Tuple
|
||||
|
||||
from etc.custom_float import Uncertainty
|
||||
|
||||
# Enable graphing
|
||||
ENABLE_GRAPHICS = False
|
||||
|
||||
# Debug messages
|
||||
ENABLE_DIAGNOSTIC_MESSAGES = False
|
||||
|
||||
# Serialize each game and save to a file
|
||||
ENABLE_SNAPSHOTS = False
|
||||
GAMESTATE_PARAMETER_SNAPSHOT_OUTPUT_DIR = "./snapshots/"
|
||||
|
||||
# Check Uncertainty class
|
||||
INITIAL_UNCERTAINTY_RANGE: Tuple[Uncertainty] = (Uncertainty(0.0), Uncertainty(0.0))
|
||||
INITIAL_BLUE_ENERGY: float = 300.0
|
||||
|
||||
DAYS_UNTIL_ELECTION = sys.maxsize
|
||||
|
||||
# Training data path
|
||||
BLUE_AGENT_LUT_PATH = "blue_training.bin"
|
||||
RED_AGENT_LUT_PATH = "red_training.bin"
|
||||
|
||||
N_GREEN_AGENT = 100
|
||||
P_GREEN_AGENT_CONNECTION = 0.05
|
||||
P_GREEN_AGENT_BLUE = 0.5
|
||||
|
||||
N_GRAY_AGENT = 10
|
||||
P_GRAY_AGENT_FRIENDLY = 0.5
|
198
play_manual.py
Normal file
198
play_manual.py
Normal file
|
@ -0,0 +1,198 @@
|
|||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import math
|
||||
import pathlib
|
||||
import pickle
|
||||
from typing import List
|
||||
from etc.custom_float import Uncertainty
|
||||
from etc.gamestate import GameState, Winner
|
||||
from etc.messages import Message, Opinion
|
||||
from agents.blue import BlueAgent
|
||||
from agents.red import RedAgent
|
||||
|
||||
import play_config as cfg
|
||||
|
||||
|
||||
def rand_rounds(gs: GameState) -> GameState:
|
||||
blue_gamestate = deepcopy(gs)
|
||||
gs.blue_agent.smart_influence(gs)
|
||||
red_gamestate = deepcopy(gs)
|
||||
gs.red_agent.smart_influence(gs)
|
||||
# gs.draw_green_network()
|
||||
# gs.green_round()
|
||||
# gs.draw_green_network()
|
||||
# spy = bool(gs.rand.getrandbits(1))
|
||||
# spy = False
|
||||
gs.green_round()
|
||||
gs.draw_green_network()
|
||||
gs.red_agent.update_short_term_mem(red_gamestate, gs)
|
||||
gs.blue_agent.update_short_term_mem(blue_gamestate, gs)
|
||||
gs.iteration += 1
|
||||
return gs
|
||||
|
||||
|
||||
def main():
|
||||
sel_parameters = input_opts("""Would you like to select parameters?
|
||||
[y] -> Yes
|
||||
[n] -> No
|
||||
> """, ['y', 'n'])
|
||||
if sel_parameters == 'y':
|
||||
select_parameters()
|
||||
|
||||
cfg.ENABLE_SNAPSHOTS = ('y' == input_opts("""Would you like to enable snapshots?
|
||||
[y] -> Yes
|
||||
[n] -> No
|
||||
> """, ['y', 'n']))
|
||||
|
||||
if cfg.ENABLE_SNAPSHOTS:
|
||||
snapshot_path = pathlib.Path(cfg.GAMESTATE_PARAMETER_SNAPSHOT_OUTPUT_DIR)
|
||||
snapshot_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cfg.ENABLE_GRAPHICS = ('y' == input_opts("""Enable graphics?
|
||||
Note that this option is currently exporting to an image file for compatibility on headless operating systems (WSL)
|
||||
[y] -> Graphics
|
||||
[n] -> No graphics
|
||||
> """, ['y', 'n']))
|
||||
|
||||
while True:
|
||||
print("Starting new game")
|
||||
state_buffer: List[GameState] = []
|
||||
# gs: GameState = GameState.short_init((100, 0.05, 0.5), (10, 0.5))
|
||||
gs: GameState = GameState(
|
||||
green_agents=(cfg.N_GREEN_AGENT, cfg.P_GREEN_AGENT_CONNECTION, cfg.P_GREEN_AGENT_BLUE),
|
||||
gray_agents=(cfg.N_GRAY_AGENT, cfg.P_GRAY_AGENT_FRIENDLY),
|
||||
uncertainty_interval=cfg.INITIAL_UNCERTAINTY_RANGE,
|
||||
seed=None, graphics=cfg.ENABLE_GRAPHICS)
|
||||
|
||||
player = input_opts("""Choose a team
|
||||
[r] -> Red
|
||||
[b] -> Blue
|
||||
[n] -> None
|
||||
> """, ['r', 'b', 'n'])
|
||||
|
||||
player: Opinion = {
|
||||
'r': Opinion.RED,
|
||||
'b': Opinion.BLUE,
|
||||
'n': Opinion.UNDEFINED
|
||||
}[player]
|
||||
|
||||
state_buffer.append(deepcopy(gs))
|
||||
|
||||
while gs.winner == Winner.NO_WINNER:
|
||||
print(gs.print_gamestate_pretty())
|
||||
|
||||
print("Blue turn")
|
||||
blue_gamestate = deepcopy(gs)
|
||||
if player == Opinion.BLUE:
|
||||
option = select_potency(BlueAgent.choices())
|
||||
gs.blue_agent.influence(gs, option)
|
||||
else:
|
||||
gs.blue_agent.dumb_influence(gs)
|
||||
|
||||
print("Red turn")
|
||||
red_gamestate = deepcopy(gs)
|
||||
if player == Opinion.RED:
|
||||
option = select_potency(RedAgent.choices())
|
||||
gs.red_agent.influence(gs, option)
|
||||
else:
|
||||
gs.red_agent.dumb_influence(gs)
|
||||
|
||||
print("Green turn")
|
||||
gs.green_round()
|
||||
gs.draw_green_network()
|
||||
|
||||
gs.red_agent.update_short_term_mem(red_gamestate, gs)
|
||||
gs.blue_agent.update_short_term_mem(blue_gamestate, gs)
|
||||
gs.iteration += 1
|
||||
gs.update_winner()
|
||||
state_buffer.append(deepcopy(gs))
|
||||
gs.close()
|
||||
|
||||
print(f"""
|
||||
Game over
|
||||
Round {gs.iteration}, reason {gs.winner}
|
||||
Winner {Winner.winner_opinion(gs.winner)}""")
|
||||
if player != Opinion.UNDEFINED:
|
||||
print("YOU WIN 🎉" if Winner.winner_opinion(gs.winner) == player else "YOU LOSE 💀")
|
||||
print(gs.print_gamestate_pretty())
|
||||
input("Press enter to continue...")
|
||||
|
||||
# Save snapshot of game
|
||||
if cfg.ENABLE_SNAPSHOTS:
|
||||
with snapshot_path.joinpath(f"game_snapshot_{datetime.now().isoformat().replace(':','_')}.bin").open("wb") as f:
|
||||
pickle.dump(state_buffer, f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
||||
def select_potency(messages: List[Message]) -> Message:
|
||||
prompt_str = "Choose a potency\n"
|
||||
for i in range(0, len(messages)):
|
||||
prompt_str += f"[{i}] -> {str(messages[i])}\n"
|
||||
return messages[input_p_int(prompt_str, 0, len(messages))]
|
||||
|
||||
|
||||
def select_parameters() -> None:
|
||||
# Override config defaults
|
||||
|
||||
print("\nBlue agent\n---")
|
||||
cfg.INITIAL_BLUE_ENERGY = input_float("Blue agent initial energy?\n> ")
|
||||
print("\nBounds of uniform uncertainty distribution at start of game for green agents\n---")
|
||||
cfg.INITIAL_UNCERTAINTY_RANGE = (input_uncertainty("Lower\n> "), input_uncertainty("Upper\n> "))
|
||||
|
||||
print("\nGreen agents\n---")
|
||||
cfg.N_GREEN_AGENT = input_p_int("Number of green agents\n> ")
|
||||
cfg.P_GREEN_AGENT_CONNECTION = input_probability("Probability of green agent connections [0,1]\n> ")
|
||||
cfg.P_GREEN_AGENT_BLUE = input_p_int("Probability of green agents initialized to blue opinion\n> ")
|
||||
|
||||
print("\nGray agents\n---")
|
||||
cfg.N_GRAY_AGENT = input_p_int("Number of gray agents\n> ")
|
||||
cfg.P_GRAY_AGENT_FRIENDLY = input_probability("Probability of gray agents being blue\n> ")
|
||||
|
||||
|
||||
def input_probability(prompt: str) -> float:
|
||||
while True:
|
||||
value = input_T(prompt, float)
|
||||
if value > 0.0 and value < 1.0:
|
||||
return value
|
||||
else:
|
||||
print("Invalid input")
|
||||
|
||||
|
||||
def input_p_int(prompt: str, l_=0, u_=math.inf) -> int:
|
||||
while True:
|
||||
value = input_T(prompt, int)
|
||||
if value >= l_ and value < u_:
|
||||
return value
|
||||
else:
|
||||
print("Invalid input")
|
||||
|
||||
|
||||
def input_float(prompt: str) -> float:
|
||||
return input_T(prompt, float)
|
||||
|
||||
|
||||
def input_uncertainty(prompt: str) -> Uncertainty:
|
||||
return input_T(prompt, Uncertainty)
|
||||
|
||||
|
||||
def input_T(prompt: str, type):
|
||||
num: type = None
|
||||
while num is None:
|
||||
try:
|
||||
num = type(input(prompt))
|
||||
except ValueError:
|
||||
print("Invalid input")
|
||||
return num
|
||||
|
||||
|
||||
def input_opts(prompt: str, opts: List[str]) -> str:
|
||||
option: str = ""
|
||||
while option not in opts:
|
||||
if len(option) > 0:
|
||||
print("Invalid input")
|
||||
option = input(prompt).strip().lower()
|
||||
return option
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
25
requirements.txt
Normal file
25
requirements.txt
Normal file
|
@ -0,0 +1,25 @@
|
|||
autopep8==1.7.0
|
||||
cycler==0.11.0
|
||||
flake8==5.0.4
|
||||
fonttools==4.37.1
|
||||
joblib==1.2.0
|
||||
kiwisolver==1.4.4
|
||||
matplotlib==3.5.3
|
||||
mccabe==0.7.0
|
||||
networkx==2.8.6
|
||||
numpy==1.23.2
|
||||
packaging==21.3
|
||||
Pillow==9.2.0
|
||||
pyaml==21.10.1
|
||||
pycodestyle==2.9.1
|
||||
pydot==1.4.2
|
||||
pyflakes==2.5.0
|
||||
pyparsing==3.0.9
|
||||
python-dateutil==2.8.2
|
||||
PyYAML==6.0
|
||||
scikit-learn==1.1.2
|
||||
scikit-optimize==0.9.0
|
||||
scipy==1.9.2
|
||||
six==1.16.0
|
||||
threadpoolctl==3.1.0
|
||||
toml==0.10.2
|
125
train.py
Normal file
125
train.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
import csv
|
||||
from datetime import datetime
|
||||
import pathlib
|
||||
import pickle
|
||||
from time import time
|
||||
import numpy as np
|
||||
from copy import deepcopy
|
||||
from types import FunctionType
|
||||
from typing import Callable, Dict, List
|
||||
from etc.gamestate import GameState, Winner
|
||||
from etc.util import debug_print
|
||||
from play_config import ENABLE_SNAPSHOTS, GAMESTATE_PARAMETER_SNAPSHOT_OUTPUT_DIR, INITIAL_UNCERTAINTY_RANGE, N_GRAY_AGENT, N_GREEN_AGENT,\
|
||||
P_GRAY_AGENT_FRIENDLY, P_GREEN_AGENT_BLUE, P_GREEN_AGENT_CONNECTION
|
||||
|
||||
|
||||
def rand_rounds(gs: GameState) -> GameState:
|
||||
blue_gamestate = deepcopy(gs)
|
||||
gs.blue_agent.dumb_influence(gs)
|
||||
red_gamestate = deepcopy(gs)
|
||||
gs.red_agent.dumb_influence(gs)
|
||||
# gs.draw_green_network()
|
||||
# gs.green_round()
|
||||
# gs.draw_green_network()
|
||||
# spy = bool(gs.rand.getrandbits(1))
|
||||
# spy = False
|
||||
gs.green_round()
|
||||
gs.red_agent.update_short_term_mem(red_gamestate, gs)
|
||||
gs.blue_agent.update_short_term_mem(blue_gamestate, gs)
|
||||
gs.draw_green_network()
|
||||
gs.iteration += 1
|
||||
return gs
|
||||
|
||||
|
||||
def intelligent_rounds(gs: GameState) -> GameState:
|
||||
blue_gamestate = deepcopy(gs)
|
||||
gs.blue_agent.smart_influence(gs)
|
||||
red_gamestate = deepcopy(gs)
|
||||
gs.red_agent.smart_influence(gs)
|
||||
gs.green_round()
|
||||
gs.red_agent.update_short_term_mem(red_gamestate, gs)
|
||||
gs.blue_agent.update_short_term_mem(blue_gamestate, gs)
|
||||
gs.draw_green_network()
|
||||
gs.iteration += 1
|
||||
return gs
|
||||
|
||||
|
||||
def round(round_func: FunctionType) -> List[GameState]:
|
||||
state_buffer: List[GameState] = []
|
||||
# gs: GameState = GameState.short_init((100, 0.05, 0.5), (10, 0.5))
|
||||
gs: GameState = GameState(
|
||||
green_agents=(N_GREEN_AGENT, P_GREEN_AGENT_CONNECTION, P_GREEN_AGENT_BLUE),
|
||||
gray_agents=(N_GRAY_AGENT, P_GRAY_AGENT_FRIENDLY),
|
||||
uncertainty_interval=INITIAL_UNCERTAINTY_RANGE,
|
||||
seed=None, graphics=False)
|
||||
state_buffer.append(deepcopy(gs))
|
||||
|
||||
debug_print("INITIAL CONDITIONS.")
|
||||
debug_print(gs)
|
||||
debug_print("STARTING GAME.")
|
||||
|
||||
# gs.draw_green_network()
|
||||
while gs.winner == Winner.NO_WINNER:
|
||||
gs = round_func(gs)
|
||||
# debug_print(gs.red_agent.red_followers, gs.blue_agent.blue_energy, len(list(gs.green_agents)))
|
||||
debug_print(gs)
|
||||
gs.update_winner()
|
||||
gs.draw_green_network()
|
||||
state_buffer.append(deepcopy(gs))
|
||||
# print(gs)
|
||||
gs.close()
|
||||
|
||||
# state_buffer.append(deepcopy(gs))
|
||||
|
||||
print(f"{gs.iteration} WINNER {gs.winner}")
|
||||
return state_buffer
|
||||
|
||||
# Calibrator
|
||||
|
||||
|
||||
def training_rounds(win_file_blue, win_file_red, round_func: Callable, training_iterations: int) -> None:
|
||||
# state_buffer: List[GameState] = round(rand_rounds)
|
||||
# exit()
|
||||
ending_states: Dict[Winner, int] = {
|
||||
Winner.BLUE_NO_ENERGY: 0,
|
||||
Winner.BLUE_WIN: 0,
|
||||
Winner.RED_NO_FOLLOWERS: 0,
|
||||
Winner.RED_WIN: 0,
|
||||
}
|
||||
|
||||
blue_win_round_len = []
|
||||
red_win_round_len = []
|
||||
|
||||
for x in range(0, training_iterations):
|
||||
t = time()
|
||||
print(f"Game {x}")
|
||||
state_buffer: List[GameState] = round(round_func)
|
||||
|
||||
ending_state: GameState = state_buffer[-1]
|
||||
|
||||
ending_states[ending_state.winner] += 1
|
||||
if ending_state.winner == Winner.BLUE_NO_ENERGY or ending_state.winner == Winner.RED_WIN:
|
||||
red_win_round_len.append(ending_state.iteration)
|
||||
blue_win_round_len.append(-ending_state.iteration)
|
||||
elif ending_state.winner == Winner.RED_NO_FOLLOWERS or ending_state.winner == Winner.BLUE_WIN:
|
||||
red_win_round_len.append(-ending_state.iteration)
|
||||
blue_win_round_len.append(ending_state.iteration)
|
||||
print(f"dt={time() - t} s")
|
||||
print(ending_states)
|
||||
print(blue_win_round_len)
|
||||
print(red_win_round_len)
|
||||
|
||||
with open(win_file_blue, "w") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(blue_win_round_len)
|
||||
|
||||
with open(win_file_red, "w") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(red_win_round_len)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
N_RANDOM_ROUNDS = 120
|
||||
N_INTELLIGENT_ROUNDS = 300
|
||||
training_rounds("rand_blue_win.csv", "rand_red_win.csv", rand_rounds, N_RANDOM_ROUNDS)
|
||||
training_rounds("intel_blue_win.csv", "intel_red_win.csv", intelligent_rounds, N_INTELLIGENT_ROUNDS)
|
21
view_snapshot.py
Normal file
21
view_snapshot.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
import argparse
|
||||
import pickle
|
||||
from etc.gamestate import GameState
|
||||
from typing import List
|
||||
|
||||
parser = argparse.ArgumentParser(description='View game snapshots.')
|
||||
parser.add_argument('snapshot_path', metavar='PATH', type=str, nargs=1,
|
||||
help='path to snapshot file (.bin)')
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
with open(args.snapshot_path[0], "rb") as f:
|
||||
gamestates: List[GameState] = pickle.load(f)
|
||||
for gamestate in gamestates:
|
||||
print(gamestate)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
34
view_training_data.py
Normal file
34
view_training_data.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import json
|
||||
import pickle
|
||||
|
||||
from etc.util import NoCopyShelf, RecursiveDict
|
||||
from etc.messages import BLUE_MESSAGES
|
||||
|
||||
|
||||
def main() -> None:
|
||||
data = NoCopyShelf.open(
|
||||
"blue_training.bin",
|
||||
protocol=pickle.HIGHEST_PROTOCOL,
|
||||
writeback=True # Yes this makes it less performant, but it will be more readable.
|
||||
)['data']
|
||||
|
||||
for x in data:
|
||||
for y in data[x]:
|
||||
for z in data[x][y]:
|
||||
for w in data[x][y][z]:
|
||||
d: RecursiveDict = data[x][y][z][w]
|
||||
d_ = []
|
||||
samples = []
|
||||
for k in range(0, len(BLUE_MESSAGES)):
|
||||
if k in d.keys():
|
||||
samples.append(str(d[k][0]).ljust(4))
|
||||
d_.append("{:.2f}".format(d[k][1] / d[k][0]).ljust(7))
|
||||
else:
|
||||
samples.append(str(0).ljust(4))
|
||||
d_.append("_NaN_".ljust(7))
|
||||
print(f"(red_op={x},blue_op={y},red_f={z},blue_e={w}) | {d_} | {samples}")
|
||||
# print(json.dumps(data, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user