Threading in Tkinter and Python3 - python

I was adjusting this code to my needs in PyCharm where it worked well, without any exceptions and errors. When I was trying it out in Jupyter Notebook it worked, but when I closed the Tkinter window, I get the exception Exception in thread Thread-: and the Error RuntimeError: main thread is not in main loop .
The traceback is: line 90, in run - line 51, in do action - line 30, in try_move
I tried to find the solution, but I only found mtTkinter for Python2.
Since I am new to threading, I don't know how to solve this problem and why it is only showing in Jupyter Notebook. Is it possible that Jupyter Notebook is the source of the problem?
The code is:
from tkinter import *
import threading
import time
def render_grid():
global specials, walls, WIDTH, x, y, player
for i in range(x):
for j in range(y):
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
temp = {}
cell_scores[(i, j)] = temp
for (i, j, c, w) in specials:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
for (i, j) in walls:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
def set_cell_score(state, action, val):
global cell_score_min, cell_score_max
def try_move(dx, dy):
global player, x, y, score, walk_reward, me, restart
if restart:
restart_game()
new_x = player[0] + dx
new_y = player[1] + dy
score += walk_reward
if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
new_y * WIDTH + WIDTH * 8 / 10)
player = (new_x, new_y)
for (i, j, c, w) in specials:
if new_x == i and new_y == j:
score -= walk_reward
score += w
if score > 0:
print("Success! score: ", score)
else:
print("Fail! score: ", score)
restart = True
return
# print "score: ", score
def call_up(event):
try_move(0, -1)
def call_down(event):
try_move(0, 1)
def call_left(event):
try_move(-1, 0)
def call_right(event):
try_move(1, 0)
def restart_game():
global player, score, me, restart
player = (0, y - 1)
score = 1
restart = False
board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)
def has_restarted():
return restart
def start_game():
master.mainloop()
master = Tk()
master.resizable(False, False)
cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]
board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04
walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}
discount = 0.3
states = []
Q = {}
for i in range(x):
for j in range(y):
states.append((i, j))
for state in states:
temp = {}
for action in actions:
temp[action] = 0.1
set_cell_score(state, action, temp[action])
Q[state] = temp
for (i, j, c, w) in specials:
for action in actions:
Q[(i, j)][action] = w
set_cell_score((i, j), action, w)
def do_action(action):
s = player
r = -score
if action == actions[0]:
try_move(0, -1)
elif action == actions[1]:
try_move(0, 1)
elif action == actions[2]:
try_move(-1, 0)
elif action == actions[3]:
try_move(1, 0)
else:
return
s2 = player
r += score
return s, action, r, s2
def max_Q(s):
val = None
act = None
for a, q in Q[s].items():
if val is None or (q > val):
val = q
act = a
return act, val
def inc_Q(s, a, alpha, inc):
Q[s][a] *= 1 - alpha
Q[s][a] += alpha * inc
set_cell_score(s, a, Q[s][a])
def run():
global discount
time.sleep(1)
alpha = 1
t = 1
while True:
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
time.sleep(0.1)
render_grid()
master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)
me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
width=1, tag="me")
board.grid(row=0, column=0)
t = threading.Thread(target=run)
t.setDaemon(True)
t.start()
start_game()

