im currently playing around with pythons turtle module and im trying to make a grid of opaque square shapes, say 30x30 that can change color based on some property (doesn't matter what property) my question is, is there anyway to change the color of a shape once its already been drawn down on the canvas?
Ive tried adding all the square shapes to an array, both stamps and polygons, but it seems impossible to change the color of any of them once they have been drawn.
i know stamp doesn't work because its like a footprint of where the turtle was but is there any method at all that allows for this with polygons or some other method im not aware of?
I didn't add any snippets of code because its a pretty basic question and can be used for many things.
Yes, you can do it. The key to this, and many complicated turtle problems, is using stamps. They are individually, or collectively, removable. And since they take on the shape of the turtle itself, they can be images or arbitrary polygons of any size or color you wish:
from turtle import Turtle, Screen
from random import randrange, choice
from collections import namedtuple
from math import ceil
GRID = 15 # GRID by GRID of squares
SIZE = 30 # each square is SIZE by SIZE
INCREASE = 1.5 # how much to lighten the square's color
WHITE = [255, 255, 255] # color at which we stop changing square
DELAY = 100 # time between calls to change() in milliseconds
DARK = 32 # range (ceil(INCREASE) .. DARK - 1) of dark colors
def change():
block = choice(blocks)
blocks.remove(block)
color = [min(int(primary * INCREASE), WHITE[i]) for i, primary in enumerate(block.color)] # lighten color
turtle.color(color)
turtle.setposition(block.position)
turtle.clearstamp(block.stamp)
stamp = turtle.stamp()
if color != WHITE:
blocks.append(Block(block.position, color, stamp)) # not white yet so keep changing this block
if blocks: # stop all changes if/when all blocks turn white
screen.ontimer(change, DELAY)
HALF_SIZE = SIZE // 2
screen = Screen()
screen.colormode(WHITE[0])
screen.register_shape("block", ((HALF_SIZE, -HALF_SIZE), (HALF_SIZE, HALF_SIZE), (-HALF_SIZE, HALF_SIZE), (-HALF_SIZE, -HALF_SIZE)))
screen.tracer(GRID ** 2) # ala #PyNuts
turtle = Turtle(shape="block", visible=False)
turtle.speed("fastest")
turtle.up()
Block = namedtuple('Block', ['position', 'color', 'stamp'])
blocks = list()
HALF_GRID = GRID // 2
for x in range(-HALF_GRID, HALF_GRID):
for y in range(-HALF_GRID, HALF_GRID):
turtle.goto(x * SIZE, y * SIZE)
color = [randrange(ceil(INCREASE), DARK) for primary in WHITE]
turtle.color(color)
blocks.append(Block(turtle.position(), color, turtle.stamp()))
screen.ontimer(change, DELAY)
screen.exitonclick()
The idea of the Turtle is that images, once drawn, become pixels, commited to the canvas - even though the Turtle commands themselves are "vector" commands, when one is talking about "vectorial" vs "raster" graphics.
That means the Turtle drawing context can't know about what is already drawn "by itself" - there is no way for you to reference an already drawn shape, or polygon and change its properties. (update: but for the stamps - the Turtle object does record information about those - thanks #cdlane)
What you'd have to do,in this case, is to annotate on your code data about any shapes you'd like to further modify - and them redraw them in someway, when you need it. In other words: you develop code for a vector graphics model -which allows you to do that.
However, you should note, that the tkinter Canvas widget, on top of which the Turtle runs (at least, I believe), is itself a Vector model - so it might be more appropriate for what you have in mind. Its documentation and way of working may be hard to grasp, though.
So, going back to "having your own Vector graphics implementation", you could take advantage of Python Object Orientation and data model to build "history-turtle" - which could record a log and replay blocks of commands within the same position context (but allowing for different color, width, etc, ... settings).
It is a bitty tricker done than said, but then, here is a sample one:
from turtle import Turtle
"""
Python Turtle with logging and replay capabilities
Author: João S. O. Bueno <gwidion#gmail.com>
License: LGPL 3.0+
This implements HistoryTurtle - a subclass of
Python's turtle.Turtle wich features a simple
command history and new `replay` and `log`
methods -
`turtle.replay(<start>, <end>)` will reissue
the commands logged in those positions of the history,
but with the current Pen settings.
The optional kwarg `position_independent` can be passed
as `True` to `replay` so that the turtle's position
and heading are not restored to the ones recorded
along with the command.
https://gist.github.com/jsbueno/cb413e985747392c460f39cc138436bc
"""
class Command:
__slots__ = ( "method_name", "args", "kwargs", "pos", "heading")
def __init__(self, method_name, args=(),
kwargs=None, pos=None, heading=None):
self.method_name = method_name
self.args = args
self.kwargs = kwargs or {}
self.pos = pos
self.heading = heading
def __repr__(self):
return "<{0}> - {1}".format(self.pos, self.method_name)
class Instrumented:
def __init__(self, turtle, method):
self.turtle = turtle
self.method = method
def __call__(self, *args, **kwargs):
command = Command(self.method.__name__, args, kwargs,
self.pos(), self.heading())
result = self.method(*args, **kwargs)
if (self.method.__name__ not in self.turtle.methods_to_skip or
not kwargs.pop('skip_self', True)):
self.turtle.history.append(command)
return result
def pos(self):
return self.turtle._execute(Command('pos'), True)
def heading(self):
return self.turtle._execute(Command('heading'), True)
class HistoryTurtle(Turtle):
methods_to_skip = ('replay', '_execute', 'log')
def __init__(self, *args, **kw):
self._inited = False
super().__init__(*args, **kw)
self.history = []
self._replaying = [False]
self._inited = True
def __getattribute__(self, attr):
result = super().__getattribute__(attr)
if (not callable(result) or
attr.startswith('_') or
not self._inited or
self._replaying[-1]):
return result
return Instrumented(self, result)
def replay(self, start, end=None, *,
position_independent=False,
skip_self=True,
restore=True
):
results = []
self._replaying.append(True)
if restore:
pos, heading, state = self.pos(), self.heading(), self.pen()['pendown']
for command in self.history[start:end]:
results.append(
self._execute(command, position_independent))
if restore:
self.setpos(pos)
self.setheading(heading)
(self.pendown() if state else self.penup())
self._replaying.pop()
return results
def log(self, start=0, end=None):
for i, command in enumerate(self.history[start:end], start):
print(i, command)
def _execute(self, command, position_independent):
""" Execute a method without triggering the log
"""
# Warning: not thread-safe:
self._replaying.append(True)
method = getattr(self, command.method_name)
if not position_independent:
state = self.pen()['pendown']
self.setpos(command.pos)
self.setheading(command.heading)
(self.pendown() if state else self.penup())
result = method(*command.args, **command.kwargs)
self._replaying.pop()
return result
Related
I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.
I'm in the process of creating a basic game in pygame at the moment, and one part of that is the procedural generation of new areas as you go off the screen. As a test, I'm looking to generate an object once per area by defining its variables, and then save that area's object within the class for if you come back to it later. Here's what I have at the moment:
#area_gen is set to "true" if you move to a new area
#swidth and sheight are set to the size of the screen
#x_area and y_area are defined as you change areas, acting as sector coordinates
#Red is defined in globals
class areas:
def __init__(self, coords):
self.coordinates = coords
self.generated = False
def gen_objects(self):
if not self.generated:
self.objs = []
obj_type = "test object"
center_x = random.randrange(105, swidth-25)
center_y = random.randrange(25, swidth - 175)
self.objs.append([obj_type, center_x, center_y])
self.generated = True
#Within The Game Loop
if area_gen == "true":
coords = str(str(x_area) + " " + str(y_area))
area = areas(coords)
area.gen_objects()
for thing in area.objs:
if thing[0] == "test object":
pygame.draw.rect(screen, Red, (thing[1], thing[2], 250, 250))
#Bottom of the Game Loop
area_gen = "false"
What I thought the self.generated variable would do was stop the new object generation if one already existed, but that doesn't seem to be working. The square still generates at a new location even if the area has already been visited. My knowledge on classes is relatively limited, so I'm a bit stuck as to where to go from here.
Pass area_gen into the constructor for the areas class, and set self.generated = area_gen. Does this work? I can't see enough of your code to know, to be honest.
So I want to display a label if someone tries to click play and there is no save file made yet. Then I want it to fade out. The while loop works, reducing the value of alpha to 0. And it displays the label as long as I don't have the self.remove_widget(no_save) added in but then it just stays as a solid label. Any help would be appreciated. Or is there an easier way to do this?
class StartMenu(Screen):
def check_save(self):
global save_state
if save_state == None:
color = (0,1,0,1)
while color[3] > 0:
no_save = Label(text='No save file found. Please press New Game', color=color)
self.add_widget(no_save)
color = color [:3] + (color[3] - (.1),)
time.sleep(.1)
self.remove_widget(no_save)
Rather than doing the fade out yourself, why not use the built in Animation functionality? Try something like this. I would also suggest moving save_state from the global realm to your class, and instead of creating and destroying the label every run, I would create at initialization and simply hide or show it as it becomes necessary.
class StartMenu(Screen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.save_state = None
no_save = Label('No save file found. Please press new game.', hidden=True)
self.add_widget(no_save)
def check_save(self):
if not self.save_state:
self.no_save.hidden = False
def hide_label(w): w.hidden = True
Animation(opacity=0, duration=1, on_complete=hide_label).start(self.no_save)
Quick shoutout to zeeMonkeys for pointing out the Animation solution in the comments before I did.
I guess my question is pretty much summed up in the title.
I am using an update call (similar to the one in the Pong tutorial). Within this call I update the points of a line. Though I can check that the points are indeed being updated, the actual line drawing is not.
I'll put some of the code up here:
class GraphInterface(Widget):
node = ObjectProperty(None)
def update(self, dt):
for widget in self.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
widget.check_connection()
class GraphEdge(Widget):
r = NumericProperty(1.0)
#determines if edge has an attached node
connected_point_0 = Property(False)
connected_point_1 = Property(False)
#provides details of the attached node
connected_node_0 = Widget()
connected_node_1 = Widget()
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
if (self.connected_point_1 is False):
print "collision"
self.connected_point_1 = True
self.connected_node_1 = node
del self.line.points[-2:]
self.line.points[-2:]+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
The idea is to check for collisions initially, and once a collision has been made, I attach the line to this node widget. The points are then update as I move the node around. However right now although the points are updated, the drawing of the line is not.
If you need anymore code or information please ask.
del self.line.points[-2:]
self.line.points[-2:]+=node.center
These lines bypass operations that set the property, so the VertexInstruction doesn't know anything has changed and doesn't redraw itself.
They're a bit strange anyway, it would be simpler to just write:
self.line.points = self.line.points[:-2] + node.center
This would also update the instruction graphics, because you set the property directly rather than only modifying the existing list.
Cutting through all the unrelated parts this is how I implemented it:
done=False
while(done==False)
#here goes some code for listening to the events
if (some condition fulfilled) then
screen.blit(background, [0,0])
screen.blit(some_sprite,[x,y])
elif (more conditions)
do something else
else
do even more stuff
pygame.display.flip()
Without the background update within this conditional statement this sprite doesn't get deleted, of course, os I get multiple copies on the screen. I have a strong suspicion this is by far not the optimal way of handling the situation, because blitting the image that doesn't change every time I need to do something else seems like a waste of resources.
I would appreciate any suggestions
Here's what I would recommend, based on personal experience. I built a tile-based game and over-engineered it a bit. My final solution looked a bit like this:
class Graphic(object):
def __init__(*some_args):
self.owner = which_object_owns_this_graphic
self.priority = some_int
# if more than one graphic are stacked on each other, which one to display?
self.surface = surface_to_draw_on
self.graphic = sprite_to_draw
#property
def x(self): return self.owner.x
#property
def y(self): return self.owner.y
def draw(self):
self.surface.blit(self.graphic, (self.x, self.y))
class Tile(object):
def __init__(*some_args):
self.x = x
self.y = y
self.graphic = some_default_Graphic_with_priority_minusone
self.contains = [self]
# list of objects that live here right now
#property
def topmost(self):
"""returns the graphic of the object with highest priority that is contained here"""
global_update_list = []
# anytime a tile is moved into or out of, place it here
Then in my event loop:
for tile in global_update_list:
tile.topmost.draw()
global_update_list = []
That prevented me from having to redraw the screen every time something moved, and I could just redraw the tile it moved out of and the tile it moved into.