I am creating a game where the player is a ship that has to dodge meteors that fall. I have 2 classes, the ship and the meteors. The meteors "fall" by having their canvas objects moved down their y axis and their y coordinates are subtracted by the number as the move function. I have an if statement that detects whether the meteors have fallen passed the border of the canvas, and it deletes those meteors, and creates new meteors at the top of the screen, thus making it seem that there are multiple meteors that are falling. The ship class has a similar function that detects if the ship has gone passed the sides of the canvas, which triggers the death function. I have a function in the meteor class that detects whether it is overlapping, with the help from Sneaky Turtle's answer. Now, it's almost finished, I just have one problem. After 3 "rounds" the meteors should get faster. How I implemented this was by having an if statement that checks if a variable is over 3. If not, the variable adds 1. When it reaches 3, it resets and adds (speed amount) to the speed attribute of the meteor,which is used when it moves. the problem is it only works on the first "wave" after that, the speed attribute stays the same. All the sound functions are commented off so that I don't have to upload the files.
Code:
from random import *
from tkinter import *
from time import *
print('''****Meteor Run****
Don't let the meteors hit you!
A-Left D-Right ''')
sleep(1.25)
#from game_sounds import*
root=Tk()
c = Canvas(width=800,height=600,bg="#37061a")
c.pack()
m1=0
m2=0
m3=0
m4=0
m5=0
m6=0
m7=0
m8=0
direction=0
speed=0
score = 0
cont=True
class ship:
def __init__(self,x1,y1,x2,y2):
self.x1=x1
self.y1=y1
self.x2=x2
self.y2=y2
self.hitbox3=387.5 + x1
self.shape=c.create_polygon(353+x1,380+y1,387.5+x1,310+y1,
420+x1,380+y1,fill="Blue")
def move(self):
global direction
if direction=="L":
self.x1 = self.x1-10
self.hitbox3 = self.hitbox3-10
c.move(self.shape,-10,0)
sleep(0.001)
root.update()
if direction=="R":
self.x1 = self.x1+10
self.hitbox3 = self.hitbox3+10
c.move(self.shape,10,0)
root.update()
self.test_lost_in_space()
sleep(0.001)
def death(self):
root.destroy()
print("You Lost!")
print("Score:",score)
# death_sound()
def test_lost_in_space(self):
if self.hitbox3<=0:
self.death()
if self.hitbox3 >=800:
self.death()
def ship_explode(self):
overlap = c.find_overlapping(353+self.x1,380+self.y1,420+self.x1,310+self.y1)
if overlap != (self.shape,):
self.death()
class meteor:
def __init__(self,x1,y1):
self.x1=x1
self.y1=y1
self.hitbox=89+x1
self.speed=.75
self.shape =c.create_polygon(1+x1,50+y1,34+x1,23+y1,67+x1,23+y1,
89+x1,57+y1,64+x1,71+y1,27+x1,71+y1,fill="brown")
def meteor_return(self):
global m1
global m2
global m3
global m4
global m5
global m6
global m7
global m8
global speed
global score
if self.y1 >=600:
c.delete(self)
m1=meteor(randrange(0,700),randrange(6,12))
m2=meteor(randrange(0,700),randrange(6,12))
m3=meteor(randrange(0,700),randrange(6,12))
m4=meteor(randrange(0,700),randrange(6,12))
m5=meteor(randrange(0,700),randrange(6,12))
m6=meteor(randrange(0,700),randrange(6,12))
m7=meteor(randrange(0,700),randrange(6,12))
m8=meteor(randrange(0,700),randrange(6,12))
if speed!=3:
speed=speed +1
score = score + 1
# lvl_up()
if speed==3:
speed=0
self.speed= self.speed + .5
print(self.speed)
score = score + 5
# lvl_up_2()
def meteor_fall(self):
global speed
self.y1 = self.y1 + self.speed
c.move(self.shape,0,self.speed)
root.update()
self.meteor_return()
# ship1.ship_explode()
def ship_move(event):
global direction
if event.keysym=="a":
direction="L"
ship1.move()
if event.keysym=="d":
direction="R"
ship1.move()
ship1 =ship(0,0,0,0)
m1=meteor(randrange(0,200),randrange(6,12))
m2=meteor(randrange(200,400),randrange(6,12))
m3 =meteor(randrange(400,600),randrange(6,12))
m4=meteor(randrange(600,800),randrange(6,12))
m5 =meteor(randrange(400,600),randrange(6,12))
m6=meteor(randrange(600,800),randrange(6,12))
m7 =meteor(randrange(400,600),randrange(6,12))
m8=meteor(randrange(600,800),randrange(6,12))
c.bind_all("<KeyPress-a>",ship_move)
c.bind_all("<KeyPress-d>",ship_move)
while cont ==True:
m1.meteor_fall()
m2.meteor_fall()
m3.meteor_fall()
m4.meteor_fall()
m5.meteor_fall()
m6.meteor_fall()
m7.meteor_fall()
m8.meteor_fall()
c.bind_all("<KeyPress-a>",ship_move)
c.bind_all("<KeyPress-d>",ship_move)
ship1.death()
Instead of comparing the x and y coordinates of each meteor and seeing whether they are within the bounds of the ships co-ordinates, I would use find_overlapping to detect what actually overlaps the Ship.
If you have nothing on your canvas except the meteors and ship, you could implement something like:
ship_coords = c.coords(self.shape)
overlap = c.find_overlapping(*ship_coords)
if overlap != (self.shape, ):
#Code to run when the meteors collide with the ship.
...
Where (self.shape, ) is the tuple returned from the coordinates you pass to find_overlapping. I recommend reading documentation on the Tkinter canvas, it seems like you have just started learning! Hopefully this helps for the moment however.
If you need to specifically detect what items are overlapping with your ship, then there are plenty of other questions and answers on Stack Overflow about find_overlapping.
Related
I write a simple app, While drag or scale the MainView, The PartView rubberband will show scene area in PartView.But sometime the rubber-band become a line, and sometime the rubberband disappear.So How to aviod this phenomenon appear?And sometime I want the rubberband only show it's border-line, not contain it's light-blue rectangle,So how can I write code ?
My Code
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import random
import math
r = lambda : random.randint(0, 255)
r255 = lambda : (r(), r(), r())
class Scene(QGraphicsScene):
def __init__(self):
super().__init__()
for i in range(1000):
item = QGraphicsEllipseItem()
item.setRect(0, 0, r(), r())
item.setBrush(QColor(*r255()))
item.setPos(r()*100, r()*100)
self.addItem(item)
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
super().drawBackground(painter, rect)
self.sigExposeRect.emit(rect)
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
def __init__(self):
super().__init__()
self.r = QRubberBand(QRubberBand.Rectangle, self)
self.r.setWindowOpacity(1)
self.r.show()
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.m.sigExposeRect.connect(self.onExposeRect)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
def onExposeRect(self, rect: QRectF):
prect = self.p.mapFromScene(rect).boundingRect()
self.p.r.setGeometry(prect)
app = QApplication([])
v = View()
v.show()
app.exec()
My Result
I think the problem is that the qrect passed to the drawBackground method is only includes the portion of the background that wasn't previously in the viewport. Not positive about that though.
Either way I was able to achieve your goal of avoiding only a section of the rubber band being drawn, by sending the area for the entire viewport to the onExposeRect slot.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
# Adding this next line was the only change I made
orect = self.mapToScene(self.viewport().geometry()).boundingRect()
super().drawBackground(painter, rect)
self.sigExposeRect.emit(orect) # and passing it to the slot.
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
A fundamental aspect about Graphics View is its high performance in drawing even thousands of elements.
To achieve this, one of the most important optimization is updating only the portions of the scene that really need redrawing, similar to what item views do, as they normally only redraw the items that actually require updates, instead of always painting the whole visible area, which can be a huge bottleneck.
This is the reason for which overriding drawBackground is ineffective: sometimes, only a small portion of the scene is updated (and, in certain situations, even no update is done at all), and the rect argument of drawBackground only includes that portion, not the whole visible area. The result is that in these situations, the signal will emit a rectangle that will not be consistent with the visible area.
Since the visible area is relative to the viewport of the scroll area, the only safe way to receive updates about that area is to connect to the horizontal and vertical scroll bars (which always work even if they are hidden).
A further precaution is to ensure that the visible rectangle is also updated whenever the scene rect is changed (since that change might not be reflected by the scroll bars), by connecting to the sceneRectChanged signal and also overriding the setSceneRect() of the source view. Considering that the changes in vertical and scroll bars might coincide, it's usually a good idea to delay the signal with a 0-delay QTimer, so that it's only sent once when more changes to the visible area happen at the same time.
Note that since you're not actually using the features of QRubberBand, there's little use in its usage, especially if you also need custom painting. Also, since the rubber band is a child of the view, it will always keep its position even if the preview view is scrolled.
In the following example I'll show two ways of drawing the "fake" rubber band (but choose only one of them, either comment one or the other to test them) that will always be consistent with both the source and target views.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signalDelay = QTimer(self, singleShot=True, interval=0,
timeout=self.emitExposeRect)
# signals might have arguments that collide with the start(interval)
# override of QTimer, let's use a basic lambda that ignores them
self.delayEmit = lambda *args: self.signalDelay.start()
self.verticalScrollBar().valueChanged.connect(self.delayEmit)
self.horizontalScrollBar().valueChanged.connect(self.delayEmit)
def emitExposeRect(self):
topLeft = self.mapToScene(self.viewport().geometry().topLeft())
bottomRight = self.mapToScene(self.viewport().geometry().bottomRight())
self.sigExposeRect.emit(QRectF(topLeft, bottomRight))
def setScene(self, scene):
if self.scene() == scene:
return
if self.scene():
try:
self.scene().sceneRectChanged.disconnect(self.delayEmit)
except TypeError:
pass
super().setScene(scene)
if scene:
scene.sceneRectChanged.connect(self.delayEmit)
def setSceneRect(self, rect):
super().setSceneRect(rect)
self.delayEmit()
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
exposeRect = None
def updateExposeRect(self, rect):
if self.exposeRect != rect:
self.exposeRect = rect
self.viewport().update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.exposeRect:
return
rect = self.mapFromScene(self.exposeRect).boundingRect()
# use either *one* of the following:
# 1. QStyle implementation, imitates QRubberBand
qp = QStylePainter(self.viewport())
opt = QStyleOptionRubberBand()
opt.initFrom(self)
opt.rect = rect
qp.drawControl(QStyle.CE_RubberBand, opt)
# 2. basic QPainter
qp = QPainter(self.viewport())
color = self.palette().highlight().color()
qp.setPen(self.palette().highlight().color())
# for background
bgd = QColor(color)
bgd.setAlpha(40)
qp.setBrush(bgd)
qp.drawRect(rect)
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
self.m.sigExposeRect.connect(self.p.updateExposeRect)
PS: please use single letter variables when they actually make sense (common variables, coordinates, loop placeholders, etc.), not for complex objects, and especially for attributes: there's no benefit in using self.m or self.p, and the only result you get is to make code less readable to you and others.
I started coding a little ago and already there is a bug I can't figure out.
I'm trying to make a Minesweeper-like program. My issue is that when I try to get a cell to display the picture of a flag, a mine or any other image (not number or letters, that I could manage very well), the grid just breaks.
Here is a very condensed part of my code to show my issue :
from tkinter import *
from functools import partial as part
from PIL import Image, ImageTk
class Grid:
def __init__(self, master):
self.master = master
self.images, self.frames, self.grid = [], [], []
self.images = {
"mine": ImageTk.PhotoImage(image=Image.open("Sprites/mine.png").resize((20, 20))),
"flag": ImageTk.PhotoImage(image=Image.open("Sprites/flag.png").resize((20, 20))),
"ghost_flag": ImageTk.PhotoImage(image=Image.open("Sprites/ghost_flag.png").resize((20, 20)))}
self.draw_grid()
def _on_click(self, event, x, y, *args):
if event == "left-click":
self.grid[x][y]["widget"]["image"] = self.images["mine"]
if event == "right-click":
self.grid[x][y]["widget"]["image"] = self.images["flag"]
def draw_grid(self):
for x in range(0, 9):
self.frames.append(Frame(self.master))
self.frames[len(self.frames) - 1].pack(side="top")
self.grid.append([])
for y in range(0, 9):
self.grid[x].append({"widget": Label(self.frames[x], bg="#888888", width=4, height=2, image=None)})
self.grid[x][y]["widget"].pack(fill=BOTH, expand=1, side="left", padx=1, pady=1)
self.grid[x][y]["widget"].bind("<Button-1>", part(self._on_click, "left-click", x, y))
self.grid[x][y]["widget"].bind("<Button-2>", part(self._on_click, "right-click", x, y))
self.grid[x][y]["widget"].bind("<Button-3>", part(self._on_click, "right-click", x, y))
root = Tk()
grid = Grid(root)
root.mainloop()
I obviously got rid of any Minesweeper core code, and the program only places mines on left clicks and flags on right clicks. The two only parts of the code that lock a size in are the .resize(x, y) function and the width and height arguments in the Label creation.
The result is something like that :
See picture
Hope someone can help me,
Have a good day
Edit: Also, if someone knows how to .bind more efficiently, I would be delighted.
Edit 2: After a little bit of thinking, I realised there is a dumb solution, which is to do something like :
self.grid[x][y]["widget"]["width"] = 92
But I would appreciate a cleaner solution.
I'm trying to make a blackjack game in Pygame. I've got to the point where if I take a new card the cards start showing up. However, there are some issues:
The screen is blank until the first card is drawn at which point you see 3 cards. so you never see the first two on their own.
How would I go about making it so that it can be played and then followed by dealers cards showing as he plays(i have the dealers logic done already.
The GUI screen is always not responding when you click it, how to mitigate that.
The code:
from random import shuffle
import pygame
class card:
def __init__(self,suit,rank):
self.suit = suit
self.rank = rank
if type(self.rank) == int:
self.value = rank
elif self.rank == 'ACE':
self.value = 11
else:
self.value = 10
def showval(self):
print('{} of {}'.format(self.rank, self.suit))
print('card value is: ', self.value)
class deck:
def __init__(self):
ranks = [i for i in range(2, 11)] + ['JACK', 'QUEEN', 'KING',
'ACE']
suits = ['SPADE', 'HEART', 'DIAMOND', 'CLUB']
self.cards = []
for i in suits:
for j in ranks:
self.cards.append(card(i,j))
self.shuffle()
def __len__(self):
return len(self.cards)
def shuffle(self):
shuffle(self.cards)
class hand:
def __init__(self,name=None):
self.contents = []
self.deal(2,theDeck)
self.show()
def deal(self,x,theDeck):
if len(theDeck) < x:
theDeck = deck()
for j in range (x):
self.contents.append(theDeck.cards.pop(0))
self.total = sum(i.value for i in self.contents)
def show(self):
print('your cards are:')
for i in self.contents:
print(i.rank,'of',i.suit)
def makedecision(self):
hitorstand = ''
while hitorstand not in ('h' , 'H' , 's' , 'S'):
self.show()
hitorstand = input('enter "H" to hit or "S" to stand: ')
return hitorstand
def play(self):
decision = self.makedecision()
if decision in('h', 'H') :
self.deal(1,theDeck)
elif decision in ('s', 'S'):
print('ok you have chosen to stand.')
self.show()
### end of classes
theDeck = deck()
pygame.init()
cardpngs={'CLUB':{'ACE':pygame.image.load('pcp\\CLUb
\\1.png'),2:pygame.image.load('pcp\\CLUB
\\2.png'),3:pygame.image.load('pcp\\CLUB
\\3.png'),4:pygame.image.load('pcp\\CLUB},'HEART':
{'ACE':pygame.image.load('pcp\\HEART\\1.png').............}}
display_width = 800
display_height = 600
black = (0, 0 ,0)
white =(255,255, 255)
red = (255, 0, 0)
back_ground = pygame.image.load('tablesmall.png')
myhand=hand()
def start_game():
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('BlackJack')
gameDisplay.blit(back_ground,(1,1))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit()
gameExit=True
card_xy = 1
myhand.play()
for i in myhand.contents:
gameDisplay.blit((cardpngs[i.suit][i.rank]),
(card_xy,card_xy))
card_xy+=30
pygame.display.update()
if __name__ == '__main__':
start_game()
The escape button doesn't seem to work to exit as the (if event.key == pygame.K_ESCAPE) would suggest. the plan is to also add some buttons to facilitate laying the game.
You asked 3 separate questions, unrelated each other. Each one should be a post on stack overflow.
I'm going to answer the first one, which is simpler to solve, and make only a couple of comments on the other to try to point what I think is the right way to go.
The screen is blank until the first card is drawn at which point you see 3 cards. so you never see the first two on their own. I'm going to answer the first which is more simple, and make only few comments on the others.
This is because when you call myhand=hand() you give the first two cards to the player. Then in the main loop you call myhand.play() which asks the user to hit a card or stay. You blit the cards on the screen and call pygame.display.update() for the first time after the player choice, so if the player chooses to hit, 3 cards are blit. The easiest solution is to move the call to myhand.play() after the call to pygame.display.update(). So first the cards are blit, and then the player can make a choice.
How would I go about making it so that it can be played and then followed by dealers cards showing as he plays.
You can consider the dealer as another player. Make a new class Dealer, which inherits from hand, and override the makedecision method. Instead of asking the player, put here the dealer choice.
Of course you also need to replicate this portion of code:
card_xy = 1
for i in myhand.contents:
gameDisplay.blit((cardpngs[i.suit][i.rank]),(card_xy,card_xy))
card_xy+=30
for the dealer to blit the delaer's cards, at a different place on the screen.
The GUI screen is always not responding when you click it, how to mitigate that.
This is really to broad to answer here, since your code does not show any attempt on this aspect.
The general answer is that you need to catch the MOUSEBUTTONUP and / or MOUSEBUTTONDOWN events in the event loop, obtaining the mouse position (to identify for example the card clicked by the user) and associate a proper action.
I made a Python turtle program that recursively generated a fractal tree, but, since it often took a few hours to fully draw, I wanted to try to use multithreading to have multiple turtles working together.
I was able to get two turtles moving at the same time, but, in this much more complex situation, everything seemed to fall apart. I've tried this many different ways, and thought this final solution would be the one, but it just throws a bunch of errors.
Here's my code:
import turtle
import threading
from queue import Queue
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def getx(self):
return self.xpos
def gety(self):
return self.ypos
def geth(self):
return self.heading
class Turtle(turtle.Turtle):
def tolocation(self, location):
self.penup()
self.setx(location.getx())
self.sety(location.gety())
self.setheading(location.geth())
self.pendown()
def get_location(self):
return Location(self.xcor(), self.ycor(), self.heading())
def draw_tree(self, startpos=Location(), size=100):
tm.q.put(self.tolocation(startpos))
for _ in range(size):
tm.q.put(self.forward(1))
for _ in range(45):
tm.q.put(self.right(1))
t2 = Turtle()
t2.speed(0)
tm.new_thread(t2.draw_tree, self.get_location(), size / 2)
for _ in range(90):
tm.q.put(self.left(1))
tm.new_thread(self.draw_tree, self.get_location(), size / 2)
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = []
def new_thread(self, func, *args):
self.threads.append(threading.Thread(target=func, args=(args,)))
self.threads[-1].daemon = True
self.threads[-1].start()
def process_queue(self, scr):
while not self.q.empty():
(self.q.get())(1)
if threading.active_count() > 1:
scr.ontimer(self.process_queue(scr), 100)
tm = ThreadManager()
scr = turtle.Screen()
t1 = Turtle()
t1.speed(0)
tm.new_thread(t1.draw_tree)
tm.process_queue(scr)
scr.exitonclick()
Can anyone give me an idea of where I went wrong here? The error messages say something along the lines of the recursion going too deep when process_queue calls itself. Am I using scr.ontimer() wrong?
There are several problems with your code:
draw_tree() doesn't have a base case to stop it's (conceptual)
recursion, it just keeps creating new threads.
Each call to draw_tree() divides size in half using floating
division so range(size) will fail as range() can only take ints.
Your calls to self.get_location() are not valid as it tells you
where the turtle is, not where it will be once the main thread
finishes processing outstanding graphics commands. You have to
compute where you will be, not look where you are.
This call, tm.q.put(self.tolocation(startpos)) isn't valid -- you
either need to do tm.q.put(self.tolocation, startpos) or call
tm.q.put() on each command inside self.tolocation().
You can't create new turtles anywhere but the main thread, as they
invoke tkinter on creation and that'll be on the wrong (not main)
thread. In my rework below, I simply preallocate them.
args=(args,) is incorrect -- should be args=args as args
is already in the correct format.
You don't need to create two new threads at each branching point, just
one. The new turtle goes one way, the old turtle continues on the
other.
Below is my rework of your code to address the above and other issues:
import math
import threading
from queue import Queue
from turtle import Turtle, Screen
class Location:
def __init__(self, xpos=0, ypos=0, heading=90):
self.xpos = xpos
self.ypos = ypos
self.heading = heading
def clone(self):
return Location(self.xpos, self.ypos, self.heading)
class Terrapin(Turtle):
def tolocation(self, location):
tm.q.put((self.penup,))
tm.q.put((self.setx, location.xpos))
tm.q.put((self.sety, location.ypos))
tm.q.put((self.setheading, location.heading))
tm.q.put((self.pendown,))
def draw_tree(self, startpos, size=100):
if size < 1:
return
self.tolocation(startpos)
tm.q.put((self.forward, size))
angle = math.radians(startpos.heading)
startpos.xpos += size * math.cos(angle)
startpos.ypos += size * math.sin(angle)
tm.q.put((self.right, 45))
startpos.heading -= 45
tm.new_thread(pond.get().draw_tree, startpos.clone(), size / 2)
tm.q.put((self.left, 90))
startpos.heading += 90
self.draw_tree(startpos, size / 2)
pond.put(self) # finished with this turtle, return it to pond
class ThreadManager:
def __init__(self):
self.q = Queue()
self.threads = Queue()
def new_thread(self, method, *arguments):
thread = threading.Thread(target=method, args=arguments)
thread.daemon = True
thread.start()
self.threads.put(thread)
def process_queue(self):
while not self.q.empty():
command, *arguments = self.q.get()
command(*arguments)
if threading.active_count() > 1:
screen.ontimer(self.process_queue, 100)
screen = Screen()
# Allocate all the turtles we'll need ahead as turtle creation inside
# threads calls into Tk which fails if not running in the main thread
pond = Queue()
for _ in range(100):
turtle = Terrapin(visible=False)
turtle.speed('fastest')
pond.put(turtle)
tm = ThreadManager()
tm.new_thread(pond.get().draw_tree, Location())
tm.process_queue()
screen.exitonclick()
if instead of large steps:
tm.q.put((self.right, 45))
you want to break down graphic commands into small steps:
for _ in range(45):
tm.q.put((self.right, 1))
that's OK, I just wanted to get the code to run. You need to figure out if this gains you anything.
I am creating a game and unable to detect turtle position in this list
I am using python3 and turtle.
the objective of this code is to create a filled shape when turtle intersect with its's own path
import turtle
t=turtle.Turtle()
t.fillcolor("red")
t.begin_fill()
s=turtle.Screen()
t.penup()
status=True
penstatus=False
t.speed(1)
x=[]
def go1():
t.left(-(t.heading()))
t.left(90)
def go2():
t.left(-(t.heading()))
# t.left(90)
def go3():
t.left(-(t.heading()))
t.left(270)
def go4():
t.left(-(t.heading()))
t.left(180)
def paint():
global penstatus
if penstatus==False:
penstatus=True
t.down()
else:
t.up()
def detect():
global status
a=t.position()
if a in x:
status=False
print("yes")
while status:
global x
s.onkeypress(go1, "Up")
s.onkeypress(go2, "Right")
s.onkeypress(go3, "Down")
s.onkeypress(go4, "Left")
s.onkeypress(paint,"space")
s.listen()
x.append(t.position())
t.fd(5)
detect()
t.end_fill()
s.mainloop()
it works sometimes but result of filling also gets wrong
There are two reasons you're having trouble detecting if the current position is in your list of past positions. The first is you "hop" five pixels at a time so you are potentially crossing the line at a "filled in" segment, not one you were actually positioned on.
The second is that turtle positions are floating point numbers and can be very slightly different when you come back to the same spot. We can fix both problems by not comparing directly but asking if the distance between points is less than our "hop" distance.
My rework of your code below implements this approach. It also changes how your keys work slightly; changes the logic to only include visible lines in the filled graphic; and is completely event-based. It also has a reset, "r", key to start a new drawing. You can back out any changes you don't like, the back position detection is still applicable:
from turtle import Turtle, Screen
DISTANCE = 3
def go_up():
turtle.setheading(90)
def go_right():
turtle.setheading(0)
def go_down():
turtle.setheading(270)
def go_left():
turtle.setheading(180)
def toggle_pen():
if turtle.isdown():
turtle.penup()
else:
turtle.pendown()
turtle.begin_fill() # ignore pending begin_fill, start anew
def reset_drawing():
global positions
turtle.reset()
turtle.fillcolor('red')
turtle.speed('fastest')
turtle.penup()
positions = []
move()
def move():
for position in positions:
if turtle.distance(position) < DISTANCE:
turtle.end_fill()
return
if turtle.isdown():
positions.append(turtle.position())
turtle.forward(DISTANCE)
screen.ontimer(move, 100)
screen = Screen()
screen.onkeypress(go_up, 'Up')
screen.onkeypress(go_right, 'Right')
screen.onkeypress(go_down, 'Down')
screen.onkeypress(go_left, 'Left')
screen.onkeypress(toggle_pen, 'space')
screen.onkeypress(reset_drawing, 'r')
screen.listen()
turtle = Turtle()
positions = None # make sure global is defined
reset_drawing()
screen.mainloop()