Probably all GUIs don't like to run in threads and all changes in widgets should be in main thread (but calculations still can be in separated threads)
In tkinter you could use master.after(milliseconds, function_name) instead of thread and while-loop to run code periodically - and this will works like loop but in current thread.
def run():
global t
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
# run again after 100ms
master.after(100, run)
and later start it as normal function
#master.after(100, run) # run after 100ms
run() # run at once
start_game()
Full working code:
EDIT:
You may also use global variable - ie. running = True - to stop looping before destroying GUI.
It may need also master.protocol("WM_DELETE_WINDOW", on_delete) to execute function when you click closing button [X]
from tkinter import *
#import threading
import time
def render_grid():
global specials, walls, WIDTH, x, y, player
for i in range(x):
for j in range(y):
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
temp = {}
cell_scores[(i, j)] = temp
for (i, j, c, w) in specials:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
for (i, j) in walls:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
def set_cell_score(state, action, val):
global cell_score_min, cell_score_max
def try_move(dx, dy):
global player, x, y, score, walk_reward, me, restart
if restart:
restart_game()
new_x = player[0] + dx
new_y = player[1] + dy
score += walk_reward
if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
new_y * WIDTH + WIDTH * 8 / 10)
player = (new_x, new_y)
for (i, j, c, w) in specials:
if new_x == i and new_y == j:
score -= walk_reward
score += w
if score > 0:
print("Success! score: ", score)
else:
print("Fail! score: ", score)
restart = True
return
# print "score: ", score
def call_up(event):
try_move(0, -1)
def call_down(event):
try_move(0, 1)
def call_left(event):
try_move(-1, 0)
def call_right(event):
try_move(1, 0)
def restart_game():
global player, score, me, restart
player = (0, y - 1)
score = 1
restart = False
board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)
def has_restarted():
return restart
def start_game():
master.mainloop()
def do_action(action):
s = player
r = -score
if action == actions[0]:
try_move(0, -1)
elif action == actions[1]:
try_move(0, 1)
elif action == actions[2]:
try_move(-1, 0)
elif action == actions[3]:
try_move(1, 0)
else:
return
s2 = player
r += score
return s, action, r, s2
def max_Q(s):
val = None
act = None
for a, q in Q[s].items():
if val is None or (q > val):
val = q
act = a
return act, val
def inc_Q(s, a, alpha, inc):
Q[s][a] *= 1 - alpha
Q[s][a] += alpha * inc
set_cell_score(s, a, Q[s][a])
def run():
global t
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
# run again after 100ms
if running:
master.after(100, run)
def on_delete():
global running
running = False
master.destroy()
# --- main ---
master = Tk()
master.resizable(False, False)
cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]
board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04
walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}
discount = 0.3
states = []
Q = {}
for i in range(x):
for j in range(y):
states.append((i, j))
for state in states:
temp = {}
for action in actions:
temp[action] = 0.1
set_cell_score(state, action, temp[action])
Q[state] = temp
for (i, j, c, w) in specials:
for action in actions:
Q[(i, j)][action] = w
set_cell_score((i, j), action, w)
render_grid()
master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)
me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
width=1, tag="me")
board.grid(row=0, column=0)
#t = threading.Thread(target=run)
#t.setDaemon(True)
#t.start()
running = True
alpha = 1
t = 1
run() # run at once
#master.after(100, run) # run after 100ms
master.protocol("WM_DELETE_WINDOW", on_delete)
start_game()

Related

A few minor problems with my tkinter sand simulation

