How to cancel the last method called? - python

class Minobot:
def __init__(self):
self.x,self.y = 0, 0
self.angle = 90
def forward(self, d):
angle = radians(90-self.angle)
nx, ny = self.x + d * cos(angle), self.y + d * sin(angle)
self.x, self.y = nx, ny
def right(self):
self.angle += 90
def left(self):
self.angle -= 90
def coordinates(self):
return round(self.x, 1), round(self.y, 1)
def manhattan_distance(self):
return int(abs(round(self.x, 1))+abs(round(self.y, 1)))
def cancel(self):
????
Now I need to add another method to this class that cancels the last method called.
For example: a.forward(2) => a.right() => a.cancel() This would set the Minobot before the a.right() is used.

You cannot cancel the last action unless you store it.
If you store the last action, you can invert it. Since you know the reverse action once you take an action, you can just store the reverse action directly.
Let your Minibot instance have a .reverse_action attribute which is a tuple of the method to call and arguments to pass.
So
def left(self):
# Note how methods can just be stored in variables.
self.reverse_action = (self.right, ())
...
def forward(self, distance):
# Forward is its own reverse with a negative distance.
self.reverse_action = (self.forward, (-distance,))
def revert_last(self):
if self.reverse_action:
(method, args) = self.reverse_action
method(*args) # Call the stored method, passing stored args.
self.reverse_action = None # Used it up.
This has an obvious downside of being able to only revert one last action. If you store a list of reverse actions for each action you take, you can .pop() from it and revert actions as long as there are any stored reverse actions in the list.
You can store only last several actions if you're taking great many actions and are memory-constrained. (Terms to google up: "Undo buffer", "Circular buffer", "Event sourcing".)
Another approach would be storing the previous state, that is, coordinates, heading, etc. Undoing the last action would be then just switching to the previous state:
def save_state(self):
self.previous_state = (self.x, self.y, self.angle)
# Or: self.previous_states_list.append(...)
def restore_previous_state(self):
(self.x, self.y, self.angle) = self.previous_state
# Or: ... = self.previous_states_list.pop()
def left(self):
self.save_state()
...
This approach is free from rounding errors, etc. It takes more memory, though, especially as your state grows large, and when you want to save an entire history of previous states.

Rather than saving the reversing action as other answers have suggested, you can save all the properties, and cancel can restore them.
class Minobot:
def __init__(self):
self.x,self.y, self.oldx, self.oldy = 0, 0
self.angle, self.oldangle = 90
def forward(self, d):
self.oldx, self.oldy = self.x, self.y
angle = radians(90-self.angle)
nx, ny = self.x + d * cos(angle), self.y + d * sin(angle)
self.x, self.y = nx, ny
def right(self):
self.oldangle = self.angle
self.angle += 90
def left(self):
self.oldangle = self.angle
self.angle -= 90
def coordinates(self):
return round(self.x, 1), round(self.y, 1)
def manhattan_distance(self):
return int(abs(round(self.x, 1))+abs(round(self.y, 1)))
def cancel(self):
self.angle, self.x, self.y = self.oldangle, self.oldx, self.oldy

You can save the last action and run it in reverse:
(thank you to the guy that made me update with lambdas)
class Minobot:
def __init__(self):
self.x,self.y = 0, 0
self.angle = 90
def forward(self, d):
angle = radians(90-self.angle)
nx, ny = self.x + d * cos(angle), self.y + d * sin(angle)
self.x, self.y = nx, ny
self.undo = lambda:self.forward(-d)
def right(self):
self.angle += 90
self.undo = lambda:self.left()
def left(self):
self.angle -= 90
self.undo = lambda:self.right()
def coordinates(self):
return round(self.x, 1), round(self.y, 1)
def manhattan_distance(self):
return int(abs(round(self.x, 1))+abs(round(self.y, 1)))
def cancel(self):
self.undo()
Note: will cause a problem if you run cancel without something to cancel

