Class angle value is reset after running move function - python

I am working on a little project to have autonomous cells move around and eventually be a little game of life simulation. Currently I'm having an issue with randomizing the cells movement. I have the cell as a class and set the starting angle in the init then in a move function the angle is updated. For some reason the updated angle is reset the next time the move function is called. To handle the simulation window and physics I'm using Python Arcade with pymunk physics.
Cell Class
import arcade
import random
import math
from dice import Dice
cell_types = ["Plant", "Animal"] # add fungus and virus later
d20 = Dice(20, 1)
count = 0
class Cell(arcade.SpriteCircle):
""" Cell Sprite """
def __init__(self, radius, color, soft, mass, x, y):
""" Init """
# initialize SpriteCircle parent class
super().__init__(radius, color, soft)
# body
self.mass = radius * mass
self.speed = radius
self.center_x = x
self.center_y = y
self.angle = random.randint(0, 360)
self.hit_box_algorithm = "Simple"
# characteristics
self.type = random.choice(cell_types)
def move(self):
# roll a d20
roll = d20.roll()
print(f"roll: {roll}")
# if d20 is 15 or more turn right
# if d20 is 5 or less turn left
print(f"old angle: {self.angle}")
if roll >= 15:
self.angle -= 90
elif roll <= 5:
self.angle += 90
print(f"new angle: {self.angle}")
# convert angle to radians
angle_rad = math.radians(self.angle)
# find next coordinates and save as a tuple
print(f"old x pos: {self.center_x}")
print(f"old y pos: {self.center_y}")
self.center_x += self.speed * math.cos(angle_rad)
self.center_y += self.speed * math.sin(angle_rad)
print(f"new x pos: {self.center_x}")
print(f"new y pos: {self.center_y}")
# return the tuple for apply force function
movement_vector = (self.center_x, self.center_y)
return movement_vector
Dice class for reference, it's just a way to have a randrange as an object instead of typing out the function each time and does function as expected otherwise the results later in the post would not have any variance between the old and new angles.
import random
class Dice:
""" Create a die specifying sides and how many dice"""
def __init__(self, sides, count=1):
self.sides = sides
self.count = count
def roll(self):
""" Roll the set of dice"""
total = 0
for i in range(self.count):
total += random.randrange(1, self.sides)
return total
The class is initialized as a "new_cell" and added into a spritelist and when the on_update function runs, the move function is called.
Relevant code for main.py using arcades boilerplate window template, segments of boilerplate not in use have been cut.
https://api.arcade.academy/en/stable/examples/starting_template.html#starting-template
import arcade
import random
from typing import Optional
from cell import Cell
from dice import Dice
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Autonomous Cells"
STARTING_CELL_COUNT = 1
SPRITE_SIZE = 32
SPRITE_SCALING = .15
CELL_SIZE = SPRITE_SIZE * SPRITE_SCALING
CELL_SIZE_MIN_MULTIPLIER = 1
CELL_SIZE_MAX_MULTIPLIER = 5
DEFAULT_DAMPING = .5
CELL_DAMPING = 0.4
CELL_FRICTION = 0.5
DEFAULT_CELL_MASS = 1.0
CELL_MAX_SPEED = 50
class MyGame(arcade.Window):
"""
Main application class.
NOTE: Go ahead and delete the methods you don't need.
If you do need a method, delete the 'pass' and replace it
with your own code. Don't leave 'pass' in this program.
"""
def __init__(self, width, height, title):
super().__init__(width, height, title)
arcade.set_background_color(arcade.color.DARK_BLUE_GRAY)
# If you have sprite lists, you should create them here,
# and set them to None
self.cell_sprite_list = None
# physics engine
self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
def setup(self):
""" Set up the game variables. Call to re-start the game. """
# Create your sprites and sprite lists here
self.cell_sprite_list = arcade.SpriteList()
for i in range(STARTING_CELL_COUNT):
new_color = (random.randrange(256),
random.randrange(256),
random.randrange(256)
)
new_cell = Cell(radius=(random.randint(CELL_SIZE_MIN_MULTIPLIER,
CELL_SIZE_MAX_MULTIPLIER
) * int(CELL_SIZE)),
color=new_color,
soft=False,
mass=DEFAULT_CELL_MASS,
x=SCREEN_WIDTH / 2 + random.randint(3, 10),
y=SCREEN_HEIGHT / 2 + random.randint(3, 10)
)
self.cell_sprite_list.append(new_cell)
# physics engine setup
damping = DEFAULT_DAMPING
self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,
gravity=(0, 0))
# add cell sprites to physics engine
for cell in self.cell_sprite_list:
self.physics_engine.add_sprite(cell,
friction=CELL_FRICTION,
collision_type="Cell",
damping=CELL_DAMPING,
max_velocity=CELL_MAX_SPEED)
def on_draw(self):
"""
Render the screen.
"""
# This command should happen before we start drawing. It will clear
# the screen to the background color, and erase what we drew last frame.
self.clear()
self.cell_sprite_list.draw()
# Call draw() on all your sprite lists below
def on_update(self, delta_time):
"""
All the logic to move, and the game logic goes here.
Normally, you'll call update() on the sprite lists that
need it.
"""
for cell in self.cell_sprite_list:
self.physics_engine.apply_force(cell, cell.move())
self.physics_engine.step()
def main():
""" Main function """
game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
game.setup()
arcade.run()
if __name__ == "__main__":
main()
move function called for each cell in the sprite list after this loop finishes the cells initial angle resets to the value at instantiation rather than retaining the newly applied value from the move function.
The function seems to work but after the for loop exits each cells angle variable is reset to its original state. Console log output shows that while in the for loop the angle is updated but after exiting the loop and running the next time the angle has been reset to the original value.
Angle before move called: 303.0
roll: 17
old angle: 303.0
new angle: 213.0
old x pos: 450.6516108053191
old y pos: 288.2189227316175
new x pos: 437.2328817181923
new y pos: 279.5046981713771
angle after move called: 213.0
Angle before move called: 303.0
roll: 3
old angle: 303.0
new angle: 393.0
old x pos: 451.40957273618153
old y pos: 287.87260184601035
new x pos: 464.8283018233083
new y pos: 296.5868264062508
angle after move called: 393.0
I have tried reworking the movement function and calling the movement function multiple times per update outside of the for loop. when called consecutively the angle does carry over to the next call but is reset to the original value by the time the next on_update function runs.
I was expecting the self.angle of the instanced cell in the cell_sprite_list to update to the new angle generated by the move function.

