# https://thepythoncodingbook.com/2021/10/31/using-lists-tuples-dictionaries-and-sets-in-python/ import random import time import turtle # Parameters to set up animation background_colour = 50, 50, 50 screen_size = 800, 800 grid_size = 16, 16 grid_scale = (screen_size[0] / grid_size[0], screen_size[1] / grid_size[1]) fraction_of_grid_points_used = 0.35 n_tiles = int(fraction_of_grid_points_used * grid_size[0] * grid_size[1]) ball_colour = 0, 191, 255 new_ball_interval = 2 max_ball_speed = 2 ball_speed_step = 0.2 # Functions to convert between grid and screen coordinates def convert_grid_to_screen_coords(grid_coords): return ( grid_coords[0] * grid_scale[0] - screen_size[0] / 2 + grid_scale[0] / 2, grid_coords[1] * grid_scale[1] - screen_size[1] / 2 + grid_scale[1] / 2, ) def convert_screen_to_grid_coords(screen_coords): return ( round( (screen_coords[0] - grid_scale[0] / 2 + screen_size[0] / 2) / grid_scale[0] ), round( (screen_coords[1] - grid_scale[1] / 2 + screen_size[1] / 2) / grid_scale[1] ), ) # Create window window = turtle.Screen() window.tracer(0) window.colormode(255) window.setup(*screen_size) window.bgcolor(background_colour) # Choose grid coordinates that will contain tiles tile_grid_coords = set() while len(tile_grid_coords) < n_tiles: tile_grid_coords.add( (random.randint(0, grid_size[0] - 1), random.randint(0, grid_size[1] - 1)) ) # Define actions based on grid point colour def speed_up(ball: turtle.Turtle): """Increase ball speed until it reaches max_ball_speed""" ball.ball_speed += ball_speed_step if ball.ball_speed > max_ball_speed: ball.ball_speed = max_ball_speed def slow_down(ball: turtle.Turtle): """Decrease ball speed. Hide and remove from list when stationary""" ball.ball_speed -= ball_speed_step if ball.ball_speed < ball_speed_step: ball.hideturtle() balls.remove(ball) def change_direction(ball: turtle.Turtle): """Rotate Turtle object by a random angle in [-90, 90] range""" ball.left(random.randint(-90, 90)) # Map colours to ball actions actions = { (144, 238, 144): speed_up, (220, 20, 60): slow_down, (255, 127, 80): change_direction, } # Create tiles tiles = {coord: random.choice(tuple(actions.keys())) for coord in tile_grid_coords} # Create balls balls = [] def change_ball_colour(ball): fraction_of_max_speed = ball.ball_speed / max_ball_speed ball.fillcolor( int(ball_colour[0] * fraction_of_max_speed), int(ball_colour[1] * fraction_of_max_speed), int(ball_colour[2] * fraction_of_max_speed), ) def create_new_ball(): ball = turtle.Turtle() ball.penup() ball.shape("circle") ball.pencolor("white") ball.setposition( random.randint(-screen_size[0] // 2, screen_size[0] // 2), random.randint(-screen_size[1] // 2, screen_size[1] // 2), ) ball.setheading(random.randint(0, 359)) ball.ball_speed = 0.5 ball.current_grid = None change_ball_colour(ball) balls.append(ball) create_new_ball() # Start animation with one ball # Draw tiles on screen grid_draw = turtle.Turtle() grid_draw.penup() grid_draw.hideturtle() for coord, colour in tiles.items(): coords = convert_grid_to_screen_coords(coord) grid_draw.setposition( coords[0] - grid_scale[0] / 2 * 0.9, coords[1] - grid_scale[1] / 2 * 0.9 ) grid_draw.color(colour) grid_draw.pendown() for _ in range(2): grid_draw.forward(grid_scale[0] * 0.8) grid_draw.left(90) grid_draw.forward(grid_scale[1] * 0.8) grid_draw.left(90) grid_draw.penup() # Main animation loop start_timer = time.time() while True: # Create new ball every time interval elapses if time.time() - start_timer > new_ball_interval: create_new_ball() start_timer = time.time() for ball in balls: # Move ball ball.forward(ball.ball_speed) # If ball goes out of bounds, move to other side if abs(ball.xcor()) > screen_size[0] / 2: ball.setx(-ball.xcor()) if abs(ball.ycor()) > screen_size[1] / 2: ball.sety(-ball.ycor()) # Check whether ball hit tile and perform required action ball_grid_coords = convert_screen_to_grid_coords(ball.position()) if ball_grid_coords in tiles.keys() and ball_grid_coords != ball.current_grid: colour = tiles[ball_grid_coords] actions[colour](ball) ball.current_grid = ball_grid_coords change_ball_colour(ball) window.update() time.sleep(0.001)