You could try keeping a records of all previous states, but memory usage could grow to be larger than you want. In the solution shown below, the pickle protocol is used to get and set the state of the object as needed. Please note that if you pickle instances of the following class, the instance will not keep its history. The code will probably need to be adapted to your particular requirements.
#! /usr/bin/env python3
import math
class MinnowBot:
__slots__ = '__x', '__y', '__angle', '__history'
def __init__(self):
self.__x = 0
self.__y = 0
self.__angle = 90
def forward(self, distance):
self.history.append(self.__getstate__())
angle = math.radians(90 - self.__angle)
self.__x += distance * math.cos(angle)
self.__y += distance * math.sin(angle)
def right(self):
self.history.append(self.__getstate__())
self.__angle += 90
def left(self):
self.history.append(self.__getstate__())
self.__angle -= 90
#property
def coordinates(self):
return round(self.__x, 1), round(self.__y, 1)
#property
def manhattan_distance(self):
return round(abs(self.__x) + abs(self.__y))
def cancel(self):
self.__setstate__(self.history.pop())
def __getstate__(self):
return self.__x, self.__y, self.__angle
def __setstate__(self, state):
self.__x, self.__y, self.__angle = state
#property
def history(self):
try:
history = self.__history
except AttributeError:
# noinspection PyAttributeOutsideInit
history = self.__history = []
return history

With you short explain maybe you can add state attribute and:
class Minobot:
def __init__(self):
self.x,self.y = 0, 0
self.angle = 90
self.state = self.__init__
self.d = 0
def forward(self, d=None):
if d is None:
d = self.d
angle = radians(90-self.angle)
nx, ny = self.x + d * cos(angle), self.y + d * sin(angle)
self.x, self.y = nx, ny
self.d = d
self.state = self.forward
def right(self):
self.angle += 90
self.state = self.left
def left(self):
self.angle -= 90
self.state = self.right
def coordinates(self):
return round(self.x, 1), round(self.y, 1)
def manhattan_distance(self):
return int(abs(round(self.x, 1))+abs(round(self.y, 1)))
def cancel(self):
if self.state is None:
pass
else:
state = self.state
self.state = None
return state()

If memory is not a concern, you can store/restore state by saving/restoring the whole object.
I am also considering that control of the target object outside its class is not a problem. I assume the object is not cancelling the last action itself, but some other code controls that. Otherwise you'll have to manage your class relation with the Stack class.
In the below example I use deepcopy, but pickle.dumps can also be used.
I choose to use a stack like structure, which allows the saving of whatever number of objects/states you want. But replacing with a rotating buffer would allow the storage of only the last n objects/states.
The advantage I see over some of the other answers is not worrying about what methods were called or attributes had changed. It's also simple. Preserving states and frames/environments always remembers me of dicts and stacks:
from copy import deepcopy
class ObjectStack():
def __init__(self):
self.objects = []
def push(self, obj):
self.objects.append(deepcopy(obj))
def pop(self):
return self.objects.pop() if self.objects else None
#just an example class, not OP's original class
class char():
def __init__(self, char):
self.char = char
stack = ObjectStack()
c = char('C')
stack.push(c) # save object
c = char('D') # somehow object changes
print(c.char)
last = stack.pop() # restore last object/state
if last is not None:
c = last
print(c.char)
last = stack.pop()
if last is None:
print('No more objects/states saved')

Related

Python OOP - Soccer Simulation

