I am working on building an AI in Python3 to play Starcraft II using python-sc2 in BurnySC2 . How do I concisely assign values to different variables based on another variable? I've kicked around a few things, but none of them seem to do what I want. All of the buildings and units are stored as a UnitTypeId with different names for similar unit/building types based on the race.
For instance, I want to do something like:
ai_race = 1
def ai_tech_tree(ai_type)
switcher = {
Race.1: ai_townhall = UnitTypeId.townhall1;
ai_worker = UnitTypeId.worker1;
ai_fighter = UnitTypeId.fighter1,
Race.2: ai_townhall = UnitTypeId.townhall2;
ai_worker = UnitTypeId.worker2;
ai_fighter = UnitTypeId.fighter2
}
ai_tech_tree(ai_race)
class MyBot(BotAI): # inherits from BotAI
async def on_step(self, iteration: int): # method called every step of the game
if self.townhalls: # does AI have a townhall?
town_hall = self.townhalls.random # select random AI-owned townhall
if town_hall.is_idle and self.can_afford(ai_worker): # is AI's townhall idle and can AI afford a worker?
town_hall.train(ai_worker) # train a worker
but you can't seem to declare a variable in the CASES statement. The closest I got to something that at least worked is doing a CASES for each unit/building type, but that was large and clunky.
ai_race = 1
def get_ai_townhall(ai_type)
switcher = {
Race.1: UnitTypeId.townhall1,
Race.2: UnitTypeId.townhall2,
Race.3: UnitTypeId.townhall3
...
}
def get_ai_worker(ai_type)
switcher = {
Race.1: UnitTypeId.worker1,
Race.2: UnitTypeId.worker2,
Race.3: UnitTypeId.worker3
...
}
...
ai_townhall = get_ai_townhall(ai_race)
ai_worker = get_ai_worker(ai_race)
...
I am hoping there is a more elegant solution.
Related
I have a simple simulation of a company using agentpy. It is doing a great job of modelling promotions, but I'd also like to be able to model people joining and leaving the company.
Leaving is easy, I can just set a flag to inactive or something, but joining is more complicated. Do I need to make a bunch of agents during setup and set their state to as yet unknown, or can I create an agent during a step and add them in?
The person class is defined like this:
class PersonAgent(ap.Agent):
def setup(self):
p = PEOPLE_DF.iloc[self.id] # 👈 the existing people are in a spreadsheet
self.name = p.full_name
self.gender = get_gender(p.gender)
self.bvn_rank = get_rank(p.b_rank)
# self.capability = float(p.capability)
print()
def rank_transition(self):
self.bvn_rank = transition(self.b_rank, self.gender)
I'm guessing I'd do something with the __init__, but I've had no luck figuring that out.
Yes, you can initiate new agents during a simulation step.
Here are some examples:
class MyModel(ap.Model):
def setup(self):
# Initiate agents at the start of the simulation
self.agents = ap.AgentList(self, 10, PersonAgent)
def step(self):
# Create new agents during a simulation step
self.single_new_agent = PersonAgent(self)
self.list_of_new_agents = ap.AgentList(self, 10, PersonAgent)
# Add them to the original list (if you want)
self.agents.append(self.single_new_agent)
self.agents.extend(self.list_of_new_agents)
https://github.com/pytransitions/transitions
I was trying with the batman = NarcolepticSuperhero("Batman") example in the documentation.
But let's say if we have 100 batmans to handle, My approach is to create a list of every batman object and track their states individually using a loop.
IMO this is very naive.
Can some-one please suggest some effective solution.
Thanks
Assuming all NarcolepticSuperheros should behave the same, I recommend to use one Machine for all 'batmen' instead of instantiating NarcolepticSuperhero a hundred times. This way, you can use Machine.dispatch to trigger an event on ALL models instead of looping through a list of superheroes (under the hood Machine also just loops through the model list though). I condensed the mentioned example a bit (all transitions in one list), removed the machine that was bound to NarcolepticSuperheros and introduced a SuperheroDen. Concerning state checking, you could use a dictionary to keep track of how many heroes are currently in what state. I added a hero_log to NarcolepticSuperhero and every time a hero changes its state, it will first log out of its current state and then log in with its new state.
from transitions import Machine
from collections import defaultdict
import random
class NarcolepticSuperhero(object):
hero_log = defaultdict(list)
def __init__(self, name):
self.name = name
self.kittens_rescued = 0
def update_journal(self):
self.kittens_rescued += 1
#property
def is_exhausted(self):
return random.random() < 0.5
#staticmethod
def change_into_super_secret_costume():
print("Beauty, eh?")
def log_out(self):
self.hero_log[self.state].remove(self)
def log_in(self):
self.hero_log[self.state].append(self)
class SuperheroDen(Machine):
states = ['asleep', 'hanging out', 'hungry', 'sweaty', 'saving the world']
transitions = [
['wake_up', 'asleep', 'hanging out'],
['work_out', 'hanging out', 'hungry'],
['eat', 'hungry', 'hanging out'],
['nap', '*', 'asleep'],
dict(trigger='distress_call', source='*', dest='saving the world', before='change_into_super_secret_costume'),
dict(trigger='complete_mission', source='saving the world', dest='sweaty', after='update_journal'),
dict(trigger='clean_up', source='sweaty', dest='asleep', conditions=['is_exhausted']),
['clean_up', 'sweaty', 'hanging out'],
]
def __init__(self):
super().__init__(model=[NarcolepticSuperhero('Clone warrior') for i in range(100)],
states=self.states,
transitions=self.transitions,
# tell each model to 'log_out' right before state change
before_state_change='log_out',
# and to 'log_in' right after
after_state_change='log_in',
initial='asleep')
# since our super heroes are asleep (and 'spawn' in their state), we have to log them in the first time
for model in self.models:
NarcolepticSuperhero.hero_log[self.initial].append(model)
machine = SuperheroDen()
# trigger event 'wake_up' on all models
machine.dispatch('wake_up')
assert len(NarcolepticSuperhero.hero_log['asleep']) == 0
assert len(NarcolepticSuperhero.hero_log['hanging out']) == 100
for i in range(10):
machine.models[i].work_out()
assert len(NarcolepticSuperhero.hero_log['hanging out']) == 90
assert len(NarcolepticSuperhero.hero_log['hungry']) == 10
assert machine.models[0] in NarcolepticSuperhero.hero_log['hungry']
What you suggest sounds reasonable to me.
heros = [NarcolepticSuperhero(f"Batman{i:02d}")
for i in range(100)]
Then either loop over all of them,
or use a while True: loop that sleeps a moment
then picks a hero at random.
Either way, you will have a hero
that you can trigger some new transition on.
I'm putting together something that randomly changes parameters in math problems. As it is now, each problem is its own function—but these functions are very similar. Each of them takes some unformatted text...
Sally has {num apples} apples. Billy takes {num_taken}. How many apples does Sally have?
(... with the possibility of other arguments in the future.) And each of them returns formatted text. The difference is in what they do in between, and the number of arguments that format() needs in order to appropriately format the unformatted text. Some of them them generate the equations of planes, other just have random numbers, while others might actually need to perform certain operations on certain mathematical structures.
Eventually, too, each problem-function will also generate an answer (and return a dictionary instead of just text).
Here is an example:
# Problem 41
def p41(unformatted_text):
plane_1 = math_structures.plane()
plane_2 = math_structures.plane()
point = math_structures.vector()
formatted_text = unformatted_text.format(
plane_1=plane_1.get_eqn(),
plane_2=plane_2.get_eqn(),
point=point.get_eqn()
)
return formatted_text
# Problem 52
def p52(unformatted_text):
num_prof = random.randrange(5, 25)
num_teaching = random.randrange(2,5)
num_courses = num_prof * num_teaching
formatted_text = unformatted_text.format(
num_prof=num_prof,
num_teaching=num_teaching,
num_courses=num_courses
)
return formatted_text
# Problem 54
def p54(unformatted_text):
range_1 = math_structures.interval()
range_2 = math_structures.interval()
relational = math_structures.random_relational()
formatted_text = unformatted_text.format(
range_1=range_1.get_eqn(),
range_2=range_2.get_eqn(),
relational=relational
)
return formatted_text
Whenever there's a similarity in structure like this, there's something that can be done to make the code more efficient. I've never had the occasion to use decorators before, but I think this might be one. I would create a decorator which takes two arguments: some function and some unformatted text. The inner-function would spit out a dictionary of some numbers or equations or whatever. The decorated-function would format the text with this dictionary, and spit out the formatted text:
def decorator(math_function):
def decorated_function(unformatted_text, *og_args, **og_kwargs):
formatting_arguments = math_function(*og_args, **og_kwargs)
formatted_text = unformatted_text.format(**formatting_arguments)
return formatted_text
And then I could have:
#decorator
def p41():
plane_1 = math_structures.plane()
plane_2 = math_structures.plane()
point = math_structures.vector()
format_dict = {
'plane_1':plane_1,
'plane_2':plane_2,
'point':point
}
return format_dict
#decorator
def p52():
num_prof = random.randrange(5, 25)
num_teaching = random.randrange(2,5)
num_courses = num_prof * num_teaching
format_dict = {
'num_prof':num_prof,
'num_teaching':num_teaching,
'num_courses':num_courses
}
return format_dict
Does this all sound about right? (Now that I see I have to put #decorator in front of every problem definition, I suspect there's something even more efficient. Maybe directly feed the function to the decorator when the text needs to be generated, instead of when things are defined?
text_gen.py:
import random
import problems
from decorators import decorator
problem_list = ['p41', 'p52', 'p54']
while True:
input('Press enter to see a problem...')
problem = random.randrange(0, len(problem_list))
decorator(getattr(problems, problem_list[problem]))
Basically, I'd like to know if I'm on the right track, if I'm misunderstanding anything, or if there's a better way of doing this. :)
You could do decorators, but I think what you're moving toward is actually simpler. You're using a decorator to alter a function, but you could just as easily write a function that creates the function. Then store these problems in a list for random access (again, functions aren't special, no need for getargs, etc).
# problem function creation
def create_from_dict(**kwargs):
"""Creates simple problems with independent arguments"""
def format(string):
format_args = dict( (key,val()) for (key,val) in kwargs.items())
return string.format(**format_args)
return format
def create_from_function(func):
"""Creates more complex problems with dependent arguments"""
def format(string):
format_args = func()
return string.format(**format_args)
return format
# make my problems
problems = []
problems.append(create_from_dict(plane_1=lambda : math_structure.plane(),
plane_2=lambda : math_structure.plane(),
point=lambda : math_structures.vector()))
def p52():
num_prof = random.randrange(5, 25)
num_teaching = random.randrange(2,5)
num_courses = num_prof * num_teaching
format_dict = {
'num_prof':num_prof,
'num_teaching':num_teaching,
'num_courses':num_courses
}
return format_dict
problems.append(create_from_function(p52))
while True:
input('press enter to see a problem...')
problem = random.choice(problems)
I have been trying my hand at a NASCAR project, where I would have to use a class to create 20 unique vehicles and then have them race ( or to see who would reach 500 miles first, through the means of repeatedly choosing a different speed between 1 and 120 and adding it to an increasing odometer). I made what you see below and ran it, and it boots well into the Python IDLE. However, it will always tell me that NameError: name 'Driver_sponsor' is not defined. See, I have been facing this error for a while now, and I have tried placing the Driver_sponsor list into a class, placing it into the Main def and placing the keyword self. before it. No matter what I did, I faced this error. I am going to go back into my class book to see what I can do, but I am hoping that someone here can tell me what I am missing within my code, since, really, I am extremely lost.
from random import randint
import time
class Car:
def __init__(self,Driver_Name,Sponsor):
self.__Total_Odometer_Miles = 0
self.__Speed_Miles_Per_Hour = 0
self.__Driver_Name = Driver_Name
self.__Sponsor = Sponsor
self.__Driver = ('Drivers name Missing')
self.__Sponsor = ('Sponsor Missing')
self.__Driver_sponsor = {'A.J.Allmendinger:3M','Aric Almirola:Allegiant ','Trevpr Bayne:AMR ','Ryan Blaney:Camping World ','Clint Bowyer:Chevrolet ',
'Chris Buesher:Coca-Cola','Kurt Busch:Coca-light ','Kyle Busch:Credit One ','Landon Cassill:Ford','Matt DiBenedetto:FDP',
'Austin Dillon:','Ty Dillon:','Dale Earnhardt:Jacob Companies ','Chase Elliott: M & M ','Denny Hamlin: Microsoft ',
'Kevin Harvick:GoodYear ','Jimmie Johnson:Nationwide','Erik Jones:SUNOCO','Kasey Kahne:Toyota','Matt Kenseth:Visa ' }
def Name(self,Driver_Name):
self.__Driver_Name = Driver_Name
def Retrieve_Name(self):
return self.__Driver_Name
def __mutualize__(self):
self.__Total_Odometer_Miles = 0
self.__Speed_Miles_Per_Hour = 0
def sponsors(self):
self.__Driver_sponsor = {'A.J.Allmendinger:3M','Aric Almirola:Allegiant ','Trevpr Bayne:AMR ','Ryan Blaney:Camping World ','Clint Bowyer:Chevrolet ',
'Chris Buesher:Coca-Cola','Kurt Busch:Coca-light ','Kyle Busch:Credit One ','Landon Cassill:Ford','Matt DiBenedetto:FDP',
'Austin Dillon:','Ty Dillon:','Dale Earnhardt:Jacob Companies ','Chase Elliott: M & M ','Denny Hamlin: Microsoft ',
'Kevin Harvick:GoodYear ','Jimmie Johnson:Nationwide','Erik Jones:SUNOCO','Kasey Kahne:Toyota','Matt Kenseth:Visa ' }
def Retrieve_sponsor(self,Driver_sponsor):
return self.__Driver_sponsor
def main():
for key in Driver_sponsor():
CurrentCar = Car()
CurrentCar.Driver = key
CurrentCar.Sponsor = val
CurrentCar.MPH = randint(1,120)
time.sleep(.05)
time = 5
currentCar.ODT = 5
CurrentCar.ODT = CurrentCar.ODT + CurrentCar.MPH*Time
print(CurrentCar.Driver,CurrentCar.ODT)
if CurrentCar.ODT >= 500:
print('\ the winner is',key,'t\ sponsored by',val)
main()
There are a few issues with your code.
First, you're getting this error because you're calling a variable that isn't set.
But more importantly, you're trying to access the driver-sponsor dict before you've initialized an instance of Car (which currently only happens inside the loop that iterates over Driver_sponsor!).
If you want to loop over driver-sponsor pairs and initialize a new Car for each one, then do you really need the full Driver_sponsor dict initialized for every Car? If so, just pass it as an argument when constructing Car and populate self.__Driver_sponsor.
For example:
driver_sponsor_pairs = {'A.J.Allmendinger:3M',...,'Matt Kenseth:Visa'}
class Car:
def __init__(self, driver_sponsor):
# ...
self.driver_sponsor = driver_sponsor
CurrentCar = Car(driver_sponsor=driver_sponsor_pairs)
# now refer to CurrentCar.driver_sponsor
Second, you are only asking for key when looping over the Driver_sponsor dict, but you call on both key (for Driver) and val (for Sponsor) in each loop . Extract both key and val in your loop creation. You'll need the .items() dict method to get both values:
for key, val in driver_sponsor_pairs.items():
...
Third, your Car __init__ expects Driver and Sponsor arguments, but you try to define CurrentCar = Car() and then populate CurrentCar.Driver and CurrentCar.Sponsor afterwards. Continuing with the above updates, try instead:
CurrentCar = Car(Driver=key, Sponsor=val)
Fourth, you won't need the Retrieve_sponsor() method if you already have the .Sponsor attribute set.
There are a lot of misunderstandings here about Object syntax and design. You may find it frustrating to try and debug at this level of complexity. I would suggest starting very simply, say, with Car() having just one attribute. Test that, make sure it works as you want, and then build more attributes and methods from there.
I am trying to teach myself Python and have created a file which runs through various questions sets spread out across classes. At the end of this file I want to summarise all of my raw inputs.
Unfortunately, I am struggling to access these values from a separate class. I have broken my coding down into a test example to demonstrate the structure of my program:
class QuestionSet(object):
next_set = 'first_set'
class ClaimEngine(QuestionSet):
def current_set(self):
last_set = "blank"
while_count = int(0)
quizset = Sets.subsets
ParentSet = QuestionSet()
while ParentSet.next_set != last_set and int(while_count)<50:
quizset[ParentSet.next_set].questioning()
while_count = while_count+1
class FirstSet(QuestionSet):
def questioning(self):
value1 = raw_input("Val1")
QuestionSet.next_set = "second_set"
class SecondSet(QuestionSet):
def questioning(self):
value2 = raw_input("Val2")
QuestionSet.next_set = "summary"
class Summary(QuestionSet):
print "test"
## I need to print a summary of my inputs here ##
## e.g. Print "The answer to value1 was:%r" %value1##
class Sets(object):
subsets = {
'first_set': FirstSet(),
'second_set': SecondSet(),
'summary': Summary()
}
I have tried defining within the Summary each class e.g. 1stSet = FirstSet() and then FirstSet.value1 etc but to no avail.
If anyone has any suggestions on how to retrieve these values that would be great as I have written a massive program full of questions and have fallen at the last hurdle!
Thank you.
The values you have in each function of the classes are not created as class members. For your application you need to create member variables which store the values within the class.
For example:
class FirstSet(QuestionSet):
def questioning(self):
self.value1 = raw_input("Val1")
QuestionSet.next_set = "second_set"
Now value1 is a member variable you can access it.
In the case of the example you have above it would probably be something like the line below to access the value1 from 'first_set'
subsets['first_set'].value1
If you're not familiar with this try
this tutorial