Some silly code for Easter holiday

Discussion in 'Python' started by Alf P. Steinbach, Mar 23, 2010.

  1. This program simulates some colored balls moving around, changing color
    according to certain rules. I think the most interesting is perhaps to not look
    at this code but just try to run it and figure out the color changing rules from
    observing the effect (extra mystery: why I wrote this). Sort of like an Easter
    holiday mystery.


    <code>
    # Py3
    # Copyright 2010 Alf P. Steinbach
    import tkinter as tk
    from collections import namedtuple
    import random

    Point = namedtuple( "Point", "x, y" )
    Size = namedtuple( "Size", "x, y" )
    RGB = namedtuple( "RGB", "r, g, b" )

    def generator( g ):
    assert isinstance( g, type( (i for i in ()) ) )
    return g

    def tk_internal_bbox_from( bbox: tuple ):
    return ((bbox[0], bbox[1], bbox[2]+2, bbox[3]+2))

    def tk_new_ellipse( canvas, bbox: tuple, **kwargs ):
    return canvas.create_oval( tk_internal_bbox_from( bbox ), **kwargs )

    class TkTimer:
    def __init__( self, widget, msecs: int, action, start_running: bool = True ):
    self._widget = widget
    self._msecs = msecs
    self._action = action
    self._id = None
    if start_running: self.start()

    def start( self ):
    self._id = self._widget.after( self._msecs, self._on_timer )

    def stop( self ):
    id = self._id;
    self._id = None
    self._widget.after_cancel( id ) # Try to cancel last event.

    def _on_timer( self ):
    if self._id is not None:
    self._action()
    self.start()

    class TkEllipse:
    def __init__( self, canvas, bbox: tuple, **kwargs ):
    self._canvas = canvas
    self._id = tk_new_ellipse( canvas, bbox, **kwargs )

    @property # id
    def id( self ): return self._id

    @property # fill
    def fill( self ):
    return self._canvas.itemcget( self._id, "fill" )
    @fill.setter
    def fill( self, color_representation: str ):
    self._canvas.itemconfigure( self._id,
    fill = color_representation
    )

    @property # internal_bbox
    def internal_bbox( self ):
    return tuple( self._canvas.coords( self._id ) )

    @property # position
    def position( self ):
    bbox = self.internal_bbox
    return Point( bbox[0], bbox[1] )
    @position.setter
    def position( self, new_pos: Point ):
    bbox = self.internal_bbox
    (dx, dy) = (new_pos.x - bbox[0], new_pos.y - bbox[1])
    self._canvas.move( self._id, dx, dy )
    #assert self.position == new_pos

    class Color:
    def __init__( self, rgb_or_name ):
    if isinstance( rgb_or_name, RGB ):
    name = None
    rgb = rgb_or_name
    else:
    assert isinstance( rgb_or_name, str )
    name = rgb_or_name
    rgb = None
    self._name = name
    self._rgb = rgb

    @property
    def representation( self ):
    if self._name is not None:
    return self._name
    else:
    rgb = self._rgb
    return "#{:02X}{:02X}{:02X}".format( rgb.r, rgb.g, rgb.b )

    def __str__( self ): return self.representation
    def __hash__( self ): return hash( self.representation )

    class Rectangle:
    def __init__( self,
    width : int,
    height : int,
    upper_left : Point = Point( 0, 0 )
    ):
    self._left = upper_left.x
    self._right = upper_left.x + width
    self._top = upper_left.y
    self._bottom = upper_left.y + height

    @property # left
    def left( self ): return self._left

    @property # top
    def top( self ): return self._top

    @property # right
    def right( self ): return self._right

    @property # bottom
    def bottom( self ): return self._bottom

    @property # width
    def width( self ): return self._right - self._left

    @property # height
    def height( self ): return self._bottom - self._top

    @property # size
    def size( self ): return Size( self.width, self.height )


    class Ball:
    def __init__( self,
    color : Color,
    position : Point = Point( 0, 0 ),
    velocity : Point = Point( 0, 0 )
    ):
    self.color = color
    self.position = position
    self.velocity = velocity

    def squared_distance_to( self, other ):
    p1 = self.position
    p2 = other.position
    return (p2.x - p1.x)**2 + (p2.y - p1.y)**2

    class BallSim:
    def __init__( self,
    rect : Rectangle,
    n_balls : int = 1
    ):
    def random_pos():
    return Point(
    random.randrange( rect.left, rect.right ),
    random.randrange( rect.top, rect.bottom )
    )
    def random_velocity():
    return Point(
    random.randint( -10, 10 ),
    random.randint( -10, 10 )
    )
    def balls( color ):
    return generator(
    Ball( color, random_pos(), random_velocity() ) for i in range(
    n_balls )
    )
    self._rect = rect
    self._kind_1_color = Color( "blue" )
    self._kind_2_color = Color( "orange" )
    self._balls = tuple( balls( self._kind_1_color ) )
    self._is_close_distance = 20;

    @property # rect
    def rect( self ): return self._rect

    @property # interaction_radius
    def interaction_radius( self ):
    return self._is_close_distance

    @property # n_balls
    def n_balls( self ): return len( self._balls )

    def ball( self, i ): return self._balls
    def balls( self ): return self._balls

    def _update_positions_and_velocities( self ):
    rect = self._rect
    for ball in self._balls:
    pos = ball.position; v = ball.velocity;
    pos = Point( pos.x + v.x, pos.y + v.y )
    if pos.x < 0:
    pos = Point( -pos.x, pos.y )
    v = Point( -v.x, v.y )
    if pos.x >= rect.width:
    pos = Point( 2*rect.width - pos.x, pos.y )
    v = Point( -v.x, v.y )
    if pos.y < 0:
    pos = Point( pos.x, -pos.y )
    v = Point( v.x, -v.y )
    if pos.y >= rect.height:
    pos = Point( pos.x, 2*rect.height - pos.y )
    v = Point( v.x, -v.y )
    ball.position = pos
    ball.velocity = v

    def _balls_possibly_close_to( self, ball ):
    max_d_squared = self._is_close_distance**2
    result = []
    for other in self._balls:
    if other is ball:
    continue
    if ball.squared_distance_to( other ) <= max_d_squared:
    result.append( other )
    return result

    def _update_kinds( self ):
    max_d_squared = self._is_close_distance**2
    for ball in self._balls:
    if ball.color == self._kind_1_color:
    for other_ball in self._balls_possibly_close_to( ball ):
    if ball.squared_distance_to( other_ball ) <= max_d_squared:
    if other_ball.color == self._kind_1_color:
    ball.color = self._kind_2_color
    other_ball.color = self._kind_2_color
    break
    else:
    if random.random() < 0.01:
    ball.color = self._kind_1_color

    def evolve( self ):
    self._update_positions_and_velocities()
    self._update_kinds()

    class View:
    def _create_widgets( self, parent_widget, sim: BallSim ):
    self.widget = tk.Frame( parent_widget )
    if True:
    canvas = tk.Canvas(
    self.widget, bg = "white", width = sim.rect.width, height =
    sim.rect.height
    )
    canvas.pack()
    self._canvas = canvas
    self._circles = []
    radius = sim.interaction_radius // 2
    self._ball_radius = radius
    for ball in sim.balls():
    (x, y) = (ball.position.x, ball.position.y)
    bbox = (x - radius, y - radius, x + radius, y + radius)
    ellipse = TkEllipse( canvas, bbox, fill =
    ball.color.representation )
    self._circles.append( ellipse )
    pass

    def __init__( self, parent_widget, sim: BallSim ):
    self._create_widgets( parent_widget, sim )
    self._sim = sim

    def update( self ):
    sim = self._sim
    r = self._ball_radius
    for (i, ball) in enumerate( sim.balls() ):
    center_pos = ball.position
    self._circles.position = Point( center_pos.x - r, center_pos.y - r )
    self._circles.fill = ball.color

    class Controller:
    def __init__( self, main_window ):
    self._window = main_window

    self._model = BallSim( Rectangle( 600, 500 ), n_balls = 20 )

    self._view = view = View( main_window, self._model )
    view.widget.place( relx = 0.5, rely = 0.5, anchor="center" )
    self._timer = TkTimer( main_window, msecs = 42, action = self._on_timer )

    def _on_timer( self ):
    self._model.evolve()
    self._view.update()


    def main():
    window = tk.Tk()
    window.title( "Sim 1 -- Chameleon Balls" )
    window.geometry( "640x510" )

    controller = Controller( window )
    window.mainloop()

    main()
    </code>


    Cheers,

    - Alf
    Alf P. Steinbach, Mar 23, 2010
    #1
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ftse Daytrader

    Summer holiday work experience

    Ftse Daytrader, Jul 13, 2004, in forum: Java
    Replies:
    3
    Views:
    439
    Scott Ellsworth
    Jul 13, 2004
  2. Thinking of your next holiday

    , Jan 14, 2008, in forum: C Programming
    Replies:
    0
    Views:
    304
  3. Replies:
    0
    Views:
    279
  4. Replies:
    0
    Views:
    292
  5. Replies:
    0
    Views:
    344
Loading...

Share This Page