I'm trying to build a simulation for robotic behavior. I have a Robot parent abstract class, and a functioning NormalRobot subclass. I want to build a FaultyRobot subclass, possibly with something like partial "parallel" inheritance?
The parent class Robot has an abstact move() method, since I want all my Robot subclasses to be able to be called on with the same name move()
NormalRobot has a functioning method move() that simulates one "turn", of moving the specified distance in the specified direction. (multiple "turns" are simulated by calling move() multiple times)
FaultyRobot should also have a move() method, which is almost identical to the NormalRobot's move() except that at the start of the "turn" it randomly has a possibility of not moving and changing direction. Obviously I can do this by writing the randomness code (which i have in a helper method turn_is_faulty()) and copy-pasting the content of NormalRobot's move(). But obviously this is bad etiquete to have repeating code. Python inheritance makes it very easy to inhereit the move() method from the parent class Robot, but I need the move() from the parallel subclass NormalRobot.
I'm imagining that the code would look something like
def Class FaultyRobot:
def move(self):
if self.turn_is_faulty():
self.set_robot_direction(360 * random.random())
else:
#call the NormalRobot move()
Does Python allow some sort of partial "parallel" inheritance?
I would say "Faulty Robot" is just a "Normal Robot" with few faults.
import numpy as np
class NormalRobot():
def __init__(self, x):
self.x = x
def move(self, d):
self.x += d
def __repr__(self):
return f"Position: {self.x}"
class FaultyRobot(NormalRobot):
def __init__(self, x, is_faulty=True):
self.x = x
self.is_faulty = is_faulty
def turn_is_faulty(self):
return self.is_faulty
def move(self, d):
if self.turn_is_faulty():
self.x += np.random.random()
else:
super().move(d)
test:
n = NormalRobot(0)
f = FaultyRobot(0)
nf = FaultyRobot(0, False)
n.move(10)
nf.move(10)
f.move(10)
print (n, nf, f)
Output:
Position: 10 Position: 10 Position: 0.07103605819788694
You can put shared functionality into a mixin class.
import random
class AbstractRobot:
"""My 1 dimensional robot"""
def __init__(self, start_at):
self.pos = start_at
class RobotMoveMixin:
def move(self, delta):
self.pos += delta
print('moved to', self.pos)
class GoodRobot(RobotMoveMixin, AbstractRobot):
pass
class BadRobot(RobotMoveMixin, AbstractRobot):
def move(self, delta):
print("bad robot")
if random.choice((True, False)):
super().move(delta)
robot = BadRobot(0)
for _ in range(10):
robot.move(3)
Now GoodRobot and its inheritors get the good movement while BadRobot and its interitors get the messed up one.
thank you all for your help. I ended up finding another solution that I think may be interesting, so I am posting it. My solution doesn't use inheritance in any formal sense, so I suspect some of you may have though of something like it but not posted it, since it seems far off from the phrasing of my question. This is my own problem, since I saw this an a problem with inheritance, and so I assumed the solution would be found with better inheritance structure. Thankfully there is a simple workaround.
def Class FaultyRobot:
def move(self):
if self.turn_is_faulty():
self.set_robot_direction(360 * random.random())
else:
StandardRobot.move(self)
Python does indeed allow to call the move() of one subclass while defining the move() of a new subclass!
Related
With the following code
x=1.0
def update(dt):
space.step(dt)
def xprinter(self, x):
print (x)
return x+1
if __name__ == "__main__":
x=pyglet.clock.schedule(xprinter,x)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
My return is simply 1.0 over and over. I would like for the value to be updated with each call. What am I missing?
The design here is based on that your function rely on returning a result.
Which causes a problem because Pyglet's internal functions are in charge of executing that function at a interval, meaning you're not the one executing the call - and there for you're not the one getting that return value, Pyglet is.
And since there's no meaningful way for Pyglet to relay that returned value (there are ways, but they involve hooking in and overriding certain internal functions), you will never see that return value.
The quick and easy workaround would be to do:
x=1.0
def update(dt):
space.step(dt)
def xprinter(self):
global x
print(x)
x += 1
if __name__ == "__main__":
pyglet.clock.schedule(xprinter)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
This way, the schedule call will update the global variable x rather than returning the result of the math equation.
A more neat approach would be to define a class with the x attribute and pass a class instance to the pyglet.clock.schedule():
class player():
def __init__(self):
self.x = 0
def update(dt):
space.step(dt)
def xprinter(self, p):
print(p)
p.x += 1
if __name__ == "__main__":
p = player()
x = pyglet.clock.schedule(xprinter, p)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
And if I'm not completely out of the ball park, this would remember the value across clock ticks, because of the instance.
This is also usually what you'll be using the schedule for, doing player / game / animation updates.
I want to know how you can delete a instance within the class. I am trying del self, but it doesn't seem to work. Here's my code:
class Thing:
def __init__(self):
self.alive = True
self.age = 0
def update(self):
self.age += 1
def kill(self):
if self.age >= 10:
del self
def all(self):
self.update()
self.kill()
things = []
for i in range(0, 10):
thing.append(Thing())
while True:
for thing in things:
thing.all()
I specifically want to delete the instance inside the class. I have also replaced del self with self = None, but this statement doesn't seem to have any effect. How can I do this?
You can't do quite what you're asking for. Python's del statement doesn't work like that. What you can do however, is mark your instance as dead (you already have an attribute for this!), and then later, filter the list of objects to drop the dead ones:
class Thing:
def __init__(self):
self.alive = True # use this attribute!
self.age = 0
def update(self):
self.age += 1
def kill(self):
if self.age >= 10:
self.alive = False # change it's value here rather than messing around with del
def all(self):
self.update()
self.kill()
things = []
for i in range(0, 10):
things.append(Thing())
while True:
for thing in things:
thing.all()
things = [thing for thing in things if thing.alive] # filter the list
Note that the loops at the end of this code run forever with no output, even after all the Thing instances are dead. You might want to modify that so you can tell what's going on, or even change the while loop to check if there's are any objects left in things. Using while things instead of while True might be a reasonable approach!
I had the same trouble last time. I searched some answers of and got one. You can use self.__delete__(). This delete method that has two underscores around it can only delete an object, so you are good to go!!!
I have defined the below code but there seems to be issues regarding methods load and damage.
(edited based on suggestions by ShadowRanger):
class RangedWeapon(Weapon):
def __init__(self, name, min_dmg, max_dmg):
super().__init__(name, min_dmg, max_dmg)
self.shots=0
def shots_left(self):
return self.shots
def load(self, ammo):
if ammo.weapon_type()==self.name:
self.shots+=ammo.get_quantity()
ammo.remove_all()
def damage(self):
if self.shots==0:
return 0
else:
self.shots-=1
return super().damage()
_
bow = RangedWeapon('bow', 10, 40)
crossbow = RangedWeapon('crossbow', 15, 45)
arrows = Ammo('arrow', bow, 5)
bolts = Ammo('bolt', crossbow, 10)
bow.load(arrows)
print(bow.shots_left()) # should return 5
print(arrows.get_quantity()) #should return 0
But for print(bow.shots_left()) I got 0 and print(arrows.get_quantity()) I got 5 instead. They are reversed. I think my problem is that I didn't load the Ammo quantity? I'm not very sure. Any help would be appreciated. Thank you!
class Ammo(Thing):
def __init__(self, name, weapon, quantity):
self.name=name
self.weapon=weapon
self.quantity=quantity
def get_quantity(self):
return self.quantity
def weapon_type(self):
return self.weapon.name
def remove_all(self):
self.quantity=0
Primary problem: Ammo's weapon_type is a method, not an attribute or property, and you didn't call it, so you're comparing the method itself to the name, not the result of calling it. This is the reason why load does nothing; no method is ever equal to a string.
Other issues:
It looks like you're calling methods on the class, not on the instances. You pass ammo (an instance) as an argument, then call methods on Ammo (the class).
Similarly, your damage method should probably be calling super().damage() not Weapon.damage(), since the latter doesn't use your instance state. And you've got typos (shots vs. shot) that should make this code non-functional in other ways.
Short version: This code is broken in a million ways, and you'll run into each of them as you fix the previous issues.
I am creating a simple game that contains classes called 'Player' and 'Strategy'. I want to assign a Strategy instance to the Player instance when the Player is created.
class Player(object):
def __init__(self):
self.Strategy = None
def Decision(self, InputA, InputB):
Result = self.Strategy(InputA, InputB)
return Result
def SetStrategy(self):
# Sets a strategy instance to the Player instance
class Strategy(object):
def Strategy1(self, InputA, InputB):
return InputA * InputB
def Strategy2(self, InputA, InputB):
return (InputA - InputB) / 2
def Strategy3(self, InputA, InputB):
return 0
What I'm trying to achieve:
in[0] Player1 = Player()
in[1] Player2 = Player()
in[2]: Player1.SetStrategy('Strategy1')
in[3]: Player2.SetStrategy('Strategy3')
in[4]: Player1.Decision(2,5)
out[0]: 10
in[5]: Player2.Decision(3,6)
out[1]: 0
Searching here and via google shows me ways of doing it with monkey patching but the approach looks a little inelegant (and although I'm a beginner I think there's a better way to do it) - is there a way to do this with inheritance that I'm not seeing?
def strategy1(inputA, inputB): # 2
return inputA * inputB
def strategy2(inputA, inputB):
return (inputA - inputB) / 2
def strategy3(inputA, inputB):
return 0
strategy = {
'mul': strategy1,
'diff': strategy2,
'zero': strategy3
}
class Player(object):
def __init__(self, strategy_name='mul'): # 1
self.strategy_name = strategy_name # 5
def decision(self, inputA, inputB): # 4
result = strategy[self.strategy_name](inputA, inputB)
return result
player1 = Player()
player2 = Player()
player1.strategy_name = 'mul' # 3
player2.strategy_name = 'zero'
print(player1.decision(2, 5))
# 10
print(player2.decision(3, 6))
# 0
Every player has a strategy, so don't allow instantiation of Player
without assigning some strategy. You could use a default strategy
(as shown below), or make strategy a mandatory argument.
The strategies could be plain functions; I don't see a reason to
bundle them as methods of a Strategy class. Always keep code as
simple as possible; don't use a class when a function would suffice;
use a class when it provides some feature (such as inheritance) which
makes the class-based solution simpler.
In Python there is no need for getters/setters like setStrategy.
You can use plain attributes for simple values, and properties to
implement more complicated behavior. Attributes and properties use
the same syntax, so you can switch from one to the other without
having to change have the class is used.
There is a convention (recommended in PEP8) that classes be named in
CamelCase, and instances, functions and variables in lowercase. The
convention is used ubiquitously, and following it will help other
understand your code more easily.
To make it easy to store the strategy in a database, you could store
the strategy_name in the database, and use a lookup dict (such as
strategy) to associate the name with the actual function.
I've made a simple game using pygame and livewires, where a sprite has to avoid falling mushrooms. The number of mushrooms falling at a certain time is meant to increase as the score increases. Here is what I mean:
from livewires import games,color
import random
games.init(screen_width=633,screen_height=479,fps=50)
class Stick_Man(games.Sprite):
def update(self):
self.x=games.mouse.x
if self.left<0:
self.left=0
if self.right>games.screen.width:
self.right=games.screen.width
self.check_collision()
def check_collision(self):
if self.overlapping_sprites:
self.over_message()
def over_message(self):
b=games.Message(value="Game Over", size=100, color=color.red,x=games.screen.width/2,y=games.screen.height/2,lifetime=250,after_death=games.screen.quit)
games.screen.add(b)
class Mushroom(games.Sprite):
score=0
start=200
score_required=100
level=1
total_score=0
speed=1
mushroom=games.load_image("mushroom.jpg")
x_position=random.randrange(640)
#staticmethod
def next_level():
indicate='Level ', + Mushroom.level, ' cleared'
message=games.Message(value=indicate,size=50,color=color.red,x=games.screen.width/2,y=games.screen.height/2, lifetime=150)
games.screen.add(message)
Mushroom().score_required+=50
Mushroom().score-=Mushroom.score_required
Mushroom().start-=150
Mushroom().speed+=5
Mushroom().level+=1
if Mushroom().start==20:
Mushroom().start+=10
def __init__(self):
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom==games.screen.height:
self.destroy()
Mushroom.score+=50
Mushroom.total_score+=Mushroom.score
if Mushroom().score==Mushroom.score_required:
self.next_level()
def check2(self):
if self.top==Mushroom.start:
self.duplicate()
def duplicate(self):
new_mush=Mushroom()
games.screen.add(new_mush)
background_image=games.load_image("background.jpg", transparent=False)
games.screen.background=background_image
stickman_image=games.load_image("stickman.png", transparent=True)
stickman=Stick_Man(image=stickman_image,left=1,bottom=480)
games.screen.add(stickman)
games.mouse.is_visible=False
b=Mushroom()
c=Mushroom()
a=Mushroom()
games.screen.add(b)
games.screen.add(a)
games.screen.add(c)
games.screen.event_brab=True
games.screen.mainloop()
The code is pretty self explanatory and whenever one of the mushrooms is equal to start, then a new object is created thus meaning a new mushroom comes in. However, what happens is that code doesn't function properly a second time and the mushrooms don't get faster spawn much faster either. Also, when the game first starts, the minute the first mushroom hits the bottom it says level one cleared, when it should be after two mushrooms. The sprite is just a red mushroom and also a stickman which can be found on g images if you want to simulate.
So my question is how do i make the object's STATS carry on from where it left off whenever another mushroom appears and also display the message at the right time
Your problem is in all of the lines that look like this:
Mushroom().score_required+=50
There are a number of problems here, which all together add up to make this have no useful effect:
Mushroom() creates a new Mushroom instance (which goes away as soon as this line is done).
Assigning (including update-assigning) to an attribute through an instance always creates or updates an instance attribute, even if there was a class attribute of the same name.
The += operator doesn't mutate immutable values like integers in-place (because that would be impossible); a += b is effectively the same as a = a + b.*
So, when you put that together, what you're doing is creating a new value equal to Mushroom.score_required + 50, then assigning that value to a new instance attribute of a temporary instance (which immediately goes away). This has no effect on the class attribute, or on any of the other instances.
You have a related, but different, problem in the lines like this:
x_position=random.randrange(640)
Unless you want all of the mushrooms to have the same x_position, this should not be a class attribute, but an instance attribute, and you're going to run into all kinds of strange problems.
Storing game stats as class attributes of a random class is a strange thing to do. There are ways you could make that work, but there's no good reason to even try. Class attributes are useful for constants that all instances of the class might as well share, but they're not useful as a substitute for global variables.
A better design would be something like this:
class Game(object):
def __init__(self):
self.score = 0
self.start = 200
self.score_required = 100
self.level = 1
self.total_score = 0
def next_level(self):
indicate = 'Level ', + Mushroom.level, ' cleared'
message = games.Message(value=indicate, size=50, color=color.red,
x=games.screen.width/2, y=games.screen.height/2,
lifetime=150)
games.screen.add(message)
self.score_required += 50
self.score -= self.score_required
self.start -= 150
self.speed += 5
self.level += 1
if self.start == 20:
self.start += 10
def update_score(self, n):
game.score += n
game.total_score += game.score
if self.score == self.score_required:
self.next_level()
class Mushroom(games.Sprite):
mushroom=games.load_image("mushroom.jpg")
def __init__(self, game):
self.x_position=random.randrange(640)
self.game = game
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom == games.screen.height:
self.destroy()
game.update_score(50)
def check2(self):
if self.top == Mushroom.start:
self.duplicate()
def duplicate(self):
games.screen.add(Mushroom(self.game))
game = Game()
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.event_brab=True
* That's not completely true. In fact, a = a + b is equivalent to a = a.__add__(b), while a += b is equivalent to a = a.__iadd__(b) if such a method exists, falling back to __add__ only if it doesn't. For mutable objects like lists, this makes a big difference, because __iadd__ can change self in-place and then return it, meaning you end up assigning the same object back to a that was already there. But for immutable objects, there's no difference.