Snake game in Python using Turtle graphics - python
So I've been working on a few games in Python (battleships, tic-tac-toe etc.) and this week's project is Snake. I've got a basic set-up going; the snake can move and eats the food but I haven't programmed in collision detection or going off the edge yet. The problem is response time. If you run the code below, you'll see that the snake responds to key presses, but not for a couple of - I'll call them frames - after the press. I don't quite understand how the listen() method works; am I using it properly? If not, how should I use it, and if so, how can I fix the delay? I know about Pygame, but a) I can't find an easy to install 64 bit version for python 3.4 (this one http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame is not easy to install, what the heck is a whl file?) and b) I want to challenge myself anyway.
Any help would be appreciated.
import random
import turtle
import time
class Square:
def __init__(self, x, y):
self.x = x
self.y = y
def drawself(self, turtle):
# draw a black box at its coordinates, leaving a small gap between cubes
turtle.goto(self.x - 9, self.y - 9)
turtle.begin_fill()
for i in range(4):
turtle.forward(18)
turtle.left(90)
turtle.end_fill()
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.state = "ON"
def changelocation(self):
# I haven't programmed it to spawn outside the snake's body yet
self.x = random.randint(0, 20)*20 - 200
self.y = random.randint(0, 20)*20 - 200
def drawself(self, turtle):
# similar to the Square drawself, but blinks on and off
if self.state == "ON":
turtle.goto(self.x - 9, self.y - 9)
turtle.begin_fill()
for i in range(4):
turtle.forward(18)
turtle.left(90)
turtle.end_fill()
def changestate(self):
# controls the blinking
self.state = "OFF" if self.state == "ON" else "ON"
class Snake:
def __init__(self):
self.headposition = [20, 0] # keeps track of where it needs to go next
self.body = [Square(-20, 0), Square(0, 0), Square(20, 0)] # body is a list of squares
self.nextX = 1 # tells the snake which way it's going next
self.nextY = 0
self.crashed = False # I'll use this when I get around to collision detection
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
# prepares the next location to add to the snake
def moveOneStep(self):
if Square(self.nextposition[0], self.nextposition[1]) not in self.body:
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
# moves the snake head to the next spot, deleting the tail
del self.body[0]
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
# resets the head and nextposition
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
else:
self.crashed = True # more unsuccessful collision detection
def moveup(self): # pretty obvious what these do
self.nextX = 0
self.nextY = 1
def moveleft(self):
self.nextX = -1
self.nextY = 0
def moveright(self):
self.nextX = 1
self.nextY = 0
def movedown(self):
self.nextX = 0
self.nextY = -1
def eatFood(self):
# adds the next spot without deleting the tail, extending the snake by 1
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
self.nextposition = [self.headposition[0] + 20*self.nextX,
self.headposition[1] + 20*self.nextY]
def drawself(self, turtle): # draws the whole snake when called
for segment in self.body:
segment.drawself(turtle)
class Game:
def __init__(self):
# game object has a screen, a turtle, a basic snake and a food
self.screen = turtle.Screen()
self.artist = turtle.Turtle()
self.artist.up()
self.artist.hideturtle()
self.snake = Snake()
self.food = Food(100, 0)
self.counter = 0 # this will be used later
self.commandpending = False # as will this
def nextFrame(self):
while True: # now here's where it gets fiddly...
game.screen.listen()
game.screen.onkey(game.snakedown, "Down")
game.screen.onkey(game.snakeup, "Up")
game.screen.onkey(game.snakeleft, "Left")
game.screen.onkey(game.snakeright, "Right")
turtle.tracer(0) # follow it so far?
self.artist.clear()
if self.counter == 5:
# only moves to next frame every 5 loops, this was an attempt to get rid of the turning delay
if (self.snake.nextposition[0], self.snake.nextposition[1]) == (self.food.x, self.food.y):
self.snake.eatFood()
self.food.changelocation()
else:
self.snake.moveOneStep()
self.counter = 0
else:
self.counter += 1
self.food.changestate() # makes the food flash
self.food.drawself(self.artist) # show the food and snake
self.snake.drawself(self.artist)
turtle.update()
self.commandpending = False
time.sleep(0.05)
def snakeup(self):
print("going up") # put this in for debugging purposes
if not self.commandpending:
# should allow only one turn each frame; I don't think it's working
self.snake.moveup()
self.commandpending = True
def snakedown(self):
print("going down")
if not self.commandpending:
self.snake.movedown()
self.commandpending = True
def snakeleft(self):
print("going left")
if not self.commandpending:
self.snake.moveleft()
self.commandpending = True
def snakeright(self):
print("going right")
if not self.commandpending:
self.snake.moveright()
self.commandpending = True
game = Game()
game.nextFrame()
print("game over!")
game.screen.mainloop()
Whenever you use while True: (sans break) in turtle code, you're defeating the event hander. You should instead use an ontimer() event to run your code compatibly with the event handler. Below is my rewrite of your code to do this along with some other functional and style tweaks:
from turtle import Turtle, Screen
import random
import time
SIZE = 20
class Square:
def __init__(self, x, y):
self.x = x
self.y = y
def drawself(self, turtle):
""" draw a black box at its coordinates, leaving a small gap between cubes """
turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
turtle.begin_fill()
for _ in range(4):
turtle.forward(SIZE - SIZE // 10)
turtle.left(90)
turtle.end_fill()
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.is_blinking = True
def changelocation(self):
# I haven't programmed it to spawn outside the snake's body yet
self.x = random.randint(0, SIZE) * SIZE - 200
self.y = random.randint(0, SIZE) * SIZE - 200
def drawself(self, turtle):
# similar to the Square drawself, but blinks on and off
if self.is_blinking:
turtle.goto(self.x - SIZE // 2 - 1, self.y - SIZE // 2 - 1)
turtle.begin_fill()
for _ in range(4):
turtle.forward(SIZE - SIZE // 10)
turtle.left(90)
turtle.end_fill()
def changestate(self):
# controls the blinking
self.is_blinking = not self.is_blinking
class Snake:
def __init__(self):
self.headposition = [SIZE, 0] # keeps track of where it needs to go next
self.body = [Square(-SIZE, 0), Square(0, 0), Square(SIZE, 0)] # body is a list of squares
self.nextX = 1 # tells the snake which way it's going next
self.nextY = 0
self.crashed = False # I'll use this when I get around to collision detection
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
# prepares the next location to add to the snake
def moveOneStep(self):
if Square(self.nextposition[0], self.nextposition[1]) not in self.body:
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
# moves the snake head to the next spot, deleting the tail
del self.body[0]
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
# resets the head and nextposition
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
else:
self.crashed = True # more unsuccessful collision detection
def moveup(self): # pretty obvious what these do
self.nextX, self.nextY = 0, 1
def moveleft(self):
self.nextX, self.nextY = -1, 0
def moveright(self):
self.nextX, self.nextY = 1, 0
def movedown(self):
self.nextX, self.nextY = 0, -1
def eatFood(self):
# adds the next spot without deleting the tail, extending the snake by 1
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
self.headposition[0], self.headposition[1] = self.body[-1].x, self.body[-1].y
self.nextposition = [self.headposition[0] + SIZE * self.nextX, self.headposition[1] + SIZE * self.nextY]
def drawself(self, turtle): # draws the whole snake when called
for segment in self.body:
segment.drawself(turtle)
class Game:
def __init__(self):
# game object has a screen, a turtle, a basic snake and a food
self.screen = Screen()
self.artist = Turtle(visible=False)
self.artist.up()
self.artist.speed("slowest")
self.snake = Snake()
self.food = Food(100, 0)
self.counter = 0 # this will be used later
self.commandpending = False # as will this
self.screen.tracer(0) # follow it so far?
self.screen.listen()
self.screen.onkey(self.snakedown, "Down")
self.screen.onkey(self.snakeup, "Up")
self.screen.onkey(self.snakeleft, "Left")
self.screen.onkey(self.snakeright, "Right")
def nextFrame(self):
self.artist.clear()
if (self.snake.nextposition[0], self.snake.nextposition[1]) == (self.food.x, self.food.y):
self.snake.eatFood()
self.food.changelocation()
else:
self.snake.moveOneStep()
if self.counter == 10:
self.food.changestate() # makes the food flash slowly
self.counter = 0
else:
self.counter += 1
self.food.drawself(self.artist) # show the food and snake
self.snake.drawself(self.artist)
self.screen.update()
self.screen.ontimer(lambda: self.nextFrame(), 100)
def snakeup(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveup()
self.commandpending = False
def snakedown(self):
if not self.commandpending:
self.commandpending = True
self.snake.movedown()
self.commandpending = False
def snakeleft(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveleft()
self.commandpending = False
def snakeright(self):
if not self.commandpending:
self.commandpending = True
self.snake.moveright()
self.commandpending = False
game = Game()
screen = Screen()
screen.ontimer(lambda: game.nextFrame(), 100)
screen.mainloop()
Does this provide the kind of response for which you're looking?
It probably is a challenge to make a speedy game this way (but that's not all bad). I think that listen() should go after the onkey()s.
You should also look at getting rid of all the duplicated code. It might seem easy short term to just copy/paste then alter. But if you have to make major changes (like after asking on a forum) it will be tedious and error prone to alter.
PS (EDIT) also your Snake.moveOneStep() method makes a new instance of Square just to check for self collision, this seems extravagant for the sake of elegance. Better to just keep a list of locations that python (ho, ho) can check through. (Quite apart from this probably not working. Try print(Square(1,2) in [Square(1,2)]))
def check_self_collision(self, x, y):
for s in self.body:
if s.x == x and s.y == y:
return False
return True
def moveOneStep(self):
if self.check_self_collision(self.nextposition[0], self.nextposition[1]):
# attempt (unsuccessful) at collision detection
self.body.append(Square(self.nextposition[0], self.nextposition[1]))
My version:
#coding: utf-8
from Tkinter import *
import random
import time
class Levely:
def __init__(self):
self.urovne=[
[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 1, 0, 0, 0], [1, 1, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 1, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 1, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0, 1, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 0, 0, 0, 0, 0, 0, 1], [0, 0, 1, 0, 0, 0, 0, 0, 1, 1], [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [0, 0, 0, 1, 0, 0, 1, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 0]],
]
self.data=[[400,13],[400,10],[400,13],[400,13],[400,13],[400,13]]
print "Choose from", len(self.urovne), "levels"
self.vyber=input("Level: ")
self.vyber-=1
h=Had(self)
class Had:
def __init__(self,Levely):
self.l=Levely
self.level=self.l.urovne[self.l.vyber]
self.mrizka=len(self.level[0])
self.velikost=self.l.data[self.l.vyber][0]
self.vtelo=100
self.r=self.l.data[self.l.vyber][1]
self.x=0
self.y=0
self.u=0
self.k=self.velikost
self.c=(self.velikost/self.mrizka)
self.poprve=0
self.neco=[[0,0],0,0,0]
self.ukonceni=None
self.aakce1=None
self.aakce2=None
self.aakce3=None
self.aakce4=None
self.s=[0,0,0,0]
self.j=[]
self.konec=0
self.score=0
self.pocet_zelenych=0
self.okno=Tk()
self.platno=Canvas(self.okno,width=self.velikost,height=self.velikost,bg="white")
self.platno.pack()
self.tl=Button(self.okno, text="Restart", command=self.start)
self.tl.pack(fill=BOTH)
self.start()
self.okno.bind("<Key-d>", self.akce1)
self.okno.bind("<Key-w>", self.akce2)
self.okno.bind("<Key-s>", self.akce3)
self.okno.bind("<Key-a>", self.akce4)
self.okno.bind("<Key-r>", self.start1)
def akce1(self, klik):
self.akce11()
def akce2(self, klik):
self.akce21()
def akce3(self, klik):
self.akce31()
def akce4(self, klik):
self.akce41()
def start1(self, klik):
self.start()
def akce11(self):
if int(self.s[1])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce11)
if int(self.s[1])%self.c==0:
self.x=self.c
self.y=0
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce1)
self.stop()
self.pohyb()
def akce21(self):
if int(self.s[0])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce21)
if int(self.s[0])%self.c==0:
self.x=0
self.y=-self.c
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce2)
self.stop()
self.pohyb()
def akce31(self):
if int(self.s[0])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce31)
if int(self.s[0])%self.c==0:
self.x=0
self.y=self.c
self.u=1
if self.poprve==1:
self.okno.after_cancel(self.aakce3)
self.stop()
self.pohyb()
def akce41(self):
if int(self.s[1])%self.c!=0:
self.aakce1=self.okno.after(9,self.akce41)
if int(self.s[1])%self.c==0:
self.x=-self.c
self.y=0
self.u=0
if self.poprve==1:
self.okno.after_cancel(self.aakce4)
self.stop()
self.pohyb()
def pohyb(self):
self.smrt()
if self.konec==1:
return None
self.test()
s=self.platno.coords(self.hlava)
self.s=self.platno.coords(self.hlava)
self.platno.delete(ALL)
self.hlava=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="white")
self.jablko=self.platno.create_rectangle(self.j[0],self.j[1],self.j[2],self.j[3], fill="red", outline="red")
for x in range(self.mrizka):
for y in range(self.mrizka):
if self.level[x][y]==0:
continue
if self.level[x][y]==1:
#KURVVAAAAA x,y,x,y
self.block=self.platno.create_rectangle(y*self.c,(x*self.c),(y*self.c)+self.c,(x*self.c)+self.c, fill="black")
self.test()
s=self.platno.coords(self.hlava)
self.poloha.append(s)
self.delka=len(self.poloha)
if s[self.u]<=self.k:
self.dx=self.x
self.dy=self.y
self.platno.move(self.hlava,self.dx/10,self.dy/10)
s=self.platno.coords(self.hlava)
self.nahrada=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="green4")
if s[self.u]>=self.k:
self.dx=0
self.dy=0
bla="Restart-Score:", int(self.score)
self.tl.config(text=bla)
for a in range(self.delka):
if 1==1:
self.ocas=self.platno.create_rectangle(self.poloha[a][0],self.poloha[a][1],self.poloha[a][2],self.poloha[a][3], fill="green2", outline="green2")
self.poloha_zeleny=self.platno.coords(self.ocas)
self.zeleny.append(self.poloha_zeleny)
self.pocet_zelenych=len(self.zeleny)
if self.pocet_zelenych>=self.delka:
del self.zeleny[0]
if self.delka>=self.vtelo:
self.neco=self.poloha[0]
del self.poloha[0]
self.s=self.platno.coords(self.hlava)
self.nahrada=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="green4", outline="green4")
self.ukonceni=self.okno.after(self.r,self.pohyb)
def smrt(self):
s=self.platno.coords(self.hlava)
bla="Restart-Score:", int(self.score)
if self.level[int(s[1]/self.c)][int(s[0]/self.c)]==1:
self.platno.delete(self.hlava)
self.tl.config(text=bla)
self.konec=1
self.smrtak=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="brown", outline="brown")
for b in range(len(self.zeleny)):
if s==self.zeleny[(b-1)]:
self.platno.delete(self.hlava)
self.tl.config(text=bla)
self.konec=1
self.smrtak=self.platno.create_rectangle(s[0],s[1],s[2],s[3], fill="brown", outline="brown")
def stop(self):
if self.poprve==1:
self.okno.after_cancel(self.ukonceni)
self.poprve=1
def start(self):
self.vtelo=60
self.platno.delete("all")
self.tl.config(text="Restart")
self.poloha=[]
self.zeleny=[]
self.konec=0
self.pocet_zelenych=0
self.score=0
self.poprve=0
self.dx=0
self.dy=0
if self.aakce1!=None:
self.okno.after_cancel(self.aakce1)
self.aakce1=None
if self.aakce2!=None:
self.okno.after_cancel(self.aakce2)
self.aakce2=None
if self.aakce3!=None:
self.okno.after_cancel(self.aakce3)
self.aakce3=None
if self.aakce4!=None:
self.okno.after_cancel(self.aakce4)
self.aakce4=None
for x in range(self.mrizka):
for y in range(self.mrizka):
if self.level[x][y]==0:
continue
if self.level[x][y]==1:
#KURVVAAAAA x,y,x,y
self.block=self.platno.create_rectangle(y*self.c,(x*self.c),(y*self.c)+self.c,(x*self.c)+self.c, fill="black")
self.hlava=self.platno.create_rectangle(0,0,self.c,self.c, fill="green4", outline="green4")
self.generace()
s=self.platno.coords(self.hlava)
self.dx=self.c
self.dy=self.c
def generace(self):
self.nx=random.randint(0,self.mrizka-1)
self.ny=random.randint(0,self.mrizka-1)
for x in self.zeleny:
if int(x[0]/self.c)==self.nx and int(x[1]/self.c)==self.ny:
self.generace()
if self.level[self.ny][self.nx]==1:
self.generace()
if self.level[self.ny][self.nx]!=1:
self.jablko=self.platno.create_rectangle(self.nx*self.c,self.ny*self.c,self.nx*self.c+self.c,self.ny*self.c+self.c, fill="red", outline="red")
def test(self):
s=self.platno.coords(self.hlava)
self.j=self.platno.coords(self.jablko)
if s==self.j:
self.vtelo+=5
self.score+=0.5
self.generace()
def mezery(self):
for x in range(30):
print ""
levliky=Levely()
mainloop()
Related
Problems about projection matrix and why the picture it shows somtimes deform
I am currently working on 3d rendering, and I am trying to accomplish it without any addition library(Except for pygame, math, sys).I have done many research but still cannot quite understand the math(I am using Methods on wikipedia). It did output the right coordinate, but it somtimes has an severely deform. Here is what the result look like. I don't quite understand the matrix so it's very hard for me to debug. I can really use some help on why is it deforming or simply just how the matrix works, thanks a lot! Here's my code: import pygame import math import sys width = 600 height = 480 class Triangle: def __init__(self, verts): for i in range(len(verts)): for r in range(3): verts[i][r] = int(float(verts[i][r])) self.verts = verts The class Triangle is to make the code more easy to read def getobj(filename): verts = [] ind = [] triangles = [] data = None with open(filename, 'r') as f: data = f.readline() while data: data = data.rstrip("\n").split(" ") if data[0] == 'v': verts.append([-1 * int(float(data[1])), -1 * int(float(data[2])), -1 * int(float(data[3]))]) elif data[0] == 'f': v = [] t = [] n = [] for i in range(3): l = data[i + 1].split('/') v.append(int(l[0])) t.append(int(l[1])) n.append(int(l[2])) ind.append([v, t, n]) data = f.readline() for points in ind: v = [] for i in points[0]: v.append(verts[i - 1]) triangles.append(Triangle(v)) return triangles Get vertex and lines from obj file class Matrix: def __init__(self, matrix): self.matrix = matrix self.height = len(matrix) self.width = len(matrix[0]) def __add__(self, other): result = [] for h in range(self.height): row = [] for w in range(self.width): row.append(self.matrix[h][w] + other.matrix[h][w]) result.append(row) return Matrix(result) def __sub__(self, other): result = [] for h in range(self.height): row = [] for w in range(self.width): row.append(self.matrix[h][w] - other.matrix[h][w]) result.append(row) return Matrix(result) def __mul__(self, other): result = [] for h in range(self.height): SUM = 0 for w in range(self.width): SUM += self.matrix[h][w] * other.matrix[0][w] result.append(SUM) return Matrix([result]) The class Matrix is to make the addition, subtraction and multiplication of matrixes more easy class Cam: def __init__(self): self.pos = Matrix([[0, 0, 0]]) self.rot = [0, 0, 0] def getcoord(self, coord): m = Matrix([coord]) data = m - self.pos F = Matrix([ [math.cos(self.rot[2]), math.sin(self.rot[2]), 0], [-1 * math.sin(self.rot[2]), math.cos(self.rot[2]), 0], [0, 0, 1] ]) S = Matrix([ [math.cos(self.rot[1]), 0, -1 * math.sin(self.rot[1])], [0, 1, 0], [math.sin(self.rot[1]), 0, math.cos(self.rot[1])] ]) T = Matrix([ [1, 0, 0], [0, math.cos(self.rot[0]), math.sin(self.rot[0])], [0, -1 * math.sin(self.rot[0]), math.cos(self.rot[0])] ]) data = F * data data = S * data data = T * data x = (width / 2) * data.matrix[0][0] / data.matrix[0][2] y = (height / 2) * data.matrix[0][1] / data.matrix[0][2] return [x, y] def events(self, event): if event.type == pygame.MOUSEMOTION: x, y = event.rel x /= 300 y /= 300 self.rot[0] -= y self.rot[1] += x def update(self, dt, key): s = dt * 2 if key[pygame.K_LSHIFT]: self.pos.matrix[0][1] -= s if key[pygame.K_SPACE]: self.pos.matrix[0][1] += s x, y = s*math.sin(self.rot[1]), s*math.cos(self.rot[1]) if key[pygame.K_w]: self.pos.matrix[0][0] -= x; self.pos.matrix[0][2] -= y if key[pygame.K_s]: self.pos.matrix[0][0] += x; self.pos.matrix[0][2] += y if key[pygame.K_a]: self.pos.matrix[0][0] += y; self.pos.matrix[0][2] -= x if key[pygame.K_d]: self.pos.matrix[0][0] -= y; self.pos.matrix[0][2] += x Where the movement and rotation is controled, and where the projection matrix is used. cam = Cam() pygame.init() screen = pygame.display.set_mode((width, height)) clock = pygame.time.Clock() pygame.event.get() pygame.mouse.get_rel() pygame.mouse.set_visible(0) pygame.event.set_grab(1) Initialize # tris = getobj("untitled.obj") tris = [ Triangle([[1, -1, 1], [-1, -1, -1], [-1, -1, 1]]), Triangle([[-1, -1, -1], [1, 1, -1], [-1, 1, -1]]), Triangle([[1, -1, -1], [1, 1, 1], [1, 1, -1]]), Triangle([[-1, 1, 1], [1, 1, -1], [1, 1, 1]]), Triangle([[-1, -1, 1], [-1, 1, -1], [-1, 1, 1]]), Triangle([[1, -1, 1], [-1, 1, 1], [1, 1, 1]]), Triangle([[1, -1, 1], [1, -1, -1], [-1, -1, -1]]), Triangle([[-1, -1, -1], [1, -1, -1], [1, 1, -1]]), Triangle([[1, -1, -1], [1, -1, 1], [1, 1, 1]]), Triangle([[-1, 1, 1], [-1, 1, -1], [1, 1, -1]]), Triangle([[-1, -1, 1], [-1, -1, -1], [-1, 1, -1]]), Triangle([[1, -1, 1], [-1, -1, 1], [-1, 1, 1]]) ] The data in the obj file I use. while True: dt = clock.tick()/1000 screen.fill((255, 255, 255)) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit(); sys.exit() cam.events(event) for tri in tris: coord1 = cam.getcoord(tri.verts[0]) coord2 = cam.getcoord(tri.verts[1]) coord3 = cam.getcoord(tri.verts[2]) pygame.draw.line(screen, (0, 0, 0), (int(coord1[0]), int(coord1[1])), (int(coord2[0]), int(coord2[1]))) pygame.draw.line(screen, (0, 0, 0), (int(coord1[0]), int(coord1[1])), (int(coord3[0]), int(coord3[1]))) pygame.draw.line(screen, (0, 0, 0), (int(coord2[0]), int(coord2[1])), (int(coord3[0]), int(coord3[1]))) pygame.display.flip() key = pygame.key.get_pressed() cam.update(dt, key) Draw the points on screen.
Your code is fine. However, the origin of the pygame coordinate system (0, 0) is in the upper left corner of the window. You need to translate the geometry from the top left corner to the center of the window: x = (width / 2) * data.matrix[0][0] / data.matrix[0][2] y = (height / 2) * data.matrix[0][1] / data.matrix[0][2] x = (width / 2) + (width / 2) * data.matrix[0][0] / data.matrix[0][2] y = (height / 2) + (height / 2) * data.matrix[0][1] / data.matrix[0][2] I suggest changing the starting position of the camera: class Cam: def __init__(self): self.pos = Matrix([[0, 0, -5]]) self.rot = [0, 0, 0] I created the animation with: angle_y = 0 while True: angle_y += 0.01 cam.rot[1] = angle_y cam.pos = Matrix([[-math.sin(angle_y)*6, 0, -math.cos(angle_y)*6]]) dt = clock.tick(100)/1000 # [...] See also Pygame rotating cubes around axis and Does PyGame do 3d?.
Your matrix multiplication is incorrectly defined. I've rewritten it in the style presented, though I would comment that there are more pythonic ways to do this: def __mul__(self, other): if self.width != other.height: raise ValueError("incompatible matrices") result = [] for h in range(self.height): row = [] for w in range(other.width): SUM = 0 for i in range(self.width): SUM += self.matrix[h][i] * other.matrix[i][w] row.append(SUM) result.append(row) return Matrix(result)
Problem when resizing and saving a turtle screen
So I could save a big drawing and see its full size in an image visualizer, I resized my turtle window bigger then my monitor size. But the saved image is not being resized, so the drawing is being truncated: from turtle import Screen, Turtle import random screen = Screen() screen.setup(width=1200, height=2700, startx=None, starty=None) t = Turtle(visible=False) t.speed('fastest') # because I have no patience t2 = Turtle(visible=False) t2.speed('fastest') # because I have no patience t3 = Turtle(visible=False) t3.speed('fastest') # because I have no patience def got(x, y, d): # to use goto more easily t.penup() t.goto(x, y) t.pendown() t.seth(d) def flatoval(r): # Horizontal Oval t.right(45) for loop in range(2): t.circle(r, 90) t.circle(r / 2, 90) got(0, -200, 0) def elipse(r, a, b, c): for extent in range(9): rnd = random.randint(1, 20) # if extent == 0 or extent == 3 or extent == 6 : # t.color('red') # if extent == 1 or extent == 4 or extent == 7 : # t.color('yellow') # if extent == 2 or extent == 5 or extent == 8 : # t.color('blue') t.circle(r, 10) heading = t.heading() if extent == 0 or extent == 1 or extent == 2: # t.color('green') t.setheading(0) t.forward(rnd) t.forward(a) t.backward(rnd) t.forward(c) t.setheading(heading) def canais(x, y, d, egnar): for tog in range(egnar): got(x, y, d) elipse(100, 0, 0, 0) elipse(50, 0, 0, 0) elipse(100, 0, 0, 0) elipse(50, 0, 0, 0) d = d + 10 elipse(200, 0, 0, 0) elipse(100, 0, 0, 0) elipse(200, 0, 0, 0) elipse(100, 0, 0, 0) elipse(300, 0, 0, 0) elipse(200, 0, 0, 0) elipse(300, 0, 0, 0) elipse(200, 0, 0, 0) canais(0, -100, 0, 40) ts = t.getscreen() ts.getcanvas().postscript(file="canais_organizados_separadamente.eps") I also tried this change: screen = Screen() screen.setup(width=1200, height=2700, startx=None, starty=None) in place of: screen = Screen() screen.setup(400, 500) Truncated image:
By default, the tkinter canvas postscript() method only captures the visible portion of the canvas. You need to tell it, via the width and height arguments, whether you want more than that. Below is your code reworked with that fix and several others to improve the performance and/or simplify the logic: from turtle import Screen, Turtle from random import randint def got(x, y, d): # to use goto more easily turtle.penup() turtle.goto(x, y) turtle.pendown() turtle.setheading(d) def flatoval(r): # Horizontal Oval turtle.right(45) for _ in range(2): turtle.circle(r, 90) turtle.circle(r / 2, 90) def elipse(r, a, b, c): for extent in range(9): rnd = randint(1, 20) turtle.circle(r, 10) heading = turtle.heading() if extent <= 2: turtle.setheading(0) turtle.forward(rnd) turtle.forward(a) turtle.backward(rnd) turtle.forward(c) turtle.setheading(heading) def canais(x, y, d, egnar): for _ in range(egnar): got(x, y, d) elipse(100, 0, 0, 0) elipse(50, 0, 0, 0) elipse(100, 0, 0, 0) elipse(50, 0, 0, 0) elipse(200, 0, 0, 0) elipse(100, 0, 0, 0) elipse(200, 0, 0, 0) elipse(100, 0, 0, 0) elipse(300, 0, 0, 0) elipse(200, 0, 0, 0) elipse(300, 0, 0, 0) elipse(200, 0, 0, 0) d += 10 screen = Screen() screen.setup(1200, 1200) turtle = Turtle(visible=False) got(0, -200, 0) screen.tracer(False) canais(0, -100, 0, 36) screen.tracer(True) canvas = screen.getcanvas() canvas.postscript(file="canais_organizados_separadamente.eps", width=1200, height=1200)
Convert Function to a Class Python, Beginner
Hi I am attempting to write my first Class by converting this function into a class. # My Function def left_click_first_Thompson(x, y, clicks=17): SetCursorPos(x, y) for i in range(clicks): mouse_event(2, 0, 0, 0, 0) mouse_event(4, 0, 0, 0, 0) def left_click_M1A1_Thompson(x, y, clicks=2): SetCursorPos(x, y) for i in range(clicks): mouse_event(2, 0, 0, 0, 0) mouse_event(4, 0, 0, 0, 0) """Me Attempting to Turn it into a Class """ class AimMouse(object): # The Constructor to Instantiate the Objects def __init__(self, x, y, clicks): self.x = x self.y = y self.clicks = clicks def left_click_first_Thompson(self, x, y, clicks=17): SetCursorPos(x, y) for i in range(clicks): mouse_event(2, 0, 0, 0, 0) mouse_event(4, 0, 0, 0, 0) def left_click_M1A1_Thompson(self, x, y, clicks=2): SetCursorPos(x, y) for i in range(clicks): mouse_event(2, 0, 0, 0, 0) mouse_event(4, 0, 0, 0, 0) Can I please get someone to proof read and correct ? Thanks
Edit: Since I just understood your idea now, here is some code to put you on the right track: import ctypes SetCursorPos = ctypes.windll.user32.SetCursorPos mouse_event = ctypes.windll.user32.mouse_event class Aimer: def __init__(self, clicks): self.clicks = clicks def handle_click(self, x, y): SetCursorPos(x, y) for i in range(self.clicks): mouse_event(2, 0, 0, 0, 0) mouse_event(4, 0, 0, 0, 0) thompsonAimer = Aimer(clicks=17) m1a1Aimer = Aimer(clicks=2) Your code is correct, but you do not explain your motivation to turn this into a class. A class is often used when the various functions need to share some state. For instance, let's say we want to fix the number of clicks for all function calls, and the functions do not receive such an argument. You set self.clicks = clicks in the constructor, and then in left_click... instead of clicks you use self.clicks. The way it is now, it is pointless to store self.x, self.y and self.clicks because they are not being used. If you are a beginner, I would not recommend focusing on classes, though it is good to have an idea of how they work.
Rendered model disappears at some asymptote
I've been trying to render a simple teapot with PyOpenGL, but have been running into strange issues. I can't seem to figure out exactly where the error originates from, despite the simplicity of the code. Main.py import pygame from pygame.locals import * from MV import * import ctypes from OpenGL.GL import * from OpenGL.GL import shaders from OpenGL.GLU import * import teapot as tp vertex_shader = ''' #version 420 in vec3 vpos_modelspace; in vec3 vnorm_modelspace; uniform mat4 mvp; out vec4 vertcolor; void main(){ vertcolor = vec4(vnorm_modelspace, 1.0); gl_Position = mvp * vec4(vpos_modelspace, 1.0); } ''' fragment_shader = ''' #version 420 in vec4 vertcolor; out vec4 fragcolor; void main(){ fragcolor = vertcolor; } ''' model = tp.teapot pygame.init() canvas = pygame.display.set_mode((800, 600), DOUBLEBUF|OPENGL) pygame.display.set_caption('Test') glClearColor(.5, .5, .5, 1) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LESS) glDisable(GL_CULL_FACE) VERTEXSHADER = shaders.compileShader(vertex_shader, GL_VERTEX_SHADER) FRAGMENTSHADER = shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER) program = shaders.compileProgram(VERTEXSHADER, FRAGMENTSHADER) glUseProgram(program) vpos_loc = glGetAttribLocation(program, 'vpos_modelspace') vnorm_loc = glGetAttribLocation(program, 'vnorm_modelspace') mvp_loc = glGetUniformLocation(program, 'mvp') eye = numpy.array([0, 0, 1], dtype=numpy.float32) at = numpy.array([0, 0, 0], dtype=numpy.float32) up = numpy.array([0, 1, 0], dtype=numpy.float32) mvp = frustum(-1, 1, 1, -1, .1, 1000)#lookAt(eye, at, up) vao = glGenVertexArrays(1) glBindVertexArray(vao) vbo_pos = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo_pos) vbo_norm = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo_norm) verts = [] normals = [] for i in range(0, len(model.faces), 3): index = model.faces[i:i+3] verts.extend(model.vertices[3*index[0]:3*index[0]+3]) verts.extend(model.vertices[3*index[1]:3*index[1]+3]) verts.extend(model.vertices[3*index[2]:3*index[2]+3]) normals.extend(model.normals[3*index[0]:3*index[0]+3]) normals.extend(model.normals[3*index[1]:3*index[1]+3]) normals.extend(model.normals[3*index[2]:3*index[2]+3]) verts = numpy.array(verts, dtype=numpy.float32) normals = numpy.array(normals, dtype=numpy.float32) glBindBuffer(GL_ARRAY_BUFFER, vbo_pos) glBufferData(GL_ARRAY_BUFFER, verts.size * verts.itemsize, verts, GL_STATIC_DRAW) glVertexAttribPointer(vpos_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0)) glEnableVertexAttribArray(vpos_loc) glBindBuffer(GL_ARRAY_BUFFER, vbo_norm) glBufferData(GL_ARRAY_BUFFER, normals.size * normals.itemsize, normals, GL_STATIC_DRAW) glVertexAttribPointer(vnorm_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0)) glEnableVertexAttribArray(vnorm_loc) glBindBuffer(GL_ARRAY_BUFFER, 0) glBindVertexArray(0) while(True): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() glUseProgram(program) rotation_matrix = rotate(.01, [0, 1, 0]) mvp = mvp # rotation_matrix glUniformMatrix4fv(mvp_loc, 1, GL_FALSE, mvp.flatten()) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) glBindVertexArray(vao) glDrawArrays(GL_TRIANGLES, 0, int(verts.size/3)) glBindVertexArray(0) glUseProgram(0) pygame.display.flip() main() MV.py import numpy def normalize(vector): return vector/numpy.linalg.norm(vector) def translate(pos): return numpy.array([[1, 0, 0, pos[0]], [0, 1, 0, pos[1]], [0, 0, 1, pos[2]], [0, 0, 0, 1]], dtype=numpy.float32) def rotate(angle, axis): rads = angle * numpy.pi/180 v = normalize(axis) c = numpy.cos(rads) omc = 1-c s = numpy.sin(rads) return numpy.array([[v[0]*v[0]*omc + c, v[0]*v[1]*omc - v[2]*s, v[0]*v[2]*omc + v[1]*s, 0], [v[0]*v[1]*omc + v[2]*s, v[1]*v[1]*omc + c, v[1]*v[2]*omc - v[0]*s, 0], [v[0]*v[2]*omc - v[1]*s, v[1]*v[2]*omc + v[0]*s, v[2]*v[2]*omc + c, 0], [0, 0, 0, 1]], dtype=numpy.float32) def lookAt(eye, at, up): n = normalize(at-eye) u = normalize(numpy.cross(n, up)) v = normalize(numpy.cross(u, n)) rotate = numpy.array([[u[0], v[0], -n[0], 0], [u[1], v[1], -n[1], 0], [u[2], v[2], -n[2], 0], [0, 0, 0, 1]], dtype=numpy.float32).transpose() return rotate#translate(-eye) def frustum(left, right, top, bottom, near, far): rl = right-left tb = top-bottom fn = far-near return numpy.array([[2*near/rl, 0, (right+left)/rl, 0], [0, 2*near/tb, (top+bottom)/tb, 0], [0, 0, -(far+near)/fn, -(2*far*near)/fn], [0, 0, -1, 0]], dtype=numpy.float32) The output shows the teapot being rotated (though not about the axis that I expected) and sort of shrinking and disappearing at rotations of 0, pi, 2pi, etc. I believe the teapot vertices are being processed correctly, as it does show up when rotated and is correctly shaded with normal values. Output at 5 degrees - Model is 'growing' Output at 30 degrees - Strange culling? Output at 60 degrees - Relatively normal Output at 170 degrees - Model is 'shrinking' Output at 190 degrees - Model is 'growing' on the other side of the plane At rotations 0, pi, 2pi, etc the model is completely invisible/too small to see.
3d cube didn't show correctly writen by pyglet
Recently, I have started to learn openGL from this site => http://3dgep.com/?p=2365 and I encounter a problem. That's I didn't get the scene as the site shows. I post my code on this site: import pyglet from pyglet.gl import * from pyglet import clock, window ''' http://www.learnersdictionary.com/search/aspect a dictionary site http://www.opengl.org/sdk/docs/man2/ opengl api reference ''' def vector(type, *args): ''' return a ctype array GLfloat GLuint ... ''' return (type*len(args))(*args) class model: def __init__(self, vertices, colorMatrix, indice): self.vertices = vertices self.colorMatrix = colorMatrix self.indice = indice self.angle = 0 def update(self): self.angle += 1 self.angle %= 360 def draw(self): glMatrixMode(GL_MODELVIEW) glLoadIdentity() glRotatef(self.angle, 1, 1, 0) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_COLOR_ARRAY) glColorPointer(3, GL_FLOAT, 0, vector(GLfloat, *self.colorMatrix)) glVertexPointer(3, GL_FLOAT, 0, vector(GLfloat, *self.vertices)) glDrawElements(GL_QUADS, len(self.indice), GL_UNSIGNED_INT, vector(GLuint, *self.indice)) glDisableClientState(GL_COLOR_ARRAY) glDisableClientState(GL_VERTEX_ARRAY) class world: def __init__(self): self.element = [] def update(self, dt): for obj in self.element: obj.update() def addModel(self, model): self.element.append(model) def draw(self): for obj in self.element: obj.draw() def setup(): # look for GL_DEPTH_BUFFER_BIT glEnable(GL_DEPTH_TEST) win = window.Window(fullscreen=False, vsync=True, resizable=True, height=600, width=600) mWorld = world() cube = ( 1, 1, 1, #0 -1, 1, 1, #1 -1, -1, 1, #2 1, -1, 1, #3 1, 1, -1, #4 -1, 1, -1, #5 -1, -1, -1, #6 1, -1, -1 #7 ) color = ( 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1 ) indice = ( 0, 1, 2, 3, # front face 0, 4, 5, 1, # top face 4, 0, 3, 7, # right face 1, 5, 6, 2, # left face 3, 2, 6, 7 # bottom face #4, 7, 6, 5 #back face ) obj = model(cube, color, indice) mWorld.addModel(obj) #win.event def on_resize(width, height): glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(-10, 10, -10, 10, -10, 10) glMatrixMode(GL_MODELVIEW) return pyglet.event.EVENT_HANDLED #win.event def on_draw(): glClearColor(0.2, 0.2, 0.2, 0.8) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) mWorld.draw() pyglet.clock.schedule(mWorld.update) clock.set_fps_limit(30) setup() pyglet.app.run() I think maybe I miss some important concepts so I can't get the correct result. Can anyone teach me what mistake I make? :( Furthermore, there is something strange. indice = ( 0, 1, 2, 3, # front face 0, 4, 5, 1, # top face 4, 0, 3, 7, # right face 1, 5, 6, 2, # left face 3, 2, 6, 7 # bottom face #4, 7, 6, 5 #back face ) If I uncomment this line from #4, 7, 6, 5 #back face to 4, 7, 6, 5 #back face the screen will show nothing...
0.0 well, that's weird. I have tried to translate this code into C++ and it shows correctly. I use opengl, glut, and c++. So, I think maybe that's the issue on pyglet. Whatever, I can go on my studying about openGL :) Finally, I find the way how to make this code run correctly!! change the code here self.vertices = vector(GLfloat, *vertices) self.colorMatrix = vector(GLfloat, *colorMatrix) self.indice = vector(GLuint, *indice) and glColorPointer(3, GL_FLOAT, 0, self.colorMatrix) glVertexPointer(3, GL_FLOAT, 0, self.vertices) glDrawElements(GL_QUADS, len(self.indice), GL_UNSIGNED_INT, self.indice) well, Is the key point garbage collection? I think self.vertices = vector(GLfloat, *vertices) this way makes there is an object to reference the vector so it won't be freed when I called glDrawElements(...) and others functions which need the c-type array
indice = ( 0, 1, 2, 3, # front face 0, 4, 5, 1, # top face 4, 0, 3, 7, # right face 1, 5, 6, 2, # left face 3, 2, 6, 7 # bottom face #4, 7, 6, 5 #back face ) You missed the last semicolon at 3,2,6,7[,] # bottom face.