How to split up dependent events in SimPy - simpy

I want to implement a discrete-event maintenance scheduling simulation in which some maintenance activities must happen whenever another one happens.
For example, if walls are repainted every 5 years, and dry-lining is replaced every 14 years, then walls must be repainted whenever the dry-lining is replaced and the clock restarted.
yr 5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls
...
I'm not sure whether I should implement each activity as a process which refers to the dependent process, or if "wall maintenance" should be a process with the logic handled internally, or some other way of.
The code I have has each activity as a process with the dependent process stored as an attribute but I feel like I'm probably missing the correct way of doing this as I'm seeing events happen twice in the same year.

You should always start with a very simple (and wrong) implementation just to get a better understanding of your use-case and a feeling how everything works, e.g.:
import simpy
def paint_walls(env, interval):
while True:
yield env.timeout(interval)
print('yr %2d: paint walls' % env.now)
def replace_dry_lining(env, interval):
while True:
yield env.timeout(interval)
print('yr %d: replace dry-lining' % env.now)
env = simpy.Environment()
env.process(paint_walls(env, interval=5))
env.process(replace_dry_lining(env, interval=14))
env.run(until=20)
Output:
yr 5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 15: paint walls
Then you can start extending/modifying your simulation. Here are two possibilities how your problem can be modeled:
Variant A
We keep using two separate processes but need a way to exchange the event "dry-lining replaced" between them, so that we can also paint the wall:
import simpy
class Maintenance:
PAINT_WALL_INTERVAL = 5
REPLACE_DRY_LINING_INTERVAL= 14
def __init__(self, env):
self.env = env
self.dry_lining_replaced = env.event()
self.p_paint_walls = env.process(self.paint_walls())
self.p_replace_dry_lining = env.process(self.replace_dry_lining())
def paint_walls(self):
timeout = self.PAINT_WALL_INTERVAL
while True:
yield self.env.timeout(timeout) | self.dry_lining_replaced
print('yr %2d: paint walls' % self.env.now)
def replace_dry_lining(self):
timeout = self.REPLACE_DRY_LINING_INTERVAL
while True:
yield self.env.timeout(timeout)
print('yr %2d: replace dry-lining' % self.env.now)
self.dry_lining_replaced.succeed()
self.dry_lining_replaced = self.env.event()
env = simpy.Environment()
m = Maintenance(env)
env.run(until=20)
Output:
yr 5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls
Variant B
We can also model it with just one process that waits for either a "paint walls" or a "replace dry-lining" event:
import simpy
def maintenance(env):
PAINT_WALL_INTERVAL = 5
REPLACE_DRY_LINING_INTERVAL = 14
paint_wall = env.timeout(PAINT_WALL_INTERVAL)
replace_dry_lining = env.timeout(REPLACE_DRY_LINING_INTERVAL)
while True:
results = yield paint_wall | replace_dry_lining
do_paint = paint_wall in results
do_replace = replace_dry_lining in results
if do_replace:
print('yr %2d: replace dry-lining' % env.now)
replace_dry_lining = env.timeout(REPLACE_DRY_LINING_INTERVAL)
if do_paint or do_replace:
print('yr %2d: paint walls' % env.now)
paint_wall = env.timeout(PAINT_WALL_INTERVAL)
env = simpy.Environment()
env.process(maintenance(env))
env.run(until=20)
Output:
yr 5: paint walls
yr 10: paint walls
yr 14: replace dry-lining
yr 14: paint walls
yr 19: paint walls

This is the approach I took in the end:
import simpy
from simpy.events import Interrupt
class Construction(object):
def __init__(self, name, components):
self.name = name
self.components = components
self.link_components()
def link_components(self):
"""Link each component to the next outermost component
"""
for i, component in enumerate(self.components):
try:
component.dependent = self.components[i+1]
except IndexError:
component.dependent = None
class Component(object):
def __init__(self, env, name, lifespan):
"""Represents a component used in a construction.
"""
self.env = env
self.name = name
self.lifespan = lifespan
self.action = env.process(self.run())
def run(self):
while True:
try:
yield self.env.timeout(self.lifespan)
self.replace()
except Interrupt: # don't replace
pass
def replace(self):
print "yr %d: replace %s" % (env.now, self.name)
if self.dependent:
self.dependent.action.interrupt() # stop the dependent process
self.dependent.replace() # replace the dependent component
env = simpy.Environment()
components = [Component(env, 'structure', 60),
Component(env, 'dry-lining', 14),
Component(env, 'paint', 5)]
wall = Construction('wall', components)
env.run(until=65)

