so there are a lot of problems going on with this. I attempted to make a snake game in python but It was just a mess so now I'm trying to make it where the only direction they can go after the initial right movement is down and they have to get to the coordinates of the food otherwise it starts over. if they 'eat' the food/square then it will make another level with ought starting new game so I can keep score, however the grow definition doesn't seem to be working at all no matter what I put under the if's. Im using python tkinter.
import Tkinter
import random
import math
from Tkinter import *
import time
# Create root window
####
root = Tkinter.Tk()
#####
# Create Model
######
speed_intvar = Tkinter.IntVar()
speed_intvar.set(1) # Initialize y coordinate
# radius and x-coordinate of circle
new_dir = 0
leng = 40
var = 1
x1 = 10
y1 = 10
x2 = 50
y2 = 10
x3 = x2
y3 = 10
direction = 3
foodx1 = 1
foodx2 = 8
foody1 = 1
foody2 = 8
food_present = 0
length = 8
food = 0# radians of angle in standard position, ccw from positive x axis
######
# Create Controller
#######
# Instantiate and place slider
# Create and place directions for the user
#canvas = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
#canvas.grid(row=0, rowspan=2, column=2)
#text = Tkinter.Button(root, text='Use WASD to move', command = new_game)
#text.grid(row=0, column =2)
#text = Tkinter.Label(root, text='Use WASD to move')
#text.grid(row=0, column =2)
######
# Create View
#######
# Create and place a canvas
def new_game():
c.delete("all")
canvas = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
canvas.grid(row=0, rowspan=2, column=1)
# Create a circle on the canvas to match the initial model
circle_item = canvas.create_line(x1, y1, x2, y2, x2, y3, fill='black', width = "8")
#circle_item2 = canvas.create_rectangle(8, 1, 9, 8,
# outline='#000000', fill='black')
#def move(event):
# if event.char=='w'
def food():
global foodx1, foodx2, foody1, foody2, food_present, food, var
food_present = 0
if food_present==0:
foodx1 = random.randint(1, 900)
foody1 = random.randint(1,900)
foodx2 = foodx1 + 8
foody2 = foody1 + 8
food = canvas.create_rectangle(foodx1, foody1, foodx2, foody2, fill = 'black')
food_present += 1
food()
#def make_longer():
# global food_present, food_list, x1, y1, x2, y2, circle_item
# #x1 = canvas.coords(circle_item)
# #y1 = canvas.coords(circle_item)
# #x2 = canvas.coords(circle_item)
# #y2 = canvas.coords(circle_item)
# xx1, yy1, xx2, yy2 = canvas.coords(circle_item)
# xx1 -= 10
# xx2 -= 50
# for i in range(len(food_list)):
# circle_item2=canvas.create_rectangle(xx1, yy1, xx2, yy2, fill ="black")
#make_longer()
def animate():
global x1, y1, x2, y2, x3, y3, var
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
#for i in range(food_present):
if var == 1:
x1 += 1
x2 += 1
#x3+= 1
if var == 4:
y1+=1
y2+=1
y3+=1
if var == 5:
x1-=1
x2-=1
x3-=1
if var == 6:
y1 -= 1
y2 -= 1
y3 -= 1
canvas.coords(circle_item, x1, y1, x2, y2, x2, y3)
#canvas.itemconfig(circle_item, x1, y1, x2, y2, x3, y3, fill="blue")
canvas.update()
#
#while
# Get the slider data and create x- and y-components of velocity
#velocity_x = speed_intvar.get() * math.cos(direction) # adj = hyp*cos()
#velocity_y = speed_intvar.get() * math.sin(direction) # opp = hyp*sin()
## Change the canvas item's coordinates
#canvas.move(circle_item, velocity_x, velocity_y)
#canvas.move(circle_item2, velocity_x, velocity_y)
# Get the new coordinates and act accordingly if ball is at an edge
#global location
## If crossing left or right of canvas
#if x2>canvas.winfo_width():
#
#if x1<0:
# canvas.move(circle_item, x+400, y)
#global direction
canvas.after(1, animate)
# Call function directly to start the recursion
animate()
#
def move(event):
global direction, var, x1, x2, x3, y1, y2, y3, new_dir
direction = 3
if event.char=='s' and direction == 3:
if direction == 1:
return
else:
new_dir = 1
var = 3
for i in range(10):
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
#for i in range(food_present):
if var == 3:
x1 += 1
x2 += 0
x3 +=0
y1 +=0
y2= y1
#x2 += 1
#x3 += 1
y3 += 1
canvas.coords(circle_item, x1, y1, x2, y2, x3, y3)
canvas.update()
if x1 == x3:
var = 4
direction = 1
canvas.update()
# if event.char=='s' and direction == 2:
# if direction == 1:
# return
# else:
# new_dir = 1
# var = 3
# for i in range(10):
# x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
##for i in range(food_present):
# if var == 3:
# x1 -= 0
# x2 -= 1
# x3 -=1
# y1 -=0
# y2 -=1
# #x2 += 1
# #x3 += 1
#
# y3 -= 1
# canvas.coords(circle_item, x1, y1, x2, y2, x3, y3)
# canvas.update()
# if x1 == x3:
# var = 4
# direction = 1
# canvas.update()
root.bind("<Key>", move)
def x():
i = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
x = Tkinter.Button(root, text='Use S to move down. Try and eat the food.', command = new_game)
x.grid(row=0, column =1)
def grow():
global x1, y1, x2, y2, x3, y3, var, food_present, foodx1, foody1, foodx2, foody2
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
if x2>canvas.winfo_width() or x1<0:
x()
if y2>canvas.winfo_height() or y3>canvas.winfo_height():
food_present = 0
x()
canvas.update()
grow()
c = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
c.grid(row=0, rowspan=2, column=1)
text = Tkinter.Button(root, text='Use S to move down. Try and eat the food.', command = new_game)
text.grid(row=0, column =1)
root.mainloop()
If you want to check if two objects are overlapping on a Tkinter canvas, you may get their positions by the method of object Canvas .coords(objectname). You use the .coords method to check on a drawn object.
Example:
circle = canvas.create_oval(0, 0, 50, 50)
canvas.move(circle, 5, 0) # moves the circle by 5 pixels to the right
coords = canvas.coords(circle) # Returns an array (5, 0, 55, 0).
What you don't do is specify a coordinate to return what object is on it(what I think you are doing).
Related
I have a canvas with a little oval in it. It moves throughout the widget using the arrow keys but when it's on the edge of the canvas if I move it beyond that, the oval just disappears.
I want the oval stays on any edge of the canvas no matter if I continue pressing the arrow key corresponding to that edge without disappearing.
This is the code:
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w//2
y = h//2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x+20, y+20, fill='cyan')
def left(event):
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x = 0
y = 10
my_canvas.move(my_circle, x, y)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
This is what it looks like:
The oval on an edge
And if I continue pressing the key looks like this:
The oval disappearing
You could test the current coordinates and compare them to your canvas size.
I created a function to get the current x1, y1, x2, y2 from your oval. This way you have the coordiantes of the borders of your oval.
So all I do is testing if the oval is touching a border.
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w // 2
y = h // 2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x + 20, y + 20, fill='cyan')
def left(event):
x1, y1, x2, y2 = get_canvas_position()
if x1 > 0:
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x1, y1, x2, y2 = get_canvas_position()
if x2 < w:
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x1, y1, x2, y2 = get_canvas_position()
if y1 > 0:
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x1, y1, x2, y2 = get_canvas_position()
if y2 < h:
x = 0
y = 10
my_canvas.move(my_circle, x, y)
def get_canvas_position():
return my_canvas.coords(my_circle)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
The canvas object is stored via 2 sets of coordinates [x1, y1, x2, y2]. You should check against the objects current location by using the .coords() method. The dimensions of the canvas object will affect the coordinates.
def left(event):
x = -10
y = 0
if my_canvas.coords(my_circle)[0] > 0: # index 0 is X coord left side object.
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
# The border collision now happens at 600 as per var "w" as previously defined above.
if my_canvas.coords(my_circle)[2] < w: # index 2 is X coord right side object.
my_canvas.move(my_circle, x, y)
Now repeat a similar process for up and down.
import time
from tkinter import *
class Template:
def __init__(self):
self.window = Tk()
self.window.title("2D Display")
self.canvas = self.canvas_display()
self.line1 = self.line_creation(650,350,500 * .3, 1000)
self.line3 = self.line_movement_creation(0, 350,2000, 350)
self.horizon = self.canvas.create_line(0,350,2000, 350, width = 2, fill ="white")
self.speedx = 0 # x movement of line3
self.speedy = 9 # y movement of line3
self.active = True
self.pos1 = []
self.move_active() #Code that creates the problem
self.canvas.update()
def canvas_display(self): #canvas
canvas = Canvas(self.window, width=500, height=400, background='black')
canvas.pack(expand=True, fill="both")
canvas.update()
return canvas
Upwards is Initialization
def line_creation(self,x,y,x1,y1): #creation of multple lines
spacing = 0
lines = [] # could list([])
for i in range(11):
id = self.canvas.create_line( x, y, x1 + spacing, y1, width=2, fill="white")
lines.append(id)
spacing += 100
pos1 = self.canvas.coords(id)
self.pos1 = pos1
print(self.pos1)
return lines
This is the creation method for the vertices lines
def line_movement_creation(self,x,y,x1,y1):
spacing1 = 0
lines = []
for i in range(4):
id = self.canvas.create_line(x , y+spacing1, x1, y1 + spacing1, width=2, fill="white")
lines.append(id)
spacing1 += 100
#line = [] equal all horizontal and vertical, 12 - 15 equal horizontal moving lines
return lines
The is the creation for the horizontal lines
def line_update(self): #line movement method
for line in self.line3:
self.canvas.move(line, self.speedx, self.speedy)
#Create variables for all x and y values for the lines
pos = self.canvas.coords(line)
print(pos)
if pos[3] >= 800:
self.canvas.move(line, self.speedx, self.speedy - 460)
def move_active(self):
if self.active:
self.line_update()
self.window.after(40, self.move_active)
This is what moves the lines creating an illusion of movement. I want to take the list of horizontal lines and set them between the outer most vertical lines. So it would stay between the vertical lines. Creating a road like image. I think I need to make a separate list for both but I am not sure. So to clarify I can someone help show me how to make the horizontal lines not attach to the ends of the screen but to inside the horizontal lines Code Demonstration
def run(self):
self.window.mainloop()
if __name__ == '__main__':
Temp = Template()
Temp.run()
So, first I would suggest that you use some game engine like pygame because it is faster and provides a bit more options and other stuff but here it is with tkinter (basically it is some simple trigonometry):
import tkinter as tk
import math
class Canvas(tk.Canvas):
def __init__(self, parent, width=700, height=500, **kwargs):
super().__init__(parent, width=width, height=height, **kwargs)
self.width = width
self.height = height
self.angle = 70
self.speedy = 10
self.change_speed_ms = 50
self.draw_angle(self.angle, 5)
self.lines, self.interval = self.init_lines(amount=5)
self.draw_lines()
#property
def radians(self):
return math.radians(self.angle)
def draw_angle(self, view_angle, lines):
orient = 0
adjacent = self.height // 2
step = view_angle // lines
half = view_angle // 2
for angle in range(orient - half, orient + half + step, step):
rad = math.radians(angle)
delta = math.tan(rad) * adjacent
x1, y1 = self.width // 2, self.height // 2
x2, y2 = x1 + delta, y1 + adjacent
self.create_line(x1, y1, x2, y2)
def init_lines(self, amount=5):
interval = round((self.height // 2) / amount)
coordinate_list = list()
offset = self.height // 2
for y in range(0, self.height // 2 + interval, interval):
delta = math.tan(self.radians / 2) * y
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
y1 = y2 = y + offset
line = self.create_line(x1, y1, x2, y2)
coordinate_list.append((line, (x1, y1, x2, y2)))
return coordinate_list, interval
def draw_lines(self):
tmp_lst = list()
for id_, (x1, y1, x2, y2) in self.lines:
y1 += self.speedy
if y1 > self.height + self.interval - self.speedy:
y1 = self.height // 2
y = y2 = y1
adjacent = y - self.height // 2
delta = math.tan(self.radians / 2) * adjacent
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
self.coords(id_, x1, y1, x2, y2)
tmp_lst.append((id_, (x1, y1, x2, y2)))
self.lines = tmp_lst
self.after(self.change_speed_ms, self.draw_lines)
root = tk.Tk()
Canvas(root, highlightthickness=0).pack()
root.mainloop()
So the main method here is Canvas().draw_lines(). First of you get a list of line IDs and their coordinates at set intervals based on the amount of total lines, then you iterate over them, change their y value and accordingly calculate the opposite side of a right-angle triangle using tan and the adjacent side which is known from the current y coordinate and the starting point (the middle).
I wanted to get a little square to move to the left 10 pixels every 3 seconds and my code is below. I'm not sure why it only moves once. Some help would be greatly apprecaited!
import tkinter as tk
import time
root = tk.Tk()
WIDTH = HEIGHT = 400
x1 = y1 = WIDTH / 2
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
canvas.pack()
c1 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10)
c2 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10)
def draw_rect():
global c2
canvas.delete(c2)
c2 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="green")
def del_rect():
canvas.delete(c1)
#canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="white", opacity=0.5)
while 1:
root.update()
time.sleep(3)
del_rect()
x1 -= 10
draw_rect()
root.mainloop()
mainloop() runs loop which works until you close window - so it blocks all loop and it is executed only once and it move rectangle only once. In your version you should remove mainloop() and it will not block it - and because you use update() in loop so it will run correctly.
But you could do this in different way.
You can use root.after(3000, draw_rect) to execute draw_rect() after 3 seconds. And draw_rect() should run again root.after(3000, draw_rect) to run it again after another 3 seconds - this way it will loop and it will not blocked by mainloop() (mainloop() will run draw_rect() every 3 seconds)
Canvas has function move(object, dx, dy) so you don't have to delete rectangle and create it again.
I use 300ms to run it faster.
import tkinter as tk
# --- constants --- (PEP8: UPPER_CASE_NAMES)
WIDTH = 400
HEIGHT = 400
# --- functions ---
def move_rect():
canvas.move(c1, -10, 0) # move left
canvas.move(c2, 10, 0) # move right
# run again after 300ms
root.after(300, move_rect) # 300ms = 0.3s # I use smaller value to make it faster
# --- main ---
x1 = y1 = WIDTH / 2
root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
canvas.pack()
c1 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="green")
c2 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="red")
# run first time after 300ms
root.after(300, move_rect) # 300ms = 0.3s # I use smaller value to make it faster
root.mainloop()
PEP 8 -- Style Guide for Python Code
EDIT:
Version which uses variables speed1, speed2 to change direction when rectangle is near border.
import tkinter as tk
# --- constants --- (PEP8: UPPER_CASE_NAMES)
WIDTH = 400
HEIGHT = 400
# --- functions ---
def move_rect():
global speed1
global speed2
#x1, y1, x2, y2 = canvas.coords(c1)
pos = canvas.coords(c1)
if speed1 < 0 :
if pos[0] < 10:
speed1 = -speed1
else:
if pos[2] > WIDTH-10:
speed1 = -speed1
#x1, y1, x2, y2 = canvas.coords(c2)
pos = canvas.coords(c2)
if speed2 < 0:
if pos[0] < 10:
speed2 = -speed2
else:
if pos[2] > WIDTH-10:
speed2 = -speed2
canvas.move(c1, speed1, 0) # move left
canvas.move(c2, speed2, 0) # move right
# run again after 30ms
root.after(30, move_rect) # 30ms = 0.03s - I uses smaller value to make it faster
# --- main ---
x1 = y1 = WIDTH / 2
speed1 = -10
speed2 = +10
root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
canvas.pack()
print(dir(canvas))
c1 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="green")
c2 = canvas.create_rectangle(x1, y1, x1 + 10, y1 + 10, fill="red")
# run first time after 300ms
root.after(300, move_rect) # 300ms = 0.3s - I uses smaller value to make it faster
root.mainloop()
from tkinter import *
import random
tk = Tk()
canvas = Canvas(tk, width=400, height=400)
canvas.pack()
for x in range(0, 40):
x1 = random.randint(0,400)
y1 = random.randint(0,400)
x2 = random.randint(0,400)
y2 = random.randint(0,400)
x3 = random.randint(0,400)
y3 = random.randint(0,400)
my_triangle = canvas.create_polygon(x1, y1, x2, y2, x3, y3,\
fill =("blue"), outline="red")
Hi! I am playing with tkinter and generating random triangles. The problem is:
I want to use random on fill = " " to generate random colors
random.randint(start, end) returns numbers, but fill accepts only strings like
fill ="red" or hexadecimal = "#RGB"
if I enter a numeric valuer like fill = (1,1,0) it doesn't work. How could I generate random string values in fill?
Thank you
Simply use random.sample() from a known list of colors. And you can find the python tkinter color chart enumerated here. You can then randomize for both fill and outline values:
COLORS = ['snow', 'ghost white', 'white smoke', 'gainsboro', 'floral white', 'old lace' ...]
for x in range(0, 40):
x1 = random.randint(0,400)
y1 = random.randint(0,400)
x2 = random.randint(0,400)
y2 = random.randint(0,400)
x3 = random.randint(0,400)
y3 = random.randint(0,400)
my_triangle = canvas.create_polygon(x1, y1, x2, y2, x3, y3,\
fill = (random.sample(COLORS, 1)[0]),
outline = random.sample(COLORS, 1)[0])
Of course, always seed if you want to reproduce same exact random generated numbers:
random.seed(444) # WHERE 444 IS ANY INTEGER
In alternative just generate your random color and format it:
from tkinter import *
import random
def random_color():
return random.randint(0,0x1000000)
tk = Tk()
canvas = Canvas(tk, width=400, height=400)
canvas.pack()
for x in range(0, 40):
color = '{:06x}'.format(random_color())
x1 = random.randint(0,400)
y1 = random.randint(0,400)
x2 = random.randint(0,400)
y2 = random.randint(0,400)
x3 = random.randint(0,400)
y3 = random.randint(0,400)
my_triangle = canvas.create_polygon(x1, y1, x2, y2, x3, y3,\
fill =('#'+ color), outline="red")
tk.mainloop()
So, I'm trying to make a ball move smoothly in Tkinter, but the key repetition is messing it up (i.e. making it too fast by calling on it too much). Is there a way to disable Tkinter or in Python in general?
This is my code:
import Tkinter
import os
root = Tkinter.Tk()
r = 10
x = 150
y = 150
canvas = Tkinter.Canvas(root, width=600, height=600, background='#FFFFFF')
canvas.grid(row=0, rowspan=2, column=1)
circle_item = canvas.create_oval(x-r, y-r, x+r, y+r,
outline='#000000', fill='#00FFFF')
global leftInt
global upInt
global downInt
global rightInt
leftInt = 0
upInt= 0
downInt = 0
rightInt = 0
def leftMove(Event):
global leftInt
leftInt = 1
ballMove()
def leftStop(Event):
global leftInt
global upInt
global downInt
global rightInt
leftInt = 0
upInt = 0
downInt = 0
rightInt = 0
print("im stop")
def rightMove(Event):
global rightInt
rightInt = 1
gogo = 1
if (gogo == 1):
ballMove()
gogo = 2
def upMove(Event):
global upInt
upInt = 1
gogo = 1
if (gogo == 1):
ballMove()
gogo = 2
def downMove(Event):
global downInt
downInt = 1
gogo = 1
if (gogo == 1):
ballMove()
gogo = 2
def ballMove():
global leftInt
global upInt
global downInt
global rightInt
if (rightInt == 1):
x1, y1, x2, y2 = canvas.coords(circle_item)
print('im go', x1)
if x1 < 597: ## keep it on the canvas
canvas.move(circle_item, 1, 0)
root.after(25, ballMove)
else:
canvas.move(circle_item, -1, 0)
if (upInt == 1):
x1, y1, x2, y2 = canvas.coords(circle_item)
print('im go', x1)
if y1 > 3: ## keep it on the canvas
canvas.move(circle_item, 0, -1)
root.after(25, ballMove)
else:
canvas.move(circle_item, 0, 1)
if (downInt == 1):
x1, y1, x2, y2 = canvas.coords(circle_item)
print('im go', x1)
if y1 < 597: ## keep it on the canvas
canvas.move(circle_item, 0, 1)
root.after(25, ballMove)
else:
canvas.move(circle_item, 0, -1)
if (leftInt == 1):
x1, y1, x2, y2 = canvas.coords(circle_item)
print('im go', x1)
if x1 > 3: ## keep it on the canvas
canvas.move(circle_item, -1, 0)
root.after(25, ballMove)
else:
canvas.move(circle_item, 1, 0)
ballMove()
root.bind('<Left>',leftMove)
root.bind('<KeyRelease>',leftStop)
root.bind('<Right>',rightMove)
root.bind('<Up>',upMove)
root.bind('<Down>',downMove)
root.mainloop()
Use Tkinter's after() to pause the infinite loop for a short period of time. A shorter example.
from Tkinter import *
class BounceTest():
def __init__(self):
self.running=True
self.window = Tk()
canvas = Canvas(self.window, width = 400, height = 300)
canvas.grid()
x0 = 10
y0 = 50
x1 = 60
y1 = 100
i = 0
deltax = 2
deltay = 3
which = canvas.create_oval(x0,y0,x1,y1,fill="red", tag='red_ball')
Button(text="Stop", bg='yellow', command=self.stop_it).grid(row=1)
while self.running:
canvas.move('red_ball', deltax, deltay)
canvas.after(20) ## give it time to draw everything
canvas.update()
if x1 >= 400:
deltax = -2
if x0 < 0:
deltax = 2
if y1 > 290:
deltay = -3
if y0 < 0:
deltay = 3
x0 += deltax
x1 += deltax
y0 += deltay
y1 += deltay
self.window.mainloop()
def stop_it(self):
self.running=False
BounceTest()