I have made a sand simulation in tkinter. Only tkinter. No pygame, no matplotlib, none of that. It works great, it looks great, but nothing in life is perfect.
Minor Problem #1: When I hold down and let the sand fall, the column where the cursor is stays the same color. I haven't been able to track this down yet.
Minor Problem #2: When I create happy little piles of sand, the sides seem to build up from the bottom rather that fall from the top. I suspect this is a rendering issue, but I also haven't found the reason for this.
That's all the problems I've noticed, but if you see any others feel free to let me know.
Code:
from tkinter import *
from random import choice, random
from copy import deepcopy
from colorsys import hsv_to_rgb
CELLSIZE = 30
AIR = 0
WALL = 1
SAND = 2
BG = '#cef'
SANDCOLOR = (45, 45, 86)
WALLCOLOR = (224, 37, 34)
TARGETFPS = 100
def randomColor(h, s, v):
h, s, v = (h / 360), s / 100, v / 100
s += (random() - 0.5) * 0.1
v += (random() - 0.5) * 0.1
if s < 0: s = 0
if s > 1: s = 1
if v < 0: v = 0
if v > 1: v = 1
r, g, b = [round(i * 255) for i in hsv_to_rgb(h, s, v)]
return '#%02x%02x%02x' % (r, g, b)
class App:
def __init__(self):
global WIDTH, HEIGHT, SANDCOLOR, WALLCOLOR
self.master = Tk()
self.master.title('Sand Simulation')
self.master.resizable(0, 0)
self.master.attributes('-fullscreen', True)
WIDTH = self.master.winfo_screenwidth() // CELLSIZE
HEIGHT = self.master.winfo_screenheight() // CELLSIZE
Width, Height = WIDTH * CELLSIZE, HEIGHT * CELLSIZE
self.canvas = Canvas(self.master, width=Width, height=Height, bg=BG, highlightthickness=0)
self.canvas.pack()
self.map = [[AIR] * WIDTH for i in range(HEIGHT)]
self.colors = [[BG] * WIDTH for i in range(HEIGHT)]
self.positions = []
for x in range(WIDTH):
for y in range(HEIGHT):
self.positions.append([x, y])
self.positions.reverse()
self.dragging, self.dragX, self.dragY = False, 0, 0
self.canvas.bind('<Button-1>', self.mouseDown)
self.canvas.bind('<B1-Motion>', self.mouseDrag)
self.canvas.bind('<ButtonRelease-1>', self.mouseUp)
## self.images = [PhotoImage(file='images/sandButton.png'), PhotoImage(file='images/sandButtonActivated.png'),
## PhotoImage(file='images/wallButton.png'), PhotoImage(file='images/wallButtonActivated.png')]
self.images = [PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank()]
self.sandButton = self.canvas.create_image(125, 125, anchor='center', image=self.images[1])
self.wallButton = self.canvas.create_image(125, 325, anchor='center', image=self.images[2])
self.drawingMode = 'SAND'
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
self.master.mainloop()
def swapBlocks(self, x1, y1, x2, y2):
block1 = self.map[y1][x1]
color1 = self.colors[y1][x1]
self.map[y1][x1] = self.map[y2][x2]
self.colors[y1][x1] = self.colors[y2][x2]
self.map[y2][x2] = block1
self.colors[y2][x2] = color1
def mouseDown(self, event):
if 50 < event.x < 200 and 50 < event.y < 200:
self.drawingMode = 'SAND'
self.canvas.itemconfig(self.sandButton, image=self.images[1])
self.canvas.itemconfig(self.wallButton, image=self.images[2])
elif 50 < event.x < 200 and 250 < event.y < 400:
self.drawingMode = 'WALL'
self.canvas.itemconfig(self.sandButton, image=self.images[0])
self.canvas.itemconfig(self.wallButton, image=self.images[3])
else:
self.dragging = True
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseDrag(self, event):
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseUp(self, event):
self.dragging = False
def updateParticles(self):
if self.dragging:
color = choice(['red', 'white', 'blue'])
if self.drawingMode == 'SAND':
self.map[self.dragY][self.dragX] = SAND
self.colors[self.dragY][self.dragX] = randomColor(SANDCOLOR[0], SANDCOLOR[1], SANDCOLOR[2])
elif self.drawingMode == 'WALL':
self.map[self.dragY][self.dragX] = WALL
self.colors[self.dragY][self.dragX] = randomColor(WALLCOLOR[0], WALLCOLOR[1], WALLCOLOR[2])
for block in self.positions:
x, y = block
block = self.map[y][x]
if block == SAND:
if y == HEIGHT - 1:
below = WALL
else:
below = self.map[y + 1][x]
if below == AIR:
self.swapBlocks(x, y, x, y + 1)
else:
left, right, belowLeft, belowRight = AIR, AIR, AIR, AIR
if y == HEIGHT - 1:
belowLeft, belowRight = WALL, WALL
else:
if x == 0:
belowLeft = WALL
left = WALL
else:
belowLeft = self.map[y + 1][x - 1]
left = self.map[y][x - 1]
if x == WIDTH - 1:
belowRight = WALL
right = WALL
else:
belowRight = self.map[y + 1][x + 1]
right = self.map[y][x + 1]
if belowLeft == AIR and belowRight == AIR:
if choice([True, False]):
if left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
else:
if right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
else:
if belowLeft == AIR and left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
if belowRight == AIR and right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
def renderMap(self, previousMap):
for block in self.positions:
x, y = block
previousBlock = previousMap[y][x]
currentBlock = self.map[y][x]
x1, y1 = x * CELLSIZE, y * CELLSIZE
x2, y2 = x1 + CELLSIZE, y1 + CELLSIZE
if previousBlock == AIR and currentBlock != AIR:
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
if previousBlock != AIR and currentBlock == AIR:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if previousBlock != AIR and currentBlock != AIR and previousBlock != currentBlock:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
self.canvas.update()
def frame(self):
previousMap = deepcopy(self.map)
self.updateParticles()
self.renderMap(previousMap)
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
def main():
app = App()
if __name__ == '__main__':
main()
Please help me fix any bugs, glitches, etc...
Problem #1 is caused by the fact that your rendering function doesn't create a new rectangle if both the old and new block types are sand. As the sand falls in a column, the first bit of sand causes rectangles to be created with its color, and because another bit of sand always falls in its place on the next frame, it just stays that color. You could compare old and new colors to see which blocks need refreshing, or store which blocks changed.
Problem #2 is related to the order of your positions. You're inserting [x,y] positions into the positions array from the left to the right, one column at a time, each column from top to bottom, and then reversing the result, which gives you an ordering of bottom to top per column with the columns from right to left. So when you iterate through the positions, you're essentially sweeping from right to left as you process the blocks, so any block that falls to the left is going to be reprocessed in the same update, and as it keeps falling to the left it will keep being reprocessed, until it settles, and all of that happened in one frame. So particles will seem to fall to the left instantly.
I bet you don't have this problem with particles falling to the right, which is why on the right side of your image you have some diagonal bands of color: problem #1 is happening there too any time two sand blocks fall to the right in sequence.
You can fix problem #2 by reordering your width and height loops when you set up self.positions. Since blocks always move down one row when they are moved, iterating from bottom to top will always update each particle only once. If you ever introduce anything that makes particles move up, you'll need a better solution.