If you just need to move circle randomly, you can directly update its coordinates:
import arcade
import random
class Game(arcade.Window):
def __init__(self):
super().__init__()
self.x = 400
self.y = 300
self.x_direction = random.choice([-1, 1])
self.y_direction = random.choice([-1, 1])
def on_draw(self):
self.clear()
arcade.draw_circle_filled(self.x, self.y, 30, arcade.color.RED)
def on_update(self, delta_time):
self.x += 3 * self.x_direction
self.y += 3 * self.y_direction
if random.random() < 0.1:
self.x_direction = random.choice([-1, 1])
self.y_direction = random.choice([-1, 1])
Game()
arcade.run()
Output:

Related

How to Level Up

I'm currently working on building the game Pong. One aspect of the game is displaying the current level. What I want to happen is have 3 levels. When the score is equal to 5, I want to display "Level 2". When the score is equal to 10, I want to display "Level 3" and then at 15 I want to display "You Win!" What's happening right now is as soon as the score is equal to 5, the Level number counts up every time it updates the window (super fast). Where or how do I write this so that it functions the way I want?
I created a function in the Pong class called level_up for this.
import arcade
import random
# These are Global constants to use throughout the game
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 300
BALL_RADIUS = 10
PADDLE_WIDTH = 10
PADDLE_HEIGHT = 50
MOVE_AMOUNT = 5
SCORE_HIT = 1
SCORE_MISS = 5
LEVEL_UP = 1
"""Point will identify the x and y coordinates of the ball"""
class Point:
def __init__(self):
"""The __init__ initializes the x and y coordinates"""
self.x=float(0)
self.y=float(0)
"""Velocity will identify the velocity"""
class Velocity:
def __init__(self):
"""The __init__ initializes the x and y velocity"""
self.dx=float(0)
self.dy=float(0)
"""Ball will identify the coordinates and the movement of the ball"""
class Ball:
def __init__(self):
"""The __init__ will initialize the Point and Velocity class values"""
self.center=Point()
#self.center will call the self.x and self.y float values that are found in the Point class.
self.velocity=Velocity()
#self.velocity will call the self.dx and self.dy values that are found in the Velocity class.
self.velocity.dx=2
self.velocity.dy=2
def draw(self):
"""This creates the ball"""
arcade.draw_circle_filled(self.center.x, self.center.y,
BALL_RADIUS, arcade.color.FLUORESCENT_YELLOW)
def advance(self):
self.center.x += self.velocity.dx
self.center.y += self.velocity.dy
def bounce_horizontal(self):
self.velocity.dx *=-1
def bounce_vertical(self):
self.velocity.dy *=-1
def restart(self):
self.center.x=0
self.center.y=random.uniform(0,SCREEN_HEIGHT)
self.velocity.dx=random.uniform(1,8)
self.velocity.dy=random.uniform(0,8)
"""Paddle will represent the paddle"""
class Paddle:
def __init__(self):
"""The __init__ will initialize the location of the paddle"""
self.center=Point()
#self.center calls the Point class
self.center.x=SCREEN_WIDTH
self.center.y=SCREEN_HEIGHT//2
def draw(self):
arcade.draw_rectangle_filled(self.center.x, self.center.y,
PADDLE_WIDTH, PADDLE_HEIGHT, arcade.color.FLUORESCENT_PINK)
def move_up(self):
self.center.y+=MOVE_AMOUNT
if self.center.y > SCREEN_HEIGHT:
self.center.y -= MOVE_AMOUNT
def move_down(self):
self.center.y-=MOVE_AMOUNT
if self.center.y < 0:
self.center.y += MOVE_AMOUNT
class Pong(arcade.Window):
"""
This class handles all the game callbacks and interaction
It assumes the following classes exist:
Point
Velocity
Ball
Paddle
This class will then call the appropriate functions of
each of the above classes.
You are welcome to modify anything in this class,
but should not have to if you don't want to.
"""
def __init__(self, width, height):
"""
Sets up the initial conditions of the game
:param width: Screen width
:param height: Screen height
"""
super().__init__(width, height)
self.ball = Ball()
self.paddle = Paddle()
self.score = 0
self.level = 1
# These are used to see if the user is
# holding down the arrow keys
self.holding_left = False
self.holding_right = False
arcade.set_background_color(arcade.color.BLACK)
def on_draw(self):
"""
Called automatically by the arcade framework.
Handles the responsiblity of drawing all elements.
"""
# clear the screen to begin drawing
arcade.start_render()
# draw each object
self.ball.draw()
self.paddle.draw()
self.draw_score()
self.draw_level()
def draw_score(self):
"""
Puts the current score on the screen
"""
score_text = "Score: {}".format(self.score)
start_x = 190
start_y = SCREEN_HEIGHT - 20
arcade.draw_text(score_text, start_x=start_x, start_y=start_y, font_size=12,
color=arcade.color.DEEP_SKY_BLUE)
def draw_level(self):
"""Displays the level"""
level_text = f"LEVEL {self.level}"
start_x= 175
start_y=SCREEN_HEIGHT - 40
arcade.draw_text(level_text, start_x=start_x, start_y=start_y, font_size=20,
color=arcade.color.ELECTRIC_GREEN)
def update(self, delta_time):
"""
Update each object in the game.
:param delta_time: tells us how much time has actually elapsed
"""
# Move the ball forward one element in time
self.ball.advance()
# Check to see if keys are being held, and then
# take appropriate action
self.check_keys()
# check for ball at important places
self.check_miss()
self.check_hit()
self.check_bounce()
def check_hit(self):
"""
Checks to see if the ball has hit the paddle
and if so, calls its bounce method.
:return:
"""
too_close_x = (PADDLE_WIDTH / 2) + BALL_RADIUS
too_close_y = (PADDLE_HEIGHT / 2) + BALL_RADIUS
if (abs(self.ball.center.x - self.paddle.center.x) < too_close_x and
abs(self.ball.center.y - self.paddle.center.y) < too_close_y and
self.ball.velocity.dx > 0):
# we are too close and moving right, this is a hit!
self.ball.bounce_horizontal()
self.score += SCORE_HIT
def level_up(self):
if self.score ==5:
self.level += LEVEL_UP
if self.score ==10:
self.level += LEVEL_UP
def check_miss(self):
"""
Checks to see if the ball went past the paddle
and if so, restarts it.
"""
if self.ball.center.x > SCREEN_WIDTH:
# We missed!
self.score -= SCORE_MISS
self.ball.restart()
def check_bounce(self):
"""
Checks to see if the ball has hit the borders
of the screen and if so, calls its bounce methods.
"""
if self.ball.center.x < 0 and self.ball.velocity.dx < 0:
self.ball.bounce_horizontal()
if self.ball.center.y < 0 and self.ball.velocity.dy < 0:
self.ball.bounce_vertical()
if self.ball.center.y > SCREEN_HEIGHT and self.ball.velocity.dy > 0:
self.ball.bounce_vertical()
def check_keys(self):
"""
Checks to see if the user is holding down an
arrow key, and if so, takes appropriate action.
"""
if self.holding_left:
self.paddle.move_down()
if self.holding_right:
self.paddle.move_up()
def on_key_press(self, key, key_modifiers):
"""
Called when a key is pressed. Sets the state of
holding an arrow key.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = True
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = True
def on_key_release(self, key, key_modifiers):
"""
Called when a key is released. Sets the state of
the arrow key as being not held anymore.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = False
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = False
# Creates the game and starts it going
window = Pong(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()
I just answered my own question, I changed the level_up function to say:
def level_up(self):
if self.score ==5 and self.level==1:
self.level += LEVEL_UP
if self.score ==10 and self.level==2:
self.level += LEVEL_UP

Why is score instance variable not decreasing when it should in python

I've been starting to learn oop and im having difficulty making it do what i need it to. I'm building a simple game using the turtle module where a circle is dropped and everytime the circle drops out of the screen the score should decrease.Ill be adding shooting functions and speed configurations and all that. But at the moment I'm stuck on this circle loop.
The problem is the score is not changing correctly. I have two classes Circle and Player. The score is an instance variable of Player class, but drop() function in Circle class tallies the score.
I can make it work using a procedural style of programming but with oop im stuck.. Heres code. Any constructive criticism is welcome.
import turtle as t
import random
class Circle:
def __init__(self,size):
self.size = size
#self.speed = speed
self.circle = t.Turtle()
self.ypos = 300
def size_color(self):
self.circle.color('red')
self.circle.shape('circle')
self.circle.shapesize(self.size,self.size,1)
#positions circle random x top y
def posit(self):
number = random.randint(-340,340)
self.circle.ht()
self.circle.penup()
self.size_color()
self.circle.goto(number,self.ypos)
self.circle.st()
#substracts from y position for falling effect
#should substract from score as well
def drop(self):
self.posit()
while True:
self.ypos = self.ypos- 4
self.circle.sety(self.ypos)
if self.ypos <= -300:
Player().score = Player().score - 10
print(Player().score)
self.ypos = 300
self.posit()
class Player:
def __init__(self,score=200):
self.score = score
def display_screen():
window = t.Screen()
window.bgcolor('black')
display_screen()
c = Circle(2)
c.drop()
Every time you call Player() you're creating a new player. So when you do
Player().score = Player().score - 10
print(Player().score)
it's roughly equivalent to:
temp_player1 = Player()
temp_player2 = Player()
temp_player1.score = temp_player2.score - 10
temp_player3 = Player()
print(temp_player3.score)
As you can see from that, there are 3 different players, so you're never decrementing any player's score, and then you're printing a different player's score.
You need to add a player attribute to the Circle, then it can use self.player instead of creating a new player each time.
import turtle as t
import random
class Circle:
def __init__(self,size):
self.size = size
#self.speed = speed
self.circle = t.Turtle()
self.ypos = 300
this.player = Player()
def size_color(self):
self.circle.color('red')
self.circle.shape('circle')
self.circle.shapesize(self.size,self.size,1)
#positions circle random x top y
def posit(self):
number = random.randint(-340,340)
self.circle.ht()
self.circle.penup()
self.size_color()
self.circle.goto(number,self.ypos)
self.circle.st()
#substracts from y position for falling effect
#should substract from score as well
def drop(self):
self.posit()
while True:
self.ypos = self.ypos- 4
self.circle.sety(self.ypos)
if self.ypos <= -300:
self.player.score = self.player.score - 10
print(self.player.score)
self.ypos = 300
self.posit()
class Player:
def __init__(self,score=200):
self.score = score
def display_screen():
window = t.Screen()
window.bgcolor('black')
display_screen()
c = Circle(2)
c.drop()
Barmar stole my answer. Use what he said, however don't forget to change this.player to self.player as he made a tiny mistake, probably because of other programming languages.

Python - Infinite loop in runSimulation function

This is an assignment where we are simulating those roomba vacuum cleaner robots. Anyway I would really appreciate some help with the runSimulation function. It launches into an infinite loop at the while loop. As far as I know the values it calculates at the condition of the while loops are correct but are they initialised each time the while loop is tested? I don't think so but could be wrong. So this simulation function instantiates a room of width and height and robots of num_robots and the simulation runs until the floor is cleaned until min_coverage is reached. The result from runSimulation is the average time steps it takes to reach min_coverage. Thanks in advance!
Simulating robots
import math
import random
import ps2_visualize
import pylab
from ps2_verify_movement27 import testRobotMovement
class Position(object):
"""
A Position represents a location in a two-dimensional room.
"""
def __init__(self, x, y):
"""
Initializes a position with coordinates (x, y).
"""
self.x = x
self.y = y
def getX(self):
return self.x
def getY(self):
return self.y
def getNewPosition(self, angle, speed):
"""
Computes and returns the new Position after a single clock-tick has
passed, with this object as the current position, and with the
specified angle and speed.
Does NOT test whether the returned position fits inside the room.
angle: number representing angle in degrees, 0 <= angle < 360
speed: positive float representing speed
Returns: a Position object representing the new position.
"""
old_x, old_y = self.getX(), self.getY()
angle = float(angle)
# Compute the change in position
delta_y = speed * math.cos(math.radians(angle))
delta_x = speed * math.sin(math.radians(angle))
# Add that to the existing position
new_x = old_x + delta_x
new_y = old_y + delta_y
return Position(new_x, new_y)
def __str__(self):
return "(%0.2f, %0.2f)" % (self.x, self.y)
class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.
A room has a width and a height and contains (width * height) tiles. At any
particular time, each of these tiles is either clean or dirty.
"""
def __init__(self, width, height):
"""
Initializes a rectangular room with the specified width and height.
Initially, no tiles in the room have been cleaned.
width: an integer > 0
height: an integer > 0
"""
self.width = width
self.height = height
self.tiles = [[False] * self.height for i in range(self.width)]
def cleanTileAtPosition(self, pos):
"""
Mark the tile under the position POS as cleaned.
Assumes that POS represents a valid position inside this room.
pos: a Position - pos is a tuple (x, y)
"""
(x_tile, y_tile) = (int(math.floor(pos.getX())), int(math.floor(pos.getY())))
#print (x_tile, y_tile)
self.tiles[x_tile][y_tile] = True
def isTileCleaned(self, m, n):
"""
Return True if the tile (m, n) has been cleaned.
Assumes that (m, n) represents a valid tile inside the room.
m: an integer
n: an integer
returns: True if (m, n) is cleaned, False otherwise
"""
self.m = m
self.n = n
if self.tiles[self.m][self.n] == True:
return True
else:
return False
def getNumTiles(self):
"""
Return the total number of tiles in the room.
returns: an integer
"""
return self.width*self.height
def getNumCleanedTiles(self):
"""
Return the total number of clean tiles in the room.
returns: an integer
"""
numCleanTiles = 0
for row in range(self.width):
for column in range(self.height):
if self.tiles[row][column] == True:
numCleanTiles +=1
return numCleanTiles
def getRandomPosition(self):
"""
Return a random position inside the room.
returns: a Position object.
"""
#
return Position(random.randrange(0, self.width), random.randrange(0, self.height))
def isPositionInRoom(self, pos):
"""
Return True if pos is inside the room.
pos: a Position object.
returns: True if pos is in the room, False otherwise.
"""
if 0 <= pos.getX() < self.width and 0 <= pos.getY() < self.height:
return True
else:
return False
class Robot(object):
"""
Represents a robot cleaning a particular room.
At all times the robot has a particular position and direction in the room.
The robot also has a fixed speed.
Subclasses of Robot should provide movement strategies by implementing
updatePositionAndClean(), which simulates a single time-step.
"""
def __init__(self, room, speed):
"""
Initializes a Robot with the given speed in the specified room. The
robot initially has a random direction and a random position in the
room. The robot cleans the tile it is on.
room: a RectangularRoom object.
speed: a float (speed > 0)
"""
#raise NotImplementedError
self.room = room
self.speed = speed
self.position = self.room.getRandomPosition()
self.direction = random.randint(0, 359)
self.room.cleanTileAtPosition(self.position)
def getRobotPosition(self):
"""
Return the position of the robot.
returns: a Position object giving the robot's position.
"""
return self.position
def getRobotDirection(self):
"""
Return the direction of the robot.
returns: an integer d giving the direction of the robot as an angle in
degrees, 0 <= d < 360.
"""
d = self.direction
return d
def setRobotPosition(self, position):
"""
Set the position of the robot to POSITION.
position: a Position object.
"""
self.position = position
return self.position
def setRobotDirection(self, direction):
"""
Set the direction of the robot to DIRECTION.
direction: integer representing an angle in degrees
"""
self.direction = direction
return self.direction
def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
raise NotImplementedError # don't change this!
class StandardRobot(Robot):
"""
A StandardRobot is a Robot with the standard movement strategy.
At each time-step, a StandardRobot attempts to move in its current
direction; when it would hit a wall, it *instead* chooses a new direction
randomly.
"""
def updatePositionAndClean(self):
"""
Simulate the raise passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
newPosition = self.position.getNewPosition(self.direction, self.speed)
if 0 <= newPosition.getX() <= self.room.width and 0 <= newPosition.getY() <= self.room.height:
self.setRobotPosition(newPosition)
self.room.cleanTileAtPosition(self.position)
else:
self.setRobotDirection(random.randint(0, 359))
# Uncomment this line to see your implementation of StandardRobot in action!
#testRobotMovement(StandardRobot, RectangularRoom)
def runSimulation(num_robots, speed, width, height, min_coverage, num_trials,
robot_type):
"""
Runs NUM_TRIALS trials of the simulation and returns the mean number of
time-steps needed to clean the fraction MIN_COVERAGE of the room.
The simulation is run with NUM_ROBOTS robots of type ROBOT_TYPE, each with
speed SPEED, in a room of dimensions WIDTH x HEIGHT.
num_robots: an int (num_robots > 0)
speed: a float (speed > 0)
width: an int (width > 0)
height: an int (height > 0)
min_coverage: a float (0 <= min_coverage <= 1.0)
num_trials: an int (num_trials > 0)
robot_type: class of robot to be instantiated (e.g. StandardRobot or
RandomWalkRobot)
"""
numTimeStepsList = []
for i in range(num_trials):
room1 = RectangularRoom(width, height)
numTimeSteps = 0
robot = []
for n in range(num_robots-1):
robot[n] = robot_type(room1, speed)
while (1.0 - float(room1.getNumCleanedTiles())/float(room1.getNumTiles())) >= min_coverage:
for n in range(num_robots-1):
robot[n].updatePositionAndClean()
numTimeSteps += 1
#print numTimeSteps
numTimeStepsList.append(numTimeSteps)
#print numTimeStepsList
return sum(numTimeStepsList)/len(numTimeStepsList)
#raise NotImplementedError
# Uncomment this line to see how much your simulation takes on average
print runSimulation(1, 1.0, 10, 10, 0.75, 30, StandardRobot)
class RandomWalkRobot(Robot):
"""
A RandomWalkRobot is a robot with the "random walk" movement strategy: it
chooses a new direction at random at the end of each time-step.
"""
def updatePositionAndClean(self):
"""
Simulate the passage of a single time-step.
Move the robot to a new position and mark the tile it is on as having
been cleaned.
"""
raise NotImplementedError
def showPlot1(title, x_label, y_label):
"""
What information does the plot produced by this function tell you?
"""
num_robot_range = range(1, 11)
times1 = []
times2 = []
for num_robots in num_robot_range:
print "Plotting", num_robots, "robots..."
times1.append(runSimulation(num_robots, 1.0, 20, 20, 0.8, 20, StandardRobot))
times2.append(runSimulation(num_robots, 1.0, 20, 20, 0.8, 20, RandomWalkRobot))
pylab.plot(num_robot_range, times1)
pylab.plot(num_robot_range, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'RandomWalkRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
def showPlot2(title, x_label, y_label):
"""
What information does the plot produced by this function tell you?
"""
aspect_ratios = []
times1 = []
times2 = []
for width in [10, 20, 25, 50]:
height = 300/width
print "Plotting cleaning time for a room of width:", width, "by height:", height
aspect_ratios.append(float(width) / height)
times1.append(runSimulation(2, 1.0, width, height, 0.8, 200, StandardRobot))
times2.append(runSimulation(2, 1.0, width, height, 0.8, 200, RandomWalkRobot))
pylab.plot(aspect_ratios, times1)
pylab.plot(aspect_ratios, times2)
pylab.title(title)
pylab.legend(('StandardRobot', 'RandomWalkRobot'))
pylab.xlabel(x_label)
pylab.ylabel(y_label)
pylab.show()
When you call runSimulation with a num_robots argument of 1, it doesn't actually make any robots at all, so nothing ever gets cleaned.
The culprit is this loop:
for n in range(num_robots-1):
robot[n] = robot_type(room1, speed)
That's almost all wrong. You can't assign to any index in the robot list because it starts empty. And you are always looping one fewer time than you should (zero times if num_robots is 1). You probably want something more like:
for _ in range(num_robots):
robot.append(robot_type(room1, speed))

more efficient wind tunnel simulation in Pygame, using numpy

I am an aerospace student working on a school project for our python programming course. The assignment is create a program only using Pygame and numpy. I decided to create a wind tunnel simulation that simulates the airflow over a two dimensional wing. I was wondering if there is a more efficient way of doing the computation from a programming perspective. I will explain the program:
I have attached an image here:
The (steady) flow field is modeled using the vortex panel method. Basically, I am using a grid of Nx times Ny points where at each point a velocity (u,v) vector is given. Then using Pygame I map these grid points as circles, so that they resemble an area of influence. The grid points are the grey circles in the following image:
I create N particles and determine their velocities by iterating as follows:
create a list of particles.
create a grid list.
for each gridpoint in grid list:
  for each particle in list of particles:
  if particle A is within the area of influence of grid point n (xn,yn):    particle A its velocity = velocity at grid point n.
Visualize everything in Pygame.
this basic way was the only way I could think of visualizing the flow in Pygame. The simulation works pretty well, but If I increase the number of grid points(increase the accuracy of the flow field), the performance decreases. My question is if there is a more efficient way to do this just using pygame and numpy?
I have attached the code here:
import pygame,random,sys,numpy
from Flow import Compute
from pygame.locals import *
import random, math, sys
#from PIL import Image
pygame.init()
Surface = pygame.display.set_mode((1000,600))
#read the airfoil geometry from a dat file
with open ('./resources/naca0012.dat') as file_name:
x, y = numpy.loadtxt(file_name, dtype=float, delimiter='\t', unpack=True)
#parameters used to describe the flow
Nx=30# 30 column grid
Ny=10#10 row grid
N=20#number of panels
alpha=0#angle of attack
u_inf=1#freestream velocity
#compute the flow field
u,v,X,Y= Compute(x,y,N,alpha,u_inf,Nx,Ny)
#The lists used for iteration
Circles = []
Particles= []
Velocities=[]
#Scaling factors used to properly map the potential flow datapoints into Pygame
magnitude=400
vmag=30
umag=30
panel_x= numpy.multiply(x,magnitude)+315
panel_y= numpy.multiply(-y,magnitude)+308
#build the grid suited for Pygame
grid_x= numpy.multiply(X,magnitude)+300
grid_y= numpy.multiply(Y,-1*magnitude)+300
grid_u =numpy.multiply(u,umag)
grid_v =numpy.multiply(v,-vmag)
panelcoordinates= zip(panel_x, panel_y)
# a grid area
class Circle:
def __init__(self,xpos,ypos,vx,vy):
self.radius=16
self.x = xpos
self.y = ypos
self.speedx = 0
self.speedy = 0
#create the grid list
for i in range(Ny):
for s in range(Nx):
Circles.append(Circle(int(grid_x[i][s]),int(grid_y[i][s]),grid_u[i][s],grid_v[i][s]))
Velocities.append((grid_u[i][s],grid_v[i][s]))
#a particle
class Particle:
def __init__(self,xpos,ypos,vx,vy):
self.image = pygame.Surface([10, 10])
self.image.fill((150,0,0))
self.rect = self.image.get_rect()
self.width=4
self.height=4
self.radius =2
self.x = xpos
self.y = ypos
self.speedx = 30
self.speedy = 0
#change particle velocity if collision with grid point
def CircleCollide(Circle,Particle):
Particle.speedx = int(Velocities[Circles.index((Circle))][0])
Particle.speedy = int(Velocities[Circles.index((Circle))][1])
#movement of particles
def Move():
for Particle in Particles:
Particle.x += Particle.speedx
Particle.y += Particle.speedy
#create particle streak
def Spawn(number_of_particles):
for i in range(number_of_particles):
i=i*(300/number_of_particles)
Particles.append(Particle(0, 160+i,1,0))
#create particles again if particles are out of wake
def Respawn(number_of_particles):
for Particle in Particles:
if Particle.x >1100:
Particles.remove(Particle)
if Particles==[]:
Spawn(number_of_particles)
#Collsion detection using pythagoras and distance formula
def CollisionDetect():
for Circle in Circles:
for Particle in Particles:
if Particle.y >430 or Particle.y<160:
Particles.remove(Particle)
if math.sqrt( ((Circle.x-Particle.x)**2) + ((Circle.y-Particle.y)**2) ) <= (Circle.radius+Particle.radius):
CircleCollide(Circle,Particle)
#draw everything
def Draw():
Surface.fill((255,255,255))
#Surface.blit(bg,(-300,-83))
for Circle in Circles:
pygame.draw.circle(Surface,(245,245,245),(Circle.x,Circle.y),Circle.radius)
for Particle in Particles:
pygame.draw.rect(Surface,(150,0,0),(Particle.x,Particle.y,Particle.width,Particle.height),0)
#pygame.draw.rect(Surface,(245,245,245),(Circle.x,Circle.y,1,16),0)
for i in range(len(panelcoordinates)-1):
pygame.draw.line(Surface,(0,0,0),panelcoordinates[i],panelcoordinates[i+1],3)
pygame.display.flip()
def GetInput():
keystate = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == QUIT or keystate[K_ESCAPE]:
pygame.quit();sys.exit()
def main():
#bg = pygame.image.load("pressure.png")
#bg = pygame.transform.scale(bg,(1600,800))
#thesize= bg.get_rect()
#bg= bg.convert()
number_of_particles=10
Spawn(number_of_particles)
clock = pygame.time.Clock()
while True:
ticks = clock.tick(60)
GetInput()
CollisionDetect()
Move()
Respawn(number_of_particles)
Draw()
if __name__ == '__main__': main()
The code requires another script that computes the flow field itself. It also reads datapoints from a textfile to get the geometry of the wing.
I have not provided these two files, but I can add them if necessary. Thank you in advance.
One bottleneck in your code is likely collision detection. CollisionDetect() computes the distance between each particle and each circle. Then, if a collision is detected, CircleCollide() finds the index of the circle in Circles (a linear search), so that the velocities can be retrieved from the same index in Velocities. Clearly this is ripe for improvement.
First, the Circle class already has the velocities in the speedx/speedy attributes, so Velocities can be eliminated .
Second, because the circles are at fixed locations, you can calculate which circle is closest to any given particle from the position of the particle.
# You may already have these values from creating grid_x etc.
# if not, you only need to calculated them once, because the
# circles don't move
circle_spacing_x = Circles[1].x - Circles[0].x
circle_spacing_y = Circles[Nx].y - Circles[0].y
circle_first_x = Circles[0].x - circle_spacing_x / 2
circle_first_y = Circles[0].y - circle_spacing_y / 2
Then CollisionDetect() becomes:
def CollisionDetect():
for particle in Particles:
if particle.y >430 or particle.y<160:
Particles.remove(particle)
continue
c = (particle.x - circle_first_x) // circle_spacing_x
r = (particle.y - circle_first_y) // circle_spacing_y
circle = Circles[r*Nx + c]
if ((circle.x - particle.x)**2 + (circle.y - particle.y)**2
<= (circle.radius+particle.radius)**2):
particle.speedx = int(circle.speedx)
particle.speedy = int(circle.speedy)
I've tidied up your code and made a few changes, namely adding scope to your classes and introducing a couple more. Without further knowledge of Flow I cannot test this fully, but if you could get back to me I can do some more. I'm assuming here that the 'flow field' can be simulated by the numpy.meshgrid function.
import pygame,numpy,sys
import pygame.locals
import math
class Particle:
def __init__(self,xpos,ypos,vx,vy):
self.size = numpy.array([4,4])
self.radius =2
self.pos = numpy.array([xpos,ypos])
self.speed = numpy.array([30,0])
self.rectangle = numpy.hstack((self.pos,self.size))
def move(self):
self.pos += self.speed
self.rectangle = numpy.hstack((self.pos,self.size))
def distance(self,circle1):
return math.sqrt(numpy.sum((circle1.pos - self.pos)**2))
def collision(self,circle1):
result = False
if self.pos[1] >430 or self.pos[1]<160:
result = True
if self.distance(circle1) <= (circle1.radius+self.radius):
self.speed = circle1.speed
return result
class Particles:
def __init__(self,num_particles):
self.num = num_particles
self.particles =[]
self.spawn()
def spawn(self):
for i in range(self.num):
i=i*(300/self.num)
self.particles.append(Particle(0, 160+i,1,0))
def move(self):
for particle in self.particles:
particle.move()
if particle.pos[0] >1100:
self.particles.remove(particle)
if not self.particles: self.spawn()
def draw(self):
for particle in self.particles:
pygame.draw.rect(Surface,(150,0,0),particle.rectangle,0)
def collisiondetect(self,circle1):
for particle in self.particles:
if particle.collision(circle1):
self.particles.remove(particle)
def GetInput():
keystate = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.locals.QUIT or keystate[pygame.locals.K_ESCAPE]:
pygame.quit()
sys.exit()
#draw everything
def Draw(sw,cir):
Surface.fill((255,255,255))
cir.draw()
for i in range(panelcoordinates.shape[1]):
pygame.draw.line(Surface,(0,0,0),panelcoordinates[0,i-1],panelcoordinates[0,i],3)
sw.draw()
pygame.display.flip()
# a grid area
class Circle:
def __init__(self,xpos,ypos,vx,vy):
self.radius=16
self.pos = numpy.array([xpos,ypos])
self.speed = numpy.array([vx,vy])
class Circles:
def __init__(self,columns,rows):
self.list = []
grid_x,grid_y = numpy.meshgrid(numpy.linspace(0,1000,columns),numpy.linspace(200,400,rows))
grid_u,grid_v = numpy.meshgrid(numpy.linspace(20,20,columns),numpy.linspace(-1,1,rows))
for y in range(rows):
for x in range(columns):
c1= Circle(int(grid_x[y,x]),int(grid_y[y,x]),grid_u[y,x],grid_v[y,x])
self.list.append(c1)
def draw(self):
for circle in self.list:
pygame.draw.circle(Surface,(245,245,245),circle.pos,circle.radius)
def detectcollision(self,parts):
for circle in self.list:
parts.collisiondetect(circle)
if __name__ == '__main__':
#initialise variables
number_of_particles=10
Nx=30
Ny=10
#circles and particles
circles1 = Circles(Nx,Ny)
particles1 = Particles(number_of_particles)
#read the airfoil geometry
panel_x = numpy.array([400,425,450,500,600,500,450,425,400])
panel_y = numpy.array([300,325,330,320,300,280,270,275,300])
panelcoordinates= numpy.dstack((panel_x,panel_y))
#initialise PyGame
pygame.init()
clock = pygame.time.Clock()
Surface = pygame.display.set_mode((1000,600))
while True:
ticks = clock.tick(6)
GetInput()
circles1.detectcollision(particles1)
particles1.move()
Draw(particles1,circles1)
I've also made some a crude stab at drawing a aerofoil, as again I don't have the knowledge of the data file with the coordinates.

Hide Turtle Window?

I am generating diagrams in Turtle, and as part of my program, I identify certain coordinates from my diagrams. I would like to be able to hide the complete turtle window, since I only care about the coordinates, is that possible?
Edit:
QUESTION 2:
This isn't really an answer, but a few other questions.
I got my program working to some extent, if you run it in IDLE and type "l" it will give you the list with the coordinates.
import Tkinter
import turtle
from turtle import rt, lt, fd # Right, Left, Forward
size = 10
root = Tkinter.Tk()
root.withdraw()
c = Tkinter.Canvas(master = root)
t = turtle.RawTurtle(c)
t.speed("Fastest")
# List entire coordinates
l = []
def findAndStoreCoords():
x = t.xcor()
y = t.ycor()
x = round(x, 0) # Round x to the nearest integer
y = round(y, 0) # Round y to the nearest integer
# Integrate coordinates into sub-list
l.append([x, y])
def hilbert(level, angle):
if level == 0:
return
t.rt(angle)
hilbert(level - 1, -angle)
t.fd(size)
findAndStoreCoords()
t.lt(angle)
hilbert(level - 1, angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, angle)
t.lt(angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, -angle)
t.rt(angle)
The problem is that Turtle is so SLOW! Is there any package that is just like Turtle but can do commands much faster?
I reimplemented the turtle class as suggested by thirtyseven. It is consistent with the api. (i.e. when you turn right in this class, it is the same as turning right in turtle.
This does not implement all the methods in the api, only common ones. (And the ones you used).
However, it's short and fairly straightforward to extend. Also, it keeps track of all of the points it has been to. It does this by adding an entry to pointsVisited every time you call forward, backward, or setpos (or any of the aliases for those functions).
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = y[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()
Yes, this is possible. The simplest way is to instantiate a root Tkinter window, withdraw it, and then use it as the master window for a RawTurtle's Canvas instance.
Example:
import Tkinter
import turtle
root=Tkinter.Tk()
root.withdraw()
c=Tkinter.Canvas(master=root)
t=turtle.RawTurtle(c)
t.fd(5)
print t.xcor() # outputs 5.0
Unfortunately, this still initiates the graphics system, but no window will appear.
The problem is that Turtle is so SLOW! Is there any package that is
just like Turtle but can do commands much faster?
Yes, turtle can. If we add a TurtleScreen to the tkinter implementation, and use it's tracer() functionality, we can speed things up more than turtle's speed() method. And we can simplify the code greatly by tossing the customizations and simply use turtle's own begin_poly(), end_poly() and get_poly() methods:
from tkinter import Tk, Canvas
from turtle import TurtleScreen, RawTurtle
SIZE = 10
def hilbert(level, angle):
if level == 0:
return
turtle.right(angle)
hilbert(level - 1, -angle)
turtle.forward(SIZE)
turtle.left(angle)
hilbert(level - 1, angle)
turtle.forward(SIZE)
hilbert(level - 1, angle)
turtle.left(angle)
turtle.forward(SIZE)
hilbert(level - 1, -angle)
turtle.right(angle)
root = Tk()
root.withdraw()
canvas = Canvas(master=root)
screen = TurtleScreen(canvas)
screen.tracer(False) # turn off turtle animation
turtle = RawTurtle(screen)
turtle.begin_poly() # start tracking movements
hilbert(5, 90)
turtle.end_poly() # end tracking movements
print(turtle.get_poly())
This prints all the points in a level 5 Hilbert curve in about 1/3 of a second on my system. Your posted code toke nearly 9 seconds to output a level 4.

Categories