I'm new to OOP. I'd like to simulate a soccer match. How do I access Play instance variables in the Player/Offender/Defender classes? If another structure is better, please help.
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
def move_to(self, x, y):
self.x = (self.x + x) / 2
self.y = (self.y + y) / 2
## Loop through Play.Players to find nearest. How do I access Play.Players?
def nearest(self):
return nearest
class Offender(Player):
def __init__(self, x, y):
super().__init__(x, y)
# Move to ball.
def strategy():
ball_x = Play.ball_x # how do I access Play.ball_x
ball_y = Play.ball_y # how do I access Play.ball_y
self.move_to(ball_x, ball_y)
class Defender(Player):
def __init__(self, x, y):
super().__init__(x, y)
# Move to nearest player
def strategy(self):
nearest = self.nearest()
self.move_to(nearest)
class Play:
def __init__(self, offense_players, defense_players, ball_x, ball_y):
self.offense = [Offender(player) for player in offense_players]
self.defense = [Defender(player) for player in defense_players]
self.players = self.offense + self.defense
self.ball_x = ball_x
self.ball_y = ball_y
def simulate(self):
while True:
for player in self.players:
player.strategy()
if __name__ == "__main__":
Play().simulate()
Instead of having Offender and Defender classes, I have one for each position, i.e. Striker(Player), Midfielder(Player), Goalie(Player), etc. which is why I'd like to store their respective strategy in their class and not in the Play class.
Not sure how much this will be helpful for you, as the implementation is in C++
You can checkout my implementation for a similar problem statement https://github.com/rimpo/footballcpp
For understanding the arcade game framework implementation for which the above bot was written, checkout http://richard-shepherd.github.io/coding-world-cup/index.html
I just elaborate the idea from the comment of martineau: We just pass the Play instance as argument to the relevant methods of Player. I also wrote out a very first draft for the nearest() method, but you might want to improve its logic. I was just drafting this to demonstrate how you could solve your OOP design problem.
import typing
class Player:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def move_to(self, x: float, y: float) -> None:
self.x = (self.x + x) / 2
self.y = (self.y + y) / 2
def nearest(self, play: "Play") -> "Player":
# This must yet be adapted to handle the edge case of two players
# having equal distance to the current player. You didn't specify the
# desired output for that case, hence I just ignored that scenario for now.
return min([
p for p in play.players
if p.x != self.x or p.y != self.y, # this is to
# exclude the player itself.
# This is a buggy logic, because it relies on an assumption that is not
# validated in the code (the assumption that two different players never
# have identical coordinates). You might want to introduce a uniqe `id`
# instance variable to the Player class, to handle this identification in a
# clean way.
],
key=lambda p: (self.x - p.x)**2 + (self.y - p.y)**2
)
class Offender(Player):
def __init__(self, x: float, y: float):
super().__init__(x, y)
# Move to ball.
def strategy(self, play: "Play") -> None:
self.move_to(play.ball_x, play.ball_y)
class Defender(Player):
def __init__(self, x: float, y: float):
super().__init__(x, y)
# Move to nearest player
def strategy(self, play: "Play") -> None:
self.move_to(self.nearest(play=play)
class Play:
def __init__(
self,
offense_players: typing.List["Offender"],
defense_players: typing.List["Defender"],
ball_x: float,
ball_y: float,
):
self.offense = offense_players
self.defense = defense_players
self.players = self.offense + self.defense
self.ball_x = ball_x
self.ball_y = ball_y
def simulate(self) -> None:
while True: # this is still a bad condition, you might want to change this. However you didn't specify your desired logic, so I didn't change it.
for player in self.players:
player.strategy(self, play=self)
I would do the following:
keep track of game state in another (data) class:
class GameState:
def __init__(self, offense_players, defense_players, ball_x, ball_y):
self.offense = offense_players
self.defense = defense_players
self.players = self.offense + self.defense
self.ball_x = ball_x
self.ball_y = ball_y
You may even wish to use python3.7 dataclasses (and some other features) for this (although it is not at all necessary)
from dataclasses import dataclass
from typing import List
#dataclass
class GameState:
offense: List[Offender]
defense: List[Defender]
ball_x: float
ball_y: float
#property
def players(self):
return offense + defense
Players then take this state in their strategy and are expected to update their internal state (like position). nearest player is implemented by taking the minimum l2 distance between other players using the key argument to min that takes a function of another player, p which is written using lambda.
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
def move_to(self, x, y, other_players):
self.x = (self.x + x) / 2
self.y = (self.y + y) / 2
def nearest(self):
return nearest
class Offender(Player):
def __init__(self, x, y):
super().__init__(x, y)
# Move to ball.
def strategy(self, game_state):
ball_x = game_sate.ball_x # how do I access Play.ball_x
ball_y = game_state.ball_y # how do I access Play.ball_y
self.move_to(ball_x, ball_y)
class Defender(Player):
def __init__(self, x, y):
super().__init__(x, y)
# Move to nearest player
def strategy(self, game_state):
# we assume you are moving to offensive players - which
# you will not be apart of
nearest = min(
game_state.offense
key=lambda p: (
(self.x - p.x) **2 + (self.y - p.y) ** 2
) ** (1/2) # take the l2 norm to find the "closest" player to you
)
self.move_to(nearest.x, nearest.y)
Then, play the game
class Play:
def __init__(self, game_state):
self.game_state = game_state
def simulate(self):
while True:
for player in self.game_state.players:
player.strategy(self.game_state)
if __name__ == "__main__":
Play(GameState(
[Offender(-1, 0), Offender(-1, -1), ...]
[Defender(1, 0), Offender(1, -1), ...]
)).simulate()
You could then implement some actual players
class TiernaDavidson(Defender):
def strategy(self, *args, **kwargs):
return better_strategy(*args, **kwargs)
You will have to ask her for the implementation of better_strategy ;)

How to fix typerror dealing with '<='