Why does the text get scaled?

I'm trying to move a text alongside the player character but it gets scaled weird...
Here's a picture of the player character alongside the text "test":
It should be some kind of antialiasing since that is off in the code for the moving text:
import pygame, sys
from pygame.locals import *
class MovingText():
def __init__(self, text, font_size, color, surface, target, off_1, off_2):
self.font = pygame.font.SysFont(None, font_size)
self.textobj = self.font.render(text, 0, color)
self.textrect = self.textobj.get_rect()
self.surface = surface
self.target = target
self.offset = (off_1, off_2)
self.textrect.center = self.target_pos()
def update(self):
self.textrect.center = self.target_pos()
self.surface.blit(self.textobj, self.textrect.center)
def target_pos(self):
pos = self.target.rect.center
return pos[0] + self.offset[1], pos[1] + self.offset[0]
What I think is causing the problem is either when the text gets created, here:
self.follow_text = movingtext.MovingText('test', 10, (255, 255, 255), self.display, self.player, 10, 5)
If you think it's something else you're free to check the rest of the code out, don't really think that but i've been proven wrong once or twice:
import pygame, sys
from pygame.locals import *
from pygame.mouse import get_pos
import time
from utils import button, constants, movingtext
from entities import player, entity
import game
class Game():
def __init__(self, map_number):
pygame.init()
self.clock = pygame.time.Clock()
self.screen = pygame.display.set_mode((pygame.display.Info().current_w, pygame.display.Info().current_h), pygame.RESIZABLE)
self.display = pygame.Surface((300,300))
self.font_small = pygame.font.SysFont(None, 20)
self.font_medium = pygame.font.SysFont(None, 32)
self.test_bg = pygame.image.load('images/wp.png')
self.pause = False
self.timer = 0
self.game_called = time.time()
self.flag_mover = False
self.map_number = map_number
f = open('maps/map'+self.map_number+'.txt')
self.map_data = [[int(c) for c in row] for row in f.read().split('\n')]
f.close()
#Tile list -----
self.spawn_img = pygame.image.load('images/spawn.png').convert()
self.spawn_img.set_colorkey((0, 0, 0))
self.goal_img = pygame.image.load('images/goal.png').convert()
self.goal_img.set_colorkey((0, 0, 0))
self.key_img = pygame.image.load('images/key.png').convert()
self.key_img.set_colorkey((0, 0, 0))
self.lava_img = pygame.image.load('images/lava.png').convert()
self.lava_img.set_colorkey((0, 0, 0))
self.grass_img = pygame.image.load('images/grass2.png').convert()
self.grass_img.set_colorkey((0, 0, 0))
#Player
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 1:
self.player = player.Player(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-0.5) * 5), self.map_data)
#goal flag
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 2:
self.goal_flag = entity.Entity(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-1) * 5), 'images/goal_flag.png')
#points
self.point_list = []
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 3:
self.points = entity.Entity(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-0.5) * 5),'images/point.png')
self.point_list.append(self.points)
self.running = True
self.click = False
def drawText(self, text, font, color, surface, x, y):
textobj = font.render(text, 1, color)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
surface.blit(textobj, textrect)
def gameLoop(self):
while self.running:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
if self.pause == False:
self.pause = True
else:
self.pause = False
if self.pause == False:
self.screen.blit(pygame.transform.scale(self.display, self.screen.get_size()), (0, 0))
self.display.fill(0) #clears the scree
self.drawText('game', self.font_small, (255, 255, 255), self.screen, 20, 20)
# self.drawText('time: '+str(int(self.timer/1000)), self.font_small, (255, 255, 255), self.screen, self.player.rect[0], self.player.rect[1])
self.follow_text = movingtext.MovingText('test', 10, (255, 255, 255), self.display, self.player, 10, 5)
# self.follow_text = movingtext.MovingText()
#Draws the map
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 0:
self.display.blit(self.lava_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 1:
self.display.blit(self.spawn_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 2:
self.display.blit(self.goal_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 3:
self.display.blit(self.key_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 4:
self.display.blit(self.grass_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
#collision detection between entities
if self.goal_flag.rect[0] == self.player.rect[0] and self.goal_flag.rect[1] == self.player.rect[1] - 2:
self.flag_mover = True
if self.flag_mover == True:
self.goal_flag.rect[1] += -0.1
#point collision
for point_collision in self.point_list:
if point_collision.rect[0] == self.player.rect[0] and point_collision.rect[1] == self.player.rect[1]:
self.point_list.remove(point_collision)
#update
for points in self.point_list:
points.update()
self.goal_flag.update()
self.player.update()
self.follow_text.update()
else:
self.screen.blit(pygame.transform.scale(self.display, self.screen.get_size()), (0, 0))
self.drawText('game', self.font_small, (255, 255, 255), self.screen, 20, 20)
self.drawText('PAUSED', self.font_medium, (255, 255, 255), self.screen, pygame.display.Info().current_w/2-50, pygame.display.Info().current_h/2)
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 0:
self.display.blit(self.lava_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 1:
self.display.blit(self.spawn_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 2:
self.display.blit(self.goal_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 3:
self.display.blit(self.key_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 4:
self.display.blit(self.grass_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
pygame.display.update()
self.clock.tick(60)
The text is scaled as you draw the text on a surface and scale the surface to the size of your window. You have to draw the text on the display surface directly.

PyCharm can't find 'keysm' in 'event' (Python 3.9 PyCharm 2020.3.3)

I recently tried making a submarine game and made about 132 lines of code. I run the code and pressed the specified keys to move the submarine. Errors came up in the Shell that event doesn't have attribute keysm. Could anyone tell what I am supposed to replace event with or correct keysm?
Full Code:
from tkinter import *
from random import randint
from time import sleep, time
from math import sqrt
HEIGHT = 500
WIDTH = 800
window = Tk()
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 10
GAP = 100
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
SHIP_R = 15
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
SHIP_SPD = 10
BUB_CHANCE = 10
time_text = c.create_text(50, 50, fill='white')
score_text = c.create_text(150, 50, fill='white')
TIME_LIMIT = 30
BONUS_SCORE = 1000
score = 0
bonus = 0
end = time() + TIME_LIMIT
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
def move_ship(event):
if event.keysm == 'Up':
c.move(ship_id, 0, -SHIP_SPD)
c.move(ship_id2, 0, -SHIP_SPD)
elif event.keysm == 'Down':
c.move(ship_id, 0, SHIP_SPD)
c.move(ship_id2, 0, SHIP_SPD)
elif event.keysm == 'Left':
c.move(ship_id2, -SHIP_SPD, 0)
c.move(ship_id2, -SHIP_SPD, 0)
elif event.keysm == 'Right':
c.move(ship_id, SHIP_SPD, 0)
c.move(ship_id2, SHIP_SPD, 0)
c.bind_all('<Key>', move_ship)
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x - r, y - r, x + r, y + r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2 - x1)**2 + (y2 - y1)**2)
def del_bubble(i):
del bub_r[i]
del bub_speed[i]
c.delete(bub_id[i])
del bub_id[i]
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R + bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
def show_score(score):
c.itemconfig(score_text, text=str(score))
def show_time(time_left):
c.itemconfig(time_text, text=str(time_left))
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[i])
if x < -GAP:
del_bubble(i)
c.create_text(50, 30, text='TIME', fill='white')
c.create_text(150, 30, text='SCORE', fill='white')
#MAIN GAME LOOP
while time() < end:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end - time()))
window.update()
sleep(0.01)

Is it possible to undraw a previously drawn shape from a loop? Zelle Graphics Python

I was wondering if it was possible to undraw previously drawn shapes from a loop. I have this function that'll create squares on click however I want to make it so that when the same area is clicked a second time the square will undraw.
from graphics import *
def createGrid():
X = 0
Y = 0
gridSize = 5
for i in range(1, gridSize + 1):
for j in range(1, gridSize + 1):
gridSquare = Rectangle(Point(X, Y), Point(X + 100, Y + 100))
gridSquare.draw(win)
X = X + 100
Y = Y + 100
X = 0
def editMode():
SelectedSquare = []
instruction = input("> ")
if instruction == "s":
selectionMode(SelectedSquare)
def selectionMode(SelectedSquare):
editSquare = Rectangle(Point(0, 0), Point(20, 20))
editSquare.setFill("black")
editSquare.draw(win)
while True:
selection = win.getMouse()
clickXPos = selection.getX()
clickYPos = selection.getY()
if clickXPos > 20 and clickYPos > 20:
PosX, PosY = clickXPos - (clickXPos % 100), clickYPos - (clickYPos % 100)
SelectedSquare = SelectedSquare + [Point(PosX, PosY)]
rect = Rectangle(Point(PosX, PosY), Point(PosX + 100, PosY + 100))
rect.setWidth(5)
rect.draw(win)
else:
editSquare.undraw()
break
win = GraphWin("GRID", 500, 500)
createGrid()
while True:
editMode()
As you can see, on click, there will be a thicker border around the grid square that was selected, I would like to be able to
1) remove the thickened border if clicked a second time
2) be able to remove all thickened borders surrounding grid squares
but I just cannot seem to figure this out, any help would be greatly appreciated!
The general problem seems to be that you have no underlying data structure or logic for your program -- you're drawing the interface first and then trying to make its behaviors define the program.
Below I've patched your code to have the points on the selected squares list remember what rectangle was drawn to highlight them, so if they are selected again, the highlight can be undone and the point removed:
from graphics import *
GRID_SIZE = 5
SQUARE_SIZE = 100
EDIT_BUTTON_SIZE = 20
BORDER_WIDTH = 5
def createGrid():
X, Y = 0, 0
for _ in range(1, GRID_SIZE + 1):
for _ in range(1, GRID_SIZE + 1):
gridSquare = Rectangle(Point(X, Y), Point(X + SQUARE_SIZE, Y + SQUARE_SIZE))
gridSquare.draw(win)
X += SQUARE_SIZE
Y += SQUARE_SIZE
X = 0
def editMode():
selectedSquares = []
instruction = input("> ")
if instruction == "s":
selectionMode(selectedSquares)
def checkSelected(point, squares):
for selected in squares:
if point.getX() == selected.getX() and point.getY() == selected.getY():
return selected
return None
def selectionMode(selectedSquares):
editSquare = Rectangle(Point(0, 0), Point(EDIT_BUTTON_SIZE, EDIT_BUTTON_SIZE))
editSquare.setFill("black")
editSquare.draw(win)
while True:
selection = win.getMouse()
clickXPos = selection.getX()
clickYPos = selection.getY()
if clickXPos <= EDIT_BUTTON_SIZE and clickYPos <= EDIT_BUTTON_SIZE:
break
PosX, PosY = clickXPos - clickXPos % SQUARE_SIZE, clickYPos - clickYPos % SQUARE_SIZE
point = Point(PosX, PosY)
selected = checkSelected(point, selectedSquares)
if selected:
selected.rect.undraw()
selectedSquares.remove(selected)
else:
rect = Rectangle(point, Point(PosX + SQUARE_SIZE, PosY + SQUARE_SIZE))
rect.setWidth(BORDER_WIDTH)
rect.draw(win)
point.rect = rect
selectedSquares.append(point)
editSquare.undraw()
win = GraphWin("GRID", GRID_SIZE * SQUARE_SIZE, GRID_SIZE * SQUARE_SIZE)
createGrid()
while True:
editMode()
But this is only a band-aid -- as you add more functionality the issue of a lack of data structure and spelled out logic will continue to frustrate you.

How to stop the python turtle from drawing

Can anyone tell me why this code always has a line on the screen and also how to stop it?
Slight problem with this is that every time this happens, I always get a line on my canvas no matter what I try to do. Any ideas on how to prevent this?
Now this is probably too much code, but I have no idea how else to show the failing code. I have tried a variety of ways but none seem to work.
def drawpixel(x, y, colour):
turtle.goto(x, y)
turtle.dot(1, colour)
def main():
screen = turtle.Screen()
screen.title('Mandelbrot set drawer')
screen.bgcolor('#22ccaa')
turtle.hideturtle()
turtle.speed(0)
turtle.penup()
turtle.tracer(-10000, 0)
turtle.speed(0)
width = int(turtle.numinput('Screen size', 'What width?', 600, 100, 20000))
height = int(turtle.numinput('Screen size', 'What height?', 600, 100, 10000))
turtle.setworldcoordinates((-width)//2, (-height)//2, (width)//2, (height)//2)
radius = 2
turtle.goto((-width)//2, (-height)//2)
x, y = ((-width)//2, (-height)//2)
while y<((height)//2 +1):
while x!=((width)//2 +1):
newx = x/(width//2)*radius
newy = y/(width//2)*radius
mpos = newx + newy*1j
drawpixel(x, y, getcolour(mpos))
x += 1
y += 1
turtle.goto((-width)//2, y)
Maybe the getcolour is failing so here is the code for it:
def iterc(c):
iters = 0
z = 0+0j
while iters < 16 and math.hypot(z.real, z.imag)<2:
z = z*z+c
iters += 1
return iters
def getcolour(pos):
x = iterc(pos)
colournum = int(x*6.25)
colour = 'gray{}'.format(colournum)
I've reworked your code to get rid of the line and fix other issues that I saw:
def main():
screen = turtle.Screen()
screen.title('Mandelbrot Set')
screen.bgcolor('#22ccaa')
width = int(screen.numinput('Screen size', 'What width?', 600, 100, 20000))
height = int(screen.numinput('Screen size', 'What height?', 600, 100, 10000))
screen.setworldcoordinates(-width // 2, -height // 2, width // 2, height // 2)
screen.tracer(0, 0)
radius = 2
turtle.penup()
turtle.hideturtle()
turtle.speed('fastest')
x, y = -width // 2, -height // 2
turtle.goto(x, y)
while y < (height // 2):
while x < (width // 2):
newx = x / (width // 2) * radius
newy = y / (width // 2) * radius
mpos = newx + newy * 1j
drawpixel(x, y, getcolour(mpos))
x += 1
x, y = -width // 2, y + 1
screen.update()
Despite its title, I don't picture this drawing a Mandelbrot but it should at least scan correctly.
UPDATE
Now that you've provided getcolour(), the Mandelbrot nature of this becomes clear. Below is the complete reworked code and some output:
import math
import turtle
def iterc(c):
iters = 0
z = 0 + 0j
while iters < 16 and math.hypot(z.real, z.imag) < 2:
z = z * z + c
iters += 1
return iters
def getcolour(pos):
x = iterc(pos)
colournum = int(x * 6.25)
return 'gray{}'.format(colournum)
def drawpixel(x, y, colour):
turtle.goto(x, y)
turtle.dot(1, colour)
def main():
screen = turtle.Screen()
screen.title('Mandelbrot Set')
screen.bgcolor('#22ccaa')
width = int(screen.numinput('Screen size', 'What width?', 600, 100, 20000))
height = int(screen.numinput('Screen size', 'What height?', 600, 100, 10000))
screen.setworldcoordinates(-width // 2, -height // 2, width // 2, height // 2)
screen.tracer(0, 0)
radius = 2
turtle.penup()
turtle.hideturtle()
turtle.speed('fastest')
x, y = -width // 2, -height // 2
turtle.goto(x, y)
while y < (height // 2):
while x < (width // 2):
newx = x / (width // 2) * radius
newy = y / (width // 2) * radius
mpos = newx + newy * 1j
drawpixel(x, y, getcolour(mpos))
x += 1
x, y = -width // 2, y + 1
screen.update()
main()
OUTPUT (reduced in size, still not finished running)
Neither the prettiest nor the fastest implementation but it is a Mandelbrot.
Now that you've seen your code works on my system, you need to review your environment (Python version, Windows or Unix, etc.) to see what's different. The above was done with Python 3.6.0 on Max OSX 10.11

Categories

Resources