Related

How to use inheritance to level up a character

# Import Modules
from Dice import dice
d10 = dice(10,1) #I use dice, but you could just as easily use random.randint()
d4 = dice(4,1)
d20 = dice(20,1)
# Assign Classes
class Player_Character:
inventory = []
def __init__(self, HP, MaxHP, AC, ToHitAdjustment, Surprise_Adjustment,
Initiative_Adjustment, Exp, \
MaxExp, Gold, Damage):
self.HP = int(HP)
self.MaxHP = int(MaxHP)
self.AC = int(AC)
self.ToHitAdjustment = int(ToHitAdjustment)
self.Surprise_Adjustment = int(Surprise_Adjustment)
self.Initiative_Adjustment = int(Initiative_Adjustment)
self.Exp = int(Exp)
self.MaxExp = int(MaxExp)
self.Gold = int(Gold)
self.Damage = int(Damage)
this next section may be where the error is, although I doubt it because when I run through the code the character is assigned exp and gold and they seem to grow at the correct rate.
def attack(self, goblin, Player_Character):
Player_attack_roll = d20.die_roll() + Player_Character.ToHitAdjustment
if (Player_attack_roll >= goblin.AC):
print('you did', Player_Character.Damage, 'damage')
goblin.HP -= Player_Character.Damage
if (goblin.HP <= 0):
print("congratulations you killed the goblin")
Player_Character.Exp += goblin.ExpReward
if (Player_Character.Exp >= Player_Character.MaxExp):
print("Congratulations you have leveled up")
Player_Character.LevelUp()
print('you have', Player_Character.Exp, 'exp ')
Player_Character.Gold += goblin.Gold
print("you have", Player_Character.Gold, 'gold ')
del goblin
else:
print("You miss ")
def flee(self):
print("you run away ")
quit()
def heal(self, Player_Character):
Player_Character.HP += d10.die_roll()
if Player_Character.HP >= Player_Character.MaxHP:
Player_Character.HP = Player_Character.MaxHP
print("You have reached max HP ")
this may be where the error is, I expect it to assign the variable name 'MrHezy' which was previously assigned to Player_Character to FighterLvl1, but when I run the code it continuously says that I am leveling up, which I am unsure if that means that the character is remaining as Player_Character, or if there is something else wrong
def LevelUp(self):
MrHezy = FighterLvl1(25, 25, 15, 10, 2, 2, 25, 75, 20, d10.die_roll()+5)
This next section has code is where my question of inheritance comes into play. Even though FighterLvl1 has inherited from (Player_Character) my IDE says things like "unresolved attribute reference 'Exp' for FighterLvl1" Also I am unsure if adding the 'attack' method under FighterLvl1 does anything; I expect it to overwrite the 'attack' method from Player_Character
class FighterLvl1(Player_Character):
def attack(self, goblin, Player_Character):
Player_attack_roll = d20.die_roll() + Player_Character.ToHitAdjustment
if (Player_attack_roll >= goblin.AC):
print('you did', Player_Character.Damage, 'damage')
goblin.HP -= Player_Character.Damage
if (goblin.HP <= 0):
print("congratulations you killed the goblin")
FighterLvl1.Exp += goblin.ExpReward
if (FighterLvl1.Exp >= FighterLvl1.MaxExp):
print("Congratulations you have leveled up")
FighterLvl1.LevelUp()
print('you have', FighterLvl1.Exp, 'exp ')
FighterLvl1.Gold += goblin.Gold
print("you have", FighterLvl1.Gold, 'gold ')
del goblin
def LevelUp(self):
MrHezy = FighterLvl2(Player_Character)
class FighterLvl2(Player_Character):
def LevelUp(self):
MrHezy = FighterLvl3(Player_Character)
class FighterLvl3(Player_Character):
def MaxLevel(self):
print("Congratulations you are level 3, this is the highest programmed level in the game
")
class goblin:
def __init__(self, HP, MaxHP, AC, ToHitAdjustment, Surprise_Adjustment,
Initiative_Adjustment, \
ExpReward, Gold, Damage):
self.HP = int(HP)
self.MaxHP = int(MaxHP)
self.AC = int(AC)
self.ToHitAdjustment = int(ToHitAdjustment)
self.Surprise_Adjustment = int(Surprise_Adjustment)
self.Initiative_Adjustment = int(Initiative_Adjustment)
self.ExpReward = int(ExpReward)
self.Gold = int(Gold)
self.Damage = int(Damage)
def attack(self, Player_Character, goblin):
if (goblin.HP <= 0):
print("the goblin is inert")
else:
goblin_attack_roll = d20.die_roll() + goblin.ToHitAdjustment
if (goblin_attack_roll >= Player_Character.AC):
goblin_damage = d4.die_roll()
print("you take", goblin_damage, 'damage')
Player_Character.HP -= goblin_damage
if (Player_Character.HP <= 0):
print("oh dear you have died")
del Player_Character
quit()
else:
print("the goblin misses ")
MrHezy = Player_Character(20, 20, 10, 5, 0, 0, 0, 25, 0, d10.die_roll())
def spawn_goblin(goblin):
G1 = goblin(5, 10, 8, 2, 0, 0, 25, 5, d4.die_roll())
return G1
goblin1 = spawn_goblin(goblin)
def battle(goblin1):
# user input
player_initiative_adjustment = MrHezy.Initiative_Adjustment
monster_initiative_adjustment = goblin1.Initiative_Adjustment
#define while loop for the battle
while True:
if (goblin1.HP <= 0):
print('oh dear looks like we need a new goblin')
break
#use random.randint(a,b) to generate player and monster base initiative
player_base_initiative = d10.die_roll()
monster_base_initiative = d10.die_roll()
#subtract the adjustment to get player and monster initiative
player_initiative = player_base_initiative - player_initiative_adjustment
monster_initiative = monster_base_initiative - monster_initiative_adjustment
#compare the initiatives and display the results
if (player_initiative < monster_initiative):
attack_flee_heal = input("congratulations you go first. Would you like to attack,
flee, or heal?")
if attack_flee_heal == 'attack':
MrHezy.attack(goblin1, MrHezy)
elif attack_flee_heal == 'heal':
MrHezy.heal(MrHezy)
print("the goblin attacks")
goblin1.attack(MrHezy)
elif attack_flee_heal == 'flee':
MrHezy.flee()
else:
print("uhoh, the monsters go first, they attack!")
goblin1.attack(MrHezy, goblin1)
attack_flee_heal = input("Would you like to attack, flee, or heal? ")
if attack_flee_heal == 'attack':
MrHezy.attack(goblin1, MrHezy)
elif attack_flee_heal == 'heal':
MrHezy.heal(MrHezy)
print("the goblin attacks")
goblin1.attack(MrHezy, goblin1)
elif attack_flee_heal == 'flee':
MrHezy.flee()
#main game loop
while True:
spawn_goblin(goblin)
battle(goblin1)
print("spawn another goblin")
goblin1.HP = 0
goblin1.HP += d10.die_roll()
This code runs perfectly well, please enter it into your IDE.
The problem that I have is a logic error, when a goblin is killed instead of leveling up the character first to level 1 then level 2 then level 3 instead the character levels up to level 1 over and over and over.
I agree with Danny Varod, in that there are very many improvements that need to be made, and I definitely recommend following his advice.
However if you are simply looking to make what you have work you need to specify in your level up methods that you are referencing the global MrHezy variable.
eg:
def LevelUp(self):
global MrHezy
MrHezy = FighterLvl2(Player_Character)
The level up replaces a variable in the object, what you should do, if you want to use a class per level (not necessarily a good idea) is:
Create a Character class.
In Character class define an implementation field.
Initialize implementation field e.g. implementation = SomeSpeciesLevel1().
To "level-up": implementation = implementation.level_up()
This way you have a constant wrapper that doesn't change which wraps the level-changing implementation.
A better design would be to have one class with functionality based on level-dependant formulas.
class CharImpl:
#abstractmethod
def can_level_up() -> bool:
return NotImplemented
#abstractmethod
def next_level() -> CharImpl:
return NotImplemented
pass
class Character:
def __init__(initial_impl: CharImpl=None):
self.impl = initial_impl.copy() if initial_impl else HumanLevel1()
pass
def level_up():
if self.impl.can_level_up():
self.impl = self.impl.next_level()
pass
pass
class Human(CharImpl):
pass
class HumanLevel1(Human):
def __init__(src: Human):
# TODO: copy relevant fields from src
pass
def can_level_up() -> bool:
return True
def next_level() -> CharImpl:
return HumanLevel2(self)
pass
class HumanLevel2(Human):
def __init__(src: Human):
# TODO: copy relevant fields
pass
def can_level_up() -> bool:
return False
def next_level() -> CharImpl:
return self
pass

How to use nested loops propely

I have this nested loop in my project (it's a lot more complicated of course I just simplify it so you can see what I mean). I know there is no label and goto in python, I just want to show what I want to do.
From line #goto third I want go back to place where you can see #label third.
I tried different setups of my loops but they never do what I want
import time
onoff = "on"
t=0
while onoff == "on":
#label first
for x in range (5):
print("first loop")
time.sleep(1)
for y in range (5):
print("second loop")
time.sleep(1)
p = 0 #for testing
t=0 #for testing
if p != 5:
if t == 0:
print("third loop")
time.sleep(1)
p2 = 5 #for testing
t=0
if p2 != 5: #label third
if t == 0:
print("go back to first loop")
time.sleep(1)
#goto first
else:
print("lock")
#lock.acquire()
else:
if t == 0:
print("go back to third loop")
p2 = 3
time.sleep(1)
#goto third
else:
print("lock")
#lock.acquire()
else:
print("lock")
#lock.acquire()
Every path in this nested loops seems to work fine but I want my loop to go back to #label third from #goto third and it goes back to #label first. How can I change my loops to make it possible?
Actions like goto first which break 'for' loops are evil in many ways. While loops are more elegant, but maybe a 'state machine' like solution is better for you. Something like:
state = 0
while is_on:
if state == 0: # do outer loop things
<do things>
state = 1 # to do inner loop things
elif state == 1:
n = 0
# do inner loop things
n += 1
if n == 5:
state = 0
elif state == 2: # do even more nested things
p = 0
if <some condition>:
state = 0
p += 1
if p == 5:
state = <whatever>
A state machine permits much more flexibility. Also, it won't cause as much indentation as nested loop. If the complexity gets larger, there are some libraries which can help you. Interesting links on Finite State Machines (FSM):
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/StateMachine.html
https://www.python-course.eu/finite_state_machine.php

Python 3 - Object Oriented Programming - Classes & Functions

I have been studying Python 3 for a few months now and have just begun looking at Object Oriented programming. I asked a question on Stack Overflow about a text adventure game I was trying to write and it was suggested that it would be better to use OOP.
I did some searching as I wanted a very simple combat system that could be used in the game. I have the basic framework from the game but I would like some help with the combat system side.
Here is the code I have so far:
import random
import time as t
class Die:
def __init__(self, sides = 6):
self.sides = sides
def roll(self):
return random.randint(1, self.sides)
class Player:
def __init__(self):
self.hit_points = 10
def take_hit(self):
self.hit_points -= 2
class Enemy:
def __init__(self):
self.hit_points = 10
def take_hit(self):
self.hit_points -= 2
p = Player()
e = Enemy()
d = Die(6)
battle = 1
while battle != 0:
human = d.roll() + 6
print("Your hit score: ",human)
enemy = d.roll() + 6
print("Enemy hit score: ",enemy)
if human > enemy:
e.take_hit()
print("Your hit points remaining: ",p.hit_points)
print("Enemy points remaining: ", e.hit_points)
if e.hit_points == 0:
battle = 0
t.sleep(2)
elif human < enemy:
p.take_hit()
print("Your hit points remaining: ",p.hit_points)
print("Enemy points remaining: ", e.hit_points)
if p.hit_points == 0:
battle = 0
t.sleep(2)
The Die class is to simulate a six sided die, and player and enemy are used for game characters. After that the code is used to roll random number and the highest number win the round until either the player or the enemy reach zero points.
I am unsure how to use those last lines of code after the three classes and create a class from it also.
I need to be able to run a battle multiple times in the game and also store the player score with points deducted after each battle.
I really like and enjoy the use of objects and would like to become better, so any help with this is very much appreciated.
You can re-use a class and create more instances from one template. The Player and Enemy class have a identical functionality. You can create different instances from one class simply using the __init__ method with different arguments.
import random
import time as t
class Player:
def __init__(self, hit_points, sides):
self.hit_points = hit_points
self.sides = sides
def take_hit(self):
self.hit_points -= 2
def roll(self):
return random.randint(1, self.sides)
p = Player(hit_points=10, sides=6)
e = Player(hit_points=8, sides=6)
battle = 1
while battle != 0:
human = p.roll()
print("Your hit score: ",human)
enemy = e.roll()
print("Enemy hit score: ",enemy)
if human > enemy:
e.take_hit()
print("Your hit points remaining: ",p.hit_points)
print("Enemy points remaining: ", e.hit_points)
elif human < enemy:
p.take_hit()
print("Your hit points remaining: ",p.hit_points)
print("Enemy points remaining: ", e.hit_points)
t.sleep(2)
if e.hit_points == 0 or p.hit_points == 0:
battle = 0

Using parameters throughout multiple subroutines python

I am currently trying to write a program where the user inputs what colour carpet they would like and then depending on what carpet and what area they have inputted it will give them a different price, However my current issue is using the parameters correctly since I am very new to both using python and to programming. The current program specification requires the use of subroutines. An example problem is with my last line main(exit1) where it says that exit1 isn't defined and if I try to edit the code to main() it says that exit1 is required. Any help will be greatly appreciated.
def prices():
PriceA = 40
PriceB = 50
PriceC = 60
def main(exit1):
exit1 = False
while exit1 == False:
carpet = str(input("What carpet would you like blue, red or yellow "))
carpet = carpet.upper()
if carpet == "X":
exit1 = True
else:
width = int(input("What is the width of the room in M^2 "))
height = int(input("What is the height of the room in M^2 "))
area = width * height
if carpet == "BLUE":
a(area)
elif carpet == "RED":
b(area)
elif carpet == "YELLOW":
c(area)
else:
print("Invalid carpet")
cost = 0
output(cost)
def output(cost, exit1):
print ("the price is £", cost)
def a(area, PriceA):
cost = PriceA * area
output(cost)
def b(area, PriceB):
cost = PriceB * area
output(cost)
def c(area, PriceC):
cost = PriceC * area
output(cost)
main(exit1)
You are trying to pass in a variable as an argument to the main function on the last line of your program, but you have not defined the exit1 variable before that line is executed (you define it within the scope of the main function). To achieve what you want to do you don't need to provide main with the exit1 argument, so you can just remove it from the function definition and the function call.
def main():
...
...
main()
The problem you are having is that a variable needs to be defined before it can be entered into a function. I fixed the code and made a few corrections it also appeared that you had an indentation error and didn't feed in enough arguments into your functions when you called them. The code ive written is a bit easier to follwo. If I have done anything you dont understand send me a message, otherwise I recommend sites such as code acadamy and pythonprogramming .net.
def main():
PriceA = 40
PriceB = 50
PriceC = 60
while True:
carpet = str(input('What carpet would you like blue, red or yellow? '))
carpet = carpet.upper()
if carpet == 'X':
break
else:
width = int(input("What is the width of the room in M? "))
height = int(input("What is the height of the room in M? "))
area = width * height
if carpet == "BLUE":
output(area,PriceA)
elif carpet == "RED":
output(area,PriceB)
elif carpet == "YELLOW":
output(area,PriceC)
else:
print('Invalid carpet')
def output(area,Price):
cost = area*Price
print('the price is £',cost)
main()

Value lost between functions?

I am working on a simple text based battle game, and I have come across a(nother) problem. I have a main function and a battle function and in the battle function once you finish the battle, you gain a certain amount of XP, which is then (or should be) remembered by changing the value of player1.xp. Right after the battle sequence I set it so in the code if player1.xp >= 1 it will change the value of player1.level to "2". Though, whenever I run the code, for some reason the xp value gets lost when the battle function is finished. I know this because it prints out the value of player1.xp after the battle function, and it says "0". Here is the code:
import random
xp1 = random.randint(1,2)
class player:
def __init__ (self, name, health, strength, defense, potion, xp, level):
self.health = health
self.strength = strength
self.defense = defense
self.name = name
self.potion = potion
self.xp = xp
self.level = level
def changeHealth(self, h):
self.health = h
def addLevel(self):
self.level += 1
def subHealth(self, num):
self.health -= num
return self.health
def subPotion(self):
self.potion -= 1
return self.health
def addPotion(self, num1):
self.potion += num1
def addHealth(self):
self.health +=2
def addXP(self):
self.xp += xp1
def battle1(enemy, player1, name1):
player1 = player(name1, player1.health, player1.strength, player1.defense, player1.potion, player1.xp, player1.level)
enemy = player("Rat", enemy.health, enemy.strength, enemy.defense, player1.potion, enemy.xp, enemy.level)
print("Fight!")
s = 0
while s == 0:
attack =input("Type 1 to attack, type 2 to use a potion.")
if attack == "1":
enemy.subHealth(15)
elif attack == "2":
if player1.potion > 0:
print("You used a potion.")
elif player1.potion <= 0:
print("You don't have any potions! You are forced to attack.")
enemy.subHealth(15)
else:
print("Your life depends on this!")
if enemy.health <= 0:
print("Congratulations, you won! You recieved", xp1, "xp!")
player1.addXP()
s = 2
def main():
name1 = input("What would you like your name to be?")
print("Hello,", name1, "you are on a quest to save otis from the evil Dongus. You must slay him, or Otis will poop.")
player1 = player(name1, 10, 2, 1, 0, 0, 1)
enemy = player("Rat", 15, 0, 0, 0, 0, 0)
pick = input("You were walking along the path and found a potion! Press 'p' to pick it up.")
if pick == "p":
print("You added a potion to your inventory.")
player1.addPotion = 1
else:
print("You have no potions, you should probably pick this one up.")
print("You added a potion to your inventory.")
player1.addPotion = 1
battle1(enemy, player1, name1)
print(player1.xp)
if player1.xp >= 1:
print("You leveled up. You are now level 2.")
player1.addLevel()
else:
print("You have 0 xp.")
main()
Does anybody have a clue why this is happening? Thanks!
In main you create two player (should be Player) instances, and pass them to battle1:
player1 = player(name1, 10, 2, 1, 0, 1)
enemy = player("Rat", 15, 0, 0, 0, 0)
battle1(enemy, player1, name1)
In battle1 you create two new player instances, based on the two argument instances, modify the new instances, and don't return anything:
# take three parameters (one of which is unnecessary)
def battle1(enemy, player1, name1):
# replace two of the arguments with copies
player1 = player(name1, player1.health, player1.strength, player1.defense, player1.xp, player1.level)
enemy = player("Rat", enemy.health, enemy.strength, enemy.defense, enemy.xp, enemy.level)
# original arguments now inaccessible
It is not clear why you expected that this would work, but the minimal fix is to stop creating new instances in battle1. Also, it might be clearer if you moved xp1 = random.randint(1,2) inside battle1.

Resources