what the codes is supposed to is take the users input and then movie the rectangle around and when I run the code i get a Typeerror:<=' not supported between instances of 'str' and 'int'
these were some of the comments made on my code
For both Point and Rectangle class constructors, you are providing default values for the parameters. You should not since the assignment states, you should make them required arguments. (-4)
rectangleCount static attribute of the Rectangle class not kept up to date (should be incremented in the constructor to keep track of count of Rectangle objects created so far) (-2)
Rectangle's constructor doesn't check if width and height is negative, and if so print error message. (-2)
Calculations in the bottomRight property is incorrect. (-2) It should be
#property
def bottomRight(self):
return Point(self.topLeft.x + self.width, self.topLeft.y + self.height)
Calculations in the perimeter proper
# Prog 120
# Rectangle & Point Classes
class Point:
def __init__(self, x, y): # makes the value required
self.__x = x
self.__y = y
#property # read only property for x
def x(self):
return self.__x
#property # read only property for y
def y(self):
return self.__y
def translate(self, dx, dy): # moves x coordinate by 'dx' amount of time and move dy
self.__x += dx
self.__y += dy
class Rectangle:
DEFAULT_WIDTH = 1 # static attributes
DEFAULT_HEIGHT = 1
rectangleCount = 0
def __init__(self, topLeft, width, height):
self.__topLeft = topLeft
# check width argument
if (width <= 0):
print("Invalid width")
self.__width = Rectangle.DEFAULT_WIDTH
else:
self.__width = width
# check height argument
if (height <= 0):
print("Invalid Height")
self.__height = Rectangle.DEFAULT_HEIGHT
else:
self.__height = height
Rectangle.rectangleCount +=1 #updates the count of rectangels created
#property
def topLeft(self):
return self.__topLeft
#topLeft.setter
def topLeft(self, newTopLeft):
self.__topLeft = newTopLeft
#property
def width(self):
return self.__width
#width.setter
def width(self, newWidth):
if (newWidth <= 0):
print("Width cannot be less than '0'. ")
else:
self.__width = newWidth
#property
def height(self):
return self.__height
#height.setter
def height(self, newHeight):
if (newHeight <= 0):
print("Height cannot be less than '0'. ")
else:
self.__height = newHeight
#property
def bottomRight(self):
return Point(self.topLeft.x + self.topLeft.y + self.height)
#property
def area(self):
return self.__width * self.__height
#property
def perimeter(self):
return self.__width *2 + self.__height *2
def translate(self, dx, dy): # moves x coordinate by 'dx' amount of time and move y
self.__topLeft.translare(dx,dy)
def main():
bill = Point(x="", y="")
will = Rectangle(topLeft="", width="", height="")
if will.width and will.height < 0:
print("Width and Height cannot be less than 0.")
will.width = will.DEFAULT_WIDTH
will.height = will.DEFAULT_HEIGHT
will.rectangleCount += 1
if __name__ == "__main__":
main()
bill = Point(x="", y="")
will = Rectangle(topLeft="", width="", height="")
Here you are setting the attributes in Rectangle to the empty string.
And here:
def __init__(self, topLeft, width, height):
self.__topLeft = topLeft
# check width argument
if (width <= 0):
print("Invalid width")
self.__width = Rectangle.DEFAULT_WIDTH
You are comparing that string to 0 - an int. Obviously you can't compare "" with 0. That doesn't make any sense. That's what python is telling you with that error message.
Maybe try passing integers to the constructors. Like this:
bill = Point(x=5, y=9)
will = Rectangle(topLeft=2, width=4, height=1)

Classes: Interacting with 2 class instances in one function

It's my first time with classes in python and I quickly wrote a simple class which lets you move rocket by coordinates. I don't know though how to make a function called let's say "distance" that would return distance between two different instances(rockets). Just to be clear, I know how to calculate the distance, I don't know how to build a function
class Rocket:
def __init__(self , start = (0, 0)):
self.start = start
self.x = self.start[0]
self.y = self.start[1]
if not self.crash():
self.start = (0,0)
print("You crashed!! Your position has been restarted to (0,0)")
def __repr__(self):
return "tbd"
def get_position(self):
return "Your curret posiiton is {}".format(self.start)
def move_side(self,x):
self.x += x
self.start = (self.x, self.y)
def move_up(self,y):
self.y += y
self.start = (self.x, self.y)
if not self.crash():
self.start = (0,0)
print("You crashed!! Your position has been restarted to (0,0)")
def move(self,x,y):
self.x += x
self.y += y
self.start = (self.x,self.y)
def land_rocket(self):
self.y = 0
self.start = (self.x,self.y)
def crash(self):
if self.y >= 0:
return True
return False
def distance(self,other):
return "???"
You need to define a class method that takes an extra argument, which is the object from which you want to calculate the distance.
To apply the cartesian distance formula, know that ** stands for exponentiation and that you can import math.sqrt for square root.
import math
class Rocket:
...
def distance(self, other):
return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
The above code only requires other to have x and y attributes, it does not need to be a Rocket instance.

Calling a method from parent class that has a different name in the subclass

Having the following code:
class Point:
'class that represents a point in the plane'
def __init__(self, xcoord=0, ycoord=0):
''' (Point,number, number) -> None
initialize point coordinates to (xcoord, ycoord)'''
self.x = xcoord
self.y = ycoord
def setx(self, xcoord):
''' (Point,number)->None
Sets x coordinate of point to xcoord'''
self.x = xcoord
def sety(self, ycoord):
''' (Point,number)->None
Sets y coordinate of point to ycoord'''
self.y = ycoord
def get(self):
'''(Point)->tuple
Returns a tuple with x and y coordinates of the point'''
return (self.x, self.y)
def move(self, dx, dy):
'''(Point,number,number)->None
changes the x and y coordinates by dx and dy'''
self.x += dx
self.y += dy
def __repr__(self):
'''(Point)->str
Returns canonical string representation Point(x, y)'''
return 'Point('+str(self.x)+','+str(self.y)+')'
class Rectangle(Point):
def __init__(self,bottom_left,top_right,color):
self.get = bottom_left
self.get = top_right
self.color = color
def get_bottom_left(self,bottom_left):
print ()
r1 = Rectangle(Point(0,0), Point(1,1), "red")
r1.get_bottom_left()
I want to be able to print "Point(0,0)" by calling self__rep__(self) from class Point from the method get_bottom_left, but I just have no idea how. I know how to use inheritance if the functions have the same name, but in this case I am stuck and it is a requirement for the child function to have the method names it has. If it looks that I am just looking for the answer, I would like the response to just explain me a similar case of this application please!
When I do the following:
class Rectangle(Point):
def __init__(self,bottom_left,top_right,color):
self.get = bottom_left
self.get = top_right
self.color = color
def get_bottom_left(self,bottom_left):
print (self.bottom_left)
I get: get_bottom_left() missing 1 required positional argument: 'bottom_left'
As mentioned in the comment, Rectangle should contain Point instances and not inherit Point. If you change Rectangle class as shown below, you'll see the expected result:
class Rectangle():
def __init__(self, bottom_left, top_right, color):
self.bottom_left = bottom_left
self.top_right = top_right
self.color = color
def get_bottom_left(self):
print self.bottom_left

Not recognizing object parameter as int

class Ball:
def __init__(self,pos,vel):
self.pos = Vector(pos.x,pos.y)
self.vel = Vector(vel.x,vel.y)
def curx(self):
return (self.pos.x + self.vel.x)
def cury(self):
return (self.pos.y + self.vel.y)
def forcex(velx):
self.vel.deltax(velx)
def forcey(vely):
self.vel.deltay(vely)
class Vector:
def __init__(self,x,y):
self.x = x
self.y = y
def x(self):
return self.x
def y(self):
return self.y
def delx(self,deltax):
self.x = self.x + deltax
def dely(self,deltay):
self.y = self.y + deltay
Here are my two classes, but when I initialize and try to get curx or cury back from ball:
ball = Ball(Vector(0,0),Vector(0,0))
print ball.curx
I get: <bound method Ball.curx of <__main__.Ball instance at 0x1142fd0>>
I feel like there should be a fairly simple answer to this and I'm just not getting it.
curx is a method of Ball. So, you have to invoke it:
print ball.curx()
Edit:
#user2357112 has noticed two more problems:
The definitions of Ball.forcex and Ball.forcey are missing their self parameters.
Vector.x and Vector.y are entirely useless methods. You already have x and y as attributes of Vector through self.x and self.y. So, you should just remove the methods altogether.
Here is how the code should be:
class Ball:
def __init__(self,pos,vel):
self.pos = Vector(pos.x,pos.y)
self.vel = Vector(vel.x,vel.y)
def curx(self):
return (self.pos.x + self.vel.x)
def cury(self):
return (self.pos.y + self.vel.y)
def forcex(self, velx):
self.vel.deltax(velx)
def forcey(self, vely):
self.vel.deltay(vely)
class Vector:
def __init__(self,x,y):
self.x = x
self.y = y
def delx(self,deltax):
self.x = self.x + deltax
def dely(self,deltay):
self.y = self.y + deltay
ball = Ball(Vector(0,0),Vector(0,0))
print ball.curx()

Categories