I am making a marching cubes project in python using PyQt5 and PyOpenGL. I am trying to hide the wireframe cube which marches across the screen, referenced as mainWindow.marchingCube to disappear after cycling through. I managed to get the disappearing cycle to occur, but the cube does not actually disappear. I called the QOpenGLWidget's update function, but the cube still did not disappear.
import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QSlider,
QOpenGLWidget, QLabel, QPushButton
)
from PyQt5.QtCore import Qt
from OpenGL.GL import (
glLoadIdentity, glTranslatef, glRotatef,
glClear, glBegin, glEnd,
glColor3fv, glVertex3fv,
GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
GL_QUADS, GL_LINES
)
from OpenGL.GLU import gluPerspective
from numerics import sin, cos, tan, avg, rnd #Numerics is my custom math library.
import random, time
class mainWindow(QMainWindow): #Main class.
shapes = [] #this will hold instances of the following classes: cube
dataPoints = []
zoomLevel = -10
rotateDegreeV = -90
rotateDegreeH = 0
marchActive = False
limit = -1
meshPoints = []
class cube():
render = True
solid = False
color = (1, 1, 1)
def config(self, x, y, z, size = 0.1, solid = False, color = (1, 1, 1)):
self.solid = solid
self.color = color
self.size = size / 2
s = self.size
self.vertices = [
(-s + x, s + y, -s + z),
(s + x, s + y, -s + z),
(s + x, -s + y, -s + z),
(-s + x, -s + y, -s + z),
(-s + x, s + y, s + z),
(s + x, s + y, s + z),
(s + x, -s + y, s + z),
(-s + x, -s + y, s + z)
]
self.edges = [
(0,1), (0,3), (0,4), (2,1),
(2,3), (2,6), (7,3), (7,4),
(7,6), (5,1), (5,4), (5,6)
]
self.facets = [
(0, 1, 2, 3), (0, 1, 6, 5),
(0, 3, 7, 4), (6, 5, 1, 2),
(6, 7, 4, 5), (6, 7, 3, 2)
]
def show(self):
self.render = True
def hide(self):
self.render = False
class dataPoint():
location = (0, 0, 0)
value = 0
shape = None
def place(self, x, y, z):
self.location = (x, y, z)
def set(self, val):
self.value = val
def setShape(self, shape):
self.shape = shape
class meshPoint():
location = (0, 0, 0)
shape = None
def place(self, x, y, z):
self.location = (x, y, z)
def setShape(self, shape):
self.shape = shape
def keyPressEvent(self, event): #This is the keypress detector. I use this to determine input to edit grids.
try:
key = event.key()
#print(key)
if key == 87:
self.rotateV(5)
elif key == 65:
self.rotateH(5)
elif key == 83:
self.rotateV(-5)
elif key == 68:
self.rotateH(-5)
elif key == 67:
self.zoom(1)
elif key == 88:
self.zoom(-1)
elif key == 77:
self.marchStep()
except:
pass
def __init__(self):
super(mainWindow, self).__init__()
self.currentStep = 0
self.width = 700 #Variables used for the setting of the size of everything
self.height = 600
self.setGeometry(0, 0, self.width + 50, self.height) #Set the window size
self.initData(3, 3, 3)
def setupUI(self):
self.openGLWidget = QOpenGLWidget(self) #Create the GLWidget
self.openGLWidget.setGeometry(0, 0, self.width, self.height)
self.openGLWidget.initializeGL()
self.openGLWidget.resizeGL(self.width, self.height) #Resize GL's knowledge of the window to match the physical size?
self.openGLWidget.paintGL = self.paintGL #override the default function with my own?
self.filterSlider = QSlider(Qt.Vertical, self)
self.filterSlider.setGeometry(self.width + 10, int(self.height / 2) - 100, 30, 200)
self.filterSlider.valueChanged[int].connect(self.filter)
self.limitDisplay = QLabel(self)
self.limitDisplay.setGeometry(self.width, int(self.height / 2) - 130, 50, 30)
self.limitDisplay.setAlignment(Qt.AlignCenter)
self.limitDisplay.setText('-1')
self.marchButton = QPushButton(self)
self.marchButton.setGeometry(self.width, int(self.height / 2) - 160, 50, 30)
self.marchButton.setText('March!')
self.marchButton.clicked.connect(self.marchStep)
def marchStep(self):
if not self.marchActive:
marchAddr = len(self.shapes)
self.shapes.append(self.cube())
self.marchingCube = self.shapes[marchAddr]
self.marchActive = True
self.currentStep = 0
if self.currentStep == len(self.marchPoints):
self.currentStep = 0
#print('meshPoints: {}'.format(self.meshPoints))
for mp in self.meshPoints:
#print(mp.shape)
self.shapes.remove(mp.shape)
self.meshPoints.clear()
self.marchingCube.hide()
return
if self.currentStep == 0:
self.marchingCube.show()
p = self.marchPoints[self.currentStep]
x, y, z = p
self.marchingCube.config(x, y, z, size = 1)
points = []
for i in range(8):
point = self.getDataPointByLocation(self.marchingCube.vertices[i])
points.append(point)
self.openGLWidget.update()
#print('step: {} x: {} y: {} z: {}'.format(self.currentStep, x, y, z))
#for point in points:
# print(point.location, end = ' ')
#print()
for pair in self.marchingCube.edges:
pointA = points[pair[0]]
pointB = points[pair[1]]
#print('pointA.value: {} pointB.value: {} limit: {}'.formatpointA.value, pointB.value, self.limit)
if (pointA.value < self.limit and pointB.value > self.limit) or (pointA.value > self.limit and pointB.value < self.limit):
xA, yA, zA = pointA.location
xB, yB, zB = pointB.location
valA = (pointA.value + 1) / 2
valB = (pointB.value + 1) / 2
xC = float(avg([xA, xB]))
yC = float(avg([yA, yB]))
zC = float(avg([zA, zB]))
mp = self.meshPoint()
mp.place(xC, yC, zC)
mp.setShape(self.cube())
mp.shape.config(xC, yC, zC, size = 0.05, solid = True, color = (1, 0, 0))
self.shapes.append(mp.shape)
self.meshPoints.append(mp)
self.currentStep += 1
self.openGLWidget.update()
def zoom(self, value):
self.zoomLevel += value
self.openGLWidget.update()
def rotateV(self, value):
self.rotateDegreeV += value
self.openGLWidget.update()
def rotateH(self, value):
self.rotateDegreeH += value
self.openGLWidget.update()
def filter(self, value):
self.limit = rnd((value / 49.5) -1, -2)
for d in self.dataPoints:
if d.value < self.limit:
d.shape.hide()
else:
d.shape.show()
self.limitDisplay.setText(str(self.limit))
self.openGLWidget.update()
def getDataPointByLocation(self, coord):
x, y, z = coord
for dp in self.dataPoints:
if dp.location == (x, y, z):
return dp
return False
def paintGL(self):
glLoadIdentity()
gluPerspective(45, self.width / self.height, 0.1, 110.0) #set perspective?
glTranslatef(0, 0, self.zoomLevel) #I used -10 instead of -2 in the PyGame version.
glRotatef(self.rotateDegreeV, 1, 0, 0) #I used 2 instead of 1 in the PyGame version.
glRotatef(self.rotateDegreeH, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
if len(self.shapes) != 0:
glBegin(GL_LINES)
for s in self.shapes:
glColor3fv(s.color)
if s.render and not s.solid:
for e in s.edges:
for v in e:
glVertex3fv(s.vertices[v])
glEnd()
glBegin(GL_QUADS)
for s in self.shapes:
glColor3fv(s.color)
if s.render and s.solid:
for f in s.facets:
for v in f:
glVertex3fv(s.vertices[v])
glEnd()
def initData(self, sizeX, sizeY, sizeZ):
marchSizeX = sizeX - 1
marchSizeY = sizeY - 1
marchSizeZ = sizeZ - 1
xOff = -(sizeX / 2) + 0.5
yOff = -(sizeY / 2) + 0.5
zOff = -(sizeZ / 2) + 0.5
xMarchOff = -(marchSizeX / 2) + 0.5
yMarchOff = -(marchSizeY / 2) + 0.5
zMarchOff = -(marchSizeZ / 2) + 0.5
vals = []
self.marchPoints = []
for z in range(marchSizeZ):
for y in range(marchSizeY):
for x in range(marchSizeX):
self.marchPoints.append((x + xMarchOff, y + yMarchOff ,z + zMarchOff))
for z in range(sizeZ):
for y in range(sizeY):
for x in range(sizeX):
loc = len(self.dataPoints)
val = self.generate(x + xOff, y + yOff, z + zOff)
self.dataPoints.append(self.dataPoint())
self.dataPoints[loc].place(x + xOff, y + yOff, z + zOff)
self.dataPoints[loc].set(val)
loc2 = len(self.shapes)
self.shapes.append(self.cube())
self.shapes[loc2].config(x + xOff, y + yOff, z + zOff, solid = True, color = (0, (val + 1) / 2, (val + 1) / -2 + 1))
self.dataPoints[loc].setShape(self.shapes[loc2])
vals.append(val)
print(avg(vals))
def generate(self, xIn, yIn, zIn): #Function which produces semi-random values based on the supplied coordinates.
i = -(xIn * yIn * (10 + zIn))
j = xIn * yIn * (10 + zIn)
if i < j:
mixer = random.randint(i, j)
else:
mixer = random.randint(j, i + 1)
a = avg([sin(cos(xIn)), tan(tan(yIn)), cos(tan(zIn))])
out = mixer * a
while out > 10:
out -= 5
while out < -10:
out += 5
return float(out / 10)
app = QApplication([])
window = mainWindow()
window.setupUI()
window.show()
sys.exit(app.exec_())
Why doesn't the cube disappear? I have caught wind during my web searches on the subject that update does not always work as expected. Directly calling self.openGLWidget.paintGL() does not work either. What must I do to make the cube disappear?
EDIT:
If I make a call to rotate, rotate, or zoom, the screen refreshes and the meshpoints as well as the marching cube all disappear. I think I may end up making a workaround by calling one of those with a zero value.
To test, save the following code in a file named numerics.py in the same directory as the rest of the code.
from decimal import Decimal as dec
degrad = 'deg'
pi = 3.14159265358979323846
terms = dec(9) #number of terms used for the trig calculations
def version():
print('numerics.py version 1.0.0')
print('Packaged with the cubes project')
def mode(modeinput = ''): #switch between degrees and radians or check the current mode
global degrad
if modeinput == 'deg':
degrad = 'deg'
return 'deg'
if modeinput == 'rad':
degrad = 'rad'
return 'rad'
if modeinput == '':
return degrad
else:
return False
def accuracy(accinput = ''):
global terms
global pi
if accinput == '':
return terms
terms = dec(accinput)
PI = calculatePi(accinput)
print('Pi is: {}'.format(PI))
return terms
def calculatePi(placeIn = terms):
if placeIn > 15:
if input("Warning: You have chosen to calculate more than 20 digits of pi. This may take a LONG TIME and may be inacurate. Enter 'yes' if you wish to proceed. If you enter anything else, this function will revert to 10 digits.") == 'yes':
place = placeIn
else:
place = 10
else:
place = placeIn
print('Calculating Pi...\nPlease wait, as this may take a while.')
PI = dec(3)
addSub = True
for i in range(2, 2 * (int(place) ** 6) + 1, 2):
if addSub:
PI += dec(4) / (dec(i) * dec(i + 1) * dec(i + 2))
elif not addSub:
PI -= dec(4) / (dec(i) * dec(i + 1) * dec(i + 2))
addSub = not addSub
return rnd(PI, -(place), mode = 'cutoff')
def radToDeg(radin):
return (dec(radin) * dec(180 / pi))
def degToRad(degin):
return (dec(degin) * dec(pi / 180))
def avg(numsIn): #return the average of two numbers, specified as an integer or float
num1 = dec(0)
for i in numsIn:
num1 += dec(i)
return rnd(dec(num1 / dec(len(numsIn))))
def sin(anglein, dr = degrad): #return sine of the supplied angle using the predetermined mode or the supplied mode
if dr == 'deg':
while anglein > 180:
anglein -= 360
while anglein < -180:
anglein += 360
angle = degToRad(anglein)
if dr == 'rad':
while anglein > pi:
anglein -= (2 * pi)
while anglein < -pi:
anglein += (2 * pi)
angle = anglein
return rnd(rawsin(dec(angle)), -terms)
def arcsin(ratioin, dr = degrad): #return arcsine of the supplied ratio using the predetermined mode or the supplied mode
if ratioin > 1 or ratioin < -1: #if the input is illegal
return False
attempt = dec(0) #start at 0
target = rnd(dec(ratioin), -terms) #identify the target value
#print('target is: {}'.format(target))
for i in range(-1, int(terms) + 1): #for each place from 10s to terms decimal place (use -i, not i)
#print('Editing place {0}'.format(10 ** -i)) #debugging
for j in range(10): #for 10 steps
#print('current attempt: {}'.format(attempt), end = ' ')
if rnd(sin(attempt, dr), -terms) == target:
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return final
if rnd(sin(attempt, dr), -terms) < target:
#add some
attempt += (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
if rnd(sin(attempt, dr), -terms) > target:
#subtract some
attempt -= (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
#print('')
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return (final)
def cos(anglein, dr = degrad): #return cosine of the supplied angle
if dr == 'deg':
return rawsin(degToRad(90 - anglein))
else:
angle = anglein
return rnd(rawsin(90 - angle), -terms)
def arccos(ratioin, dr = degrad): #return arccosine of the supplied ratio
if ratioin > 1 or ratioin < -1:
return False
attempt = dec(0) #start at 0
target = rnd(dec(ratioin), -terms) #identify the target value
#print('target is: {}'.format(target))
for i in range(-1, int(terms) + 1): #for each place from 10s to terms decimal place (use -i, not i)
#print('Editing place {0}'.format(10 ** -i)) #debugging
for j in range(10): #for 10 steps
#print('current attempt: {}'.format(attempt), end = ' ')
if rnd(cos(attempt, dr), -terms) == target:
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return final
if rnd(cos(attempt, dr), -terms) < target:
#add some
attempt += (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
if rnd(cos(attempt, dr), -terms) > target:
#subtract some
attempt -= (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
#print('')
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return (final)
def tan(anglein, dr = degrad): #return tangent of the supplied angle
a = sin(anglein, dr)
b = cos(anglein, dr)
if (not a == 0) and (not b == 0):
return rnd((a / b), -terms)
else:
return False
def arctan(ratioin, dr = degrad): #return arctangent of the supplied ratio
if ratioin > 1 or ratioin < -1:
return False
attempt = dec(0) #start at 0
target = rnd(dec(ratioin), -terms) #identify the target value
#print('target is: {}'.format(target))
for i in range(-1, int(terms) + 1): #for each place from 10s to terms decimal place (use -i, not i)
#print('Editing place {0}'.format(10 ** -i)) #debugging
for j in range(10): #for 10 steps
#print('current attempt: {}'.format(attempt), end = ' ')
if rnd(tan(attempt, dr), -terms) == target:
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return final
if rnd(tan(attempt, dr), -terms) < target:
#add some
attempt += (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
if rnd(tan(attempt, dr), -terms) > target:
#subtract some
attempt -= (dec(10) ** -i)
#print('attempt: {}'.format(attempt), end = ' ')
#print('')
if attempt < 0:
final = (attempt * dec(-1))
else:
final = attempt
#print('attempt: {0} final: {1}'.format(attempt, final))
return (final)
def rawsin(anglein): #return the result of sine of the supplied angle, using radians
#This is the taylor series used.
#final = x - (x^3 / 3!) + (x^5 / 5!) - (x^7 / 7!) + (x^9 / 9!) - (x^11 / 11!)...
angle = dec(anglein)
final = angle
add = False
for i in range(3, int(terms) * 3, 2):
if add:
final += dec(angle ** i) / fact(i)
elif not add:
final -= dec(angle ** i) / fact(i)
add = not add
return final
def fact(intin): #return the factorial of the given integer, return False if not given an int
if intin == int(intin):
intout = 1
for i in range(1, intin + 1):
intout *= i
return intout
else:
return False
def rnd(numIn, decPlcIn = -terms, mode = 'fiveHigher'): #return the given number, rounded to the given decimal place.
#use 1 to indicate 10s, 0 to indicate 1s, -2 to indicate 100ths, etc.
num1 = dec(numIn)
decPlc = dec(decPlcIn)
if mode == 'fiveHigher':
return dec(str(dec(round(num1 * (dec(10) ** -decPlc))) * (dec(10) ** decPlc)).rstrip('0'))
elif mode == 'cutoff':
return dec(str(dec(int(num1 * (dec(10) ** -decPlc))) * (dec(10) ** decPlc)).rstrip('0'))
def root(numIn, rootVal):
num = dec(numIn)
rt = dec(dec(1) / rootVal)
num1 = num ** rt
return rnd(num1, -terms)
def quad(aIn, bIn, cIn): #Plugin for the quadratic formula. Provide a, b, and c.
a = dec(aIn)
b = dec(bIn)
c = dec(cIn)
try:
posResult = (-b + root((b ** dec(2)) - (dec(4) * a * c), 2)) / (dec(2) * a)
except:
posResult = False
try:
negResult = (-b - root((b ** dec(2)) - (dec(4) * a * c), 2)) / (dec(2) * a)
except:
negResult = False
return (posResult, negResult)
You are missing 1 call to self.openGLWidget.update(). There is a return statement in the instruction block of the if. The function is terminated at this point and the self.openGLWidget.update() instruction at the end of the code is never executed.
Add self.openGLWidget.update() right before return, to solve the issue:
class mainWindow(QMainWindow):
# [...]
def marchStep(self):
if not self.marchActive:
# [...]
self.currentStep = 0
if self.currentStep == len(self.marchPoints):
# [...]
self.meshPoints.clear()
self.marchingCube.hide()
self.openGLWidget.update() # <--------- ADD
return
if self.currentStep == 0:
self.marchingCube.show()
# [...]
I just currently finished making the game 'snake' as a practice to learn how to program, as I am new to programming for about 3 months.
Although the game is completed and runs the way I intended, I want to try to simplify my code and reduce the amount of lines as much as possible, and possibly make the script tidier as the current majority of my codes are cluster in the while loop.
Until now I haven't touched upon class objects, and I want everything in the while loop to go into individual classes that get called out from the while loop to reduce the amount of lines in it.
off-topic: by reading through the script, how else can I improve it to be run more efficiently, including simplifying some code as I may have over-complicated it?
I looked up how class object is used from w3school and other programming tutorials, but I still don't fully understand it as it only shows examples in using print. I did play around and experimented with class object examples and attempted to call them without using print, but I lack the knowledge of how to use them properly.
from graphics import *
from threading import Timer
import keyboard, random, time
# configurations
width = 400
gridHeight = width
height = 470
timer = False
game = True
score = 0
bonus = 0
x = 70
y = 30
radius = 10
length = radius * 2
playerLength = 3
poisonLength = playerLength
i = 0
k = 0
pointRadius = 5
points = False
cherryPoints = False
key = "Right"
countDown = 0
# set coordinations
cX = 90
cY = 30
coordX = [10]
coordY = [10]
while coordX[len(coordX)-1] != width-10:
cX+=20
coordX.append(cX)
while coordY[len(coordY)-1] != 390:
cY+=20
coordY.append(cY)
randomX = random.choice(coordX)
randomY = random.choice(coordY)
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
# window set up
win = GraphWin("SNAKE", width, height, autoflush = False)
win.setBackground(color_rgb(15,15,15))
# grid
lineX = 20
while lineX < width:
gridX = Line(Point(lineX,0),Point(lineX,gridHeight))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineX += 20
lineY = 20
while lineY <= gridHeight:
gridX = Line(Point(0,lineY),Point(width,lineY))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineY += 20
# snake banner
UI = Rectangle(Point(0,400),Point(width,height))
UI.setFill(color_rgb(102,51,0))
UI.setOutline(color_rgb(102,51,0))
UI.draw(win)
snakeTitle = Text(Point(width/2,420),"SNAKE")
snakeTitle.setTextColor("green")
snakeTitle.setSize(20)
snakeTitle.draw(win)
scoreTitle = Text(Point(320,424),"SCORE")
scoreTitle.setTextColor("white")
scoreTitle.setSize(10)
scoreTitle.draw(win)
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# make player
player = {}
player[0] = Rectangle(Point(x-20-radius,y-radius), Point(x-20+radius, y+radius))
player[1] = Rectangle(Point(x-40-radius,y-radius), Point(x-40+radius, y+radius))
player[2] = Rectangle(Point(x-60-radius,y-radius), Point(x-60+radius, y+radius))
# make poison
poison = {}
def main():
global timer, scoreUI, score, bonus, playerLength, poisonLength, x, y, points, cherryPoints, randomX, randomY, cherryRandomX, cherryRandomY, poisonRandomX, poisonRandomY, key, countDown, k, game
while(game==True):
# score update
scoreUI.undraw()
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# generating new body blocks
if len(player) < playerLength:
i+=1
player[i] = player[i-1].clone()
# body following player
player[0].undraw()
for i in range(1,len(player)):
player[len(player)-i].undraw()
player[len(player)-i] = player[len(player)-i-1].clone()
player[len(player)-i].draw(win)
# update player's head coordinate
player[0] = Rectangle(Point(x-radius,y-radius), Point(x+radius,y+radius))
player[0].setFill("green")
player[0].setWidth(2)
player[0].draw(win)
# player movement
if keyboard.is_pressed("Up") and key != "Down":
key = "Up"
elif keyboard.is_pressed("Left") and key != "Right":
key = "Left"
elif keyboard.is_pressed("Down") and key != "Up":
key = "Down"
elif keyboard.is_pressed("Right") and key != "Left":
key = "Right"
if key == "Up":
y -= length
elif key == "Left":
x -= length
elif key == "Down":
y += length
elif key == "Right":
x += length
# point
if points == False: # generates new point when eaten
point = Rectangle(Point(randomX-pointRadius,randomY-pointRadius),Point(randomX+pointRadius,randomY+pointRadius))
point.setFill("white")
point.setWidth(2)
point.draw(win)
points = True
if player[0].getCenter().getX() == point.getCenter().getX() and player[0].getCenter().getY() == point.getCenter().getY(): # when player eats the point
point.undraw()
playerLength += 1
poisonLength += 1
score += 200+bonus
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(player)):
if (point.getCenter().getX() == player[i].getCenter().getX() and point.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoints == True and cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and cherry
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if point shares the same coordinate to other array of poisons
if point.getCenter().getX() == poison[i].getCenter().getX() and point.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
points = False
# cherry
if countDown == 150:
countDown = 0
if cherryPoints == False: # generates new cherry from countdown
cherryPoint = Rectangle(Point(cherryRandomX-pointRadius,cherryRandomY-pointRadius),Point(cherryRandomX+pointRadius,cherryRandomY+pointRadius))
cherryPoint.setFill(color_rgb(213,0,50))
cherryPoint.setWidth(2)
cherryPoint.draw(win)
cherryPoints = True
if cherryPoints == True:
for i in range(2, 6): # cherry blinks between countdown 40 to 100
if countDown == 20*i:
cherryPoint.undraw()
elif countDown == 10+(20*i):
cherryPoint.draw(win)
if countDown >= 100: # when countdown becomes 100, remove cherry and reset count down
cherryPoints = False
countDown = 0
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
if cherryPoints==True and player[0].getCenter().getX() == cherryPoint.getCenter().getX() and player[0].getCenter().getY() == cherryPoint.getCenter().getY(): # when player eats the cherry
cherryPoint.undraw()
score += 500
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(player)):
if (cherryPoint.getCenter().getX() == player[i].getCenter().getX() and cherryPoint.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if cherry shares the same coordinate to other array of poisons
if cherryPoint.getCenter().getX() == poison[i].getCenter().getX() and cherryPoint.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
cherryPoints = False
# poison
if poisonLength % 5 == 0: # generates a poison block each time the player size reaches the multiple of 5
poison[k] = Rectangle(Point(poisonRandomX-pointRadius,poisonRandomY-pointRadius),Point(poisonRandomX+pointRadius,poisonRandomY+pointRadius))
poison[k].setFill("green")
poison[k].setWidth(2)
poison[k].draw(win)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(player)):
if (poison[k].getCenter().getX() == player[i].getCenter().getX() and poison[k].getCenter().getY() == player[i].getCenter().getY()) or (poison[k].getCenter().getX() == point.getCenter().getX() and poison[k].getCenter().getY() == point.getCenter().getY()) or (cherryPoints==True and poison[k].getCenter().getX() == cherryPoint.getCenter().getX() and poison[k].getCenter().getY() == cherryPoint.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point and cherry
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(poison)):
if poison[k].getCenter().getX() == poison[i].getCenter().getX() and poison[k].getCenter().getY() == poison[i].getCenter().getY(): # regenerate x and y coordinate if new poison shares the same coordinate to other array of poisons
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
bonus+=50
k+=1
poisonLength+=1
# game over requirements
for i in range(len(poison)): # if player touches poison
if player[0].getCenter().getX() == poison[i].getCenter().getX() and player[0].getCenter().getY() == poison[i].getCenter().getY():
game = False
for i in range(2, len(player)): # if player touches its own body or reach out of window
if (player[0].getCenter().getX() == player[i].getCenter().getX() and player[0].getCenter().getY() == player[i].getCenter().getY()) or x < 0 or x > width or y < 0 or y > gridHeight:
game = False
# FPS
update(10)
countDown += 1
# GAME OVER
gameOver = Text(Point(width/2,200), "GAME OVER")
gameOver.setTextColor("red")
gameOver.setSize(30)
gameOver.draw(win)
update()
time.sleep(2)
win.close()
main()
Ideally the result should replace each code in the while loop with individual classes outside of the function to reduce the amount of lines in the main() function and make the script easier to read.
Classes are essentially just bundles of code that contain various attributes and methods.
A Snake class might have a list of coordinates for each section of the body (the first is the head).
class Snake:
def __init__(self, x, y):
self.positions = [(x, y)]
def get_head(self):
return self.positions[0]
def move_forward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] + 1)
def move_backward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] - 1)
...
And so on. Classes, at this level, let you think of objects as concrete entities, distinct from each other but easily manipulated.
I'm trying to figure out why the while loop in one of my functions is still running even after the points in my graphics are equal, which is when I set it to stop. Is there anything I'm doing wrong? I've tried to switch other things around to get it to work but no luck.
It's for a game--when the character reaches the endbox the loop needs to break, but it isn't doing that after I explicitly coded it to. It's in the second function I have:
from graphics import *
def field():
#creating the window
win = GraphWin('The Field',400,400)
win.setBackground('white')
#drawing the grid
boxlist = []
for i in range(0,400,40):
for j in range(0,400,40):
box = Rectangle(Point(i,j),Point(i+40,j+40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
#creating other boxes
startbox = Rectangle(Point(0,0),Point(40,40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
endbox = Rectangle(Point(360,360),Point(400,400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(startbox)
boxlist.append(endbox)
#creating Pete
pete = Rectangle(Point(2,2),Point(38,38))
pete.setFill('gold')
pete.draw(win)
return win,boxlist,pete
def move(win2,boxlist,pete,endbox):
peteloc = pete.getCenter()
#creating loop to move pete
while peteloc != endbox.getCenter():
click = win2.getMouse()
x = click.getX()
y = click.getY()
peteloc = pete.getCenter()
petex = peteloc.getX()
petey = peteloc.getY()
#moving pete
if x>=petex+20 and y<=petey+20 and y>=petey-20:
pete.move(40,0)
elif x<=petex-20 and y<=petey+20 and y>=petey-20:
pete.move(-40,0)
elif y>=petey+20 and x<=petex+20 and x>=petex-20:
pete.move(0,40)
elif y<=petey-20 and x<=petex+20 and x>=petex-20:
pete.move(0,-40)
peteloc = pete.getCenter()
# The main function
def main():
win2,boxlist,pete = field()
endbox = boxlist[len(boxlist)-1]
move(win2,boxlist,pete,endbox)
main()
I think maybe it is caused by precision of float. I guess pete.getCenter() and endbox.getCenter() are something like [float, float], you should avoid using != between float, such as 1.0000001 is not equal to 1.
So even if the character reaches the endbox, the position will still get a little float bias.
So you can change a != b to abs(a - b) > acceptable_error when the error is acceptable. Sample code is like:
# while peteloc != endbox.getCenter():
while abs(peteloc.getX() - endbox.getCenter().getX()) > 0.01 and abs(peteloc.getY() - endbox.getCenter().getY()) > 0.01:
Hope that will help you.
Zelle graphics Point objects don't appear to ever compare as equal:
>>> from graphics import *
>>> a = Point(100, 100)
>>> b = Point(100, 100)
>>> a == b
False
>>>
We have to extract coordinates and do our own comparison. Although #recnac provides a workable solution (+1), I'm going to suggest a more general one. We'll create a distance() method that's valid for any object that inherits from _BBox, which includes Rectangle, Oval, Circle and Line:
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
We can now measure the distance between objects, horizontally, vertically and diagonally. Since your boxes are moving twenty pixels at a time, we can assume that if they are withing 1 pixel of each other, they are in the same location. Your code rewritten to use the distance() method and other tweaks:
from graphics import *
def field(win):
# drawing the grid
boxlist = []
for i in range(0, 400, 40):
for j in range(0, 400, 40):
box = Rectangle(Point(i, j), Point(i + 40, j + 40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
# creating other boxes
startbox = Rectangle(Point(0, 0), Point(40, 40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
boxlist.append(startbox)
endbox = Rectangle(Point(360, 360), Point(400, 400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(endbox)
# creating Pete
pete = Rectangle(Point(2, 2), Point(38, 38))
pete.setFill('gold')
pete.draw(win)
return boxlist, pete
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
def move(win, pete, endbox):
# creating loop to move pete
while distance(pete, endbox) > 1:
click = win.getMouse()
x, y = click.getX(), click.getY()
peteloc = pete.getCenter()
petex, petey = peteloc.getX(), peteloc.getY()
# moving pete
if x >= petex + 20 and petey - 20 <= y <= petey + 20:
pete.move(40, 0)
elif x <= petex - 20 and petey - 20 <= y <= petey + 20:
pete.move(-40, 0)
elif y >= petey + 20 and petex - 20 <= x <= petex + 20:
pete.move(0, 40)
elif y <= petey - 20 and petex - 20 <= x <= petex + 20:
pete.move(0, -40)
# The main function
def main():
# creating the window
win = GraphWin('The Field', 400, 400)
win.setBackground('white')
boxlist, pete = field(win)
endbox = boxlist[-1]
move(win, pete, endbox)
main()
I have been working on an implementation of a parametric L-system with python. My case is a 2D tree therefore i am using python turtle library. I wrote this simple code for a non-parametric L-system which it works pretty good. Here is my code:
import turtle as T
class LSystem:
def __init__(self, axiom, width, length, angle):
self.axiom = axiom
self.state = axiom
self.width = width
self.length = length
self.angle = angle
self.rules = {}
T.pensize(self.width)
def add_rules(self, *rules):
for arg in rules:
self.rules[arg[0]] = arg[-1]
def single_generation(self):
next_state = ""
for char in self.state:
if char in self.rules:
next_state += self.rules[char]
else:
next_state += char
self.state = next_state
def set_turtle(self, my_tuple):
T.up()
T.goto(my_tuple[0], my_tuple[1])
T.seth(my_tuple[2])
T.down()
def length_function(self, r):
self.length *= r
def width_function(self, q, e):
self.width *= q**e
def draw_turtle(self):
turtle_state = []
# ***************
T.tracer(0, 0)
T.down()
T.ht()
T.speed(0)
T.seth(90)
# ***************
for move in self.state:
if move == "[":
turtle_state.append((T.xcor(), T.ycor(), T.heading(), T.pensize()))
elif move == "]":
xcor, ycor, head, wid = turtle_state.pop()
self.set_turtle((xcor, ycor, head))
self.width = wid
elif move == 'F':
T.fd(self.length)
T.pensize(self.width)
elif move == '+':
T.left(self.angle)
elif move == '-':
T.right(self.angle)
T.getscreen()
T.done()
if __name__ == '__main__':
my_axiom = "A"
my_width = 10
my_length = 60
my_angle = 33.5
LSys = LSystem(my_axiom, my_width, my_length, my_angle)
my_rule = ("A", "F[+A][-A]")
LSys.add_rules(my_rule)
LSys.single_generation()
LSys.single_generation()
LSys.single_generation()
LSys.single_generation()
LSys.single_generation()
LSys.draw_turtle()
The problem is this code is working for a simple non-parametric L-system but i want to decrease diameter and length of my tree in each step. I have wrote two functions for this, length_function and width_functionbut i don't know where or how to use it. Here is my L-system rules and axiom:
A(s,w) ------> F(s,w)[+A(s*r1, w* q^e)][-A(s*r2, w* (1-q)^e]
axiom = "A(s0, w0)"
The values for r1, r2, q, e are known. also the s0 & w0 are known. I don't want to store the values of these parameters in a string format, i want them in a list or array but i don't know how. Here is a picture of the tree i'm trying to draw vs what my code draws:
You correctly store the pensize when you encounter a '[' but you do not reduce it.
Do something like:
if move == "[":
self.width *= 0.95
turtle_state.append((T.xcor(), T.ycor(), T.heading(), self.width))
I've programmed Conways Game of Life in Python and now I'm trying to display the simple data that it gives me as an output in a heat map.
This is my current code:
from Tkinter import *
import matplotlib.pyplot as plt
import time
import numpy as np
import random
size_x = 100
size_y = 10
# create the matrices
cell = [[0 for row in range(0, size_y)] for col in range(0, size_x)]
live = [[0 for row in range(0, size_y)] for col in range(0, size_x)]
temp = [[0 for row in range(0, size_y)] for col in range(0, size_x)]
# process and draw the next frame
def frame():
process()
draw()
root.after(100, frame)
# load the initial data
def load(initial=0.5):
for y in range(0, size_y):
for x in range(0, size_x):
if random.random()<initial: live[x][y] = 1
temp[x][y] = 0
# Applying rules
def process():
for y in range(0, size_y):
for x in range(0, size_x):
lives = live_neighbors(x,y)
if live[x][y] == 1:
if lives < 2 or lives > 3:
temp[x][y] = 0
else:
temp[x][y] = 1
if live[x][y] == 0:
if lives == 3:
temp[x][y] = 1
else:
temp[x][y] = 0
for y in range(0, size_y):
for x in range(0, size_x):
live[x][y] = temp[x][y]
# live = temp
# Count live neighbors
def live_neighbors(a,b):
lives = 0
if live[a][(b+1)%size_y] == 1: lives += 1
if live[a][(b-1)%size_y] == 1: lives += 1
if live[(a+1)%size_x][b] == 1: lives += 1
if live[(a+1)%size_x][(b+1)%size_y] == 1: lives += 1
if live[(a+1)%size_x][(b-1)%size_y] == 1: lives += 1
if live[(a-1)%size_x][b] == 1: lives += 1
if live[(a-1)%size_x][(b+1)%size_y] == 1: lives += 1
if live[(a-1)%size_x][(b-1)%size_y] == 1: lives += 1
return lives
# Draw all cells
def draw():
nLiving = 0
nDead = 0
for y in range(size_y):
for x in range(size_x):
if live[x][y]==0:
canvas.itemconfig(cell[x][y], fill="black")
nDead+=1
if live[x][y]==1:
canvas.itemconfig(cell[x][y], fill="white")
nLiving+=1
print nLiving,nDead
# count cells
def count():
nLiving = 0
nDead = 0
for y in range(size_y):
for x in range(size_x):
if live[x][y]==0:
nDead+=1
if live[x][y]==1:
nLiving+=1
z = nLiving / 10.0
print z,
print "%"
def one_game(initial):
load(initial)
for gen in range(1, 101):
print str(gen) + ":",
count()
process()
def many_games():
numbers = range(1,51)
for initial in numbers:
print initial/100.0
one_game(initial/100.0)
many_games()
#one_game(0.5)
The code for making a normal heat map with given input would be:
fig, ax = plt.subplots(1)
x = np.array( [[11,12,13], [21,22,23], [31,32,33]] )
p = ax.pcolormesh(x)
fig.colorbar(p)
plt.show()
How do I get my data (which in this case would be, the generations, the value which initializes the one_game() function, and nLiving) into an array?
I'm not 100% sure this is what you're intending, but it produced a pretty output heat map :)
def count():
nLiving = 0
nDead = 0
for y in range(size_y):
for x in range(size_x):
if live[x][y]==0:
nDead+=1
if live[x][y]==1:
nLiving+=1
z = nLiving / 10.0
print("nLiving over ten is: ", z,)
print("%")
return nLiving
def one_game(initial):
load(initial)
gen_array = []
for gen in range(1, 101):
print("Gen: ", str(gen) + ":",)
nLiving = count()
process()
gen_array.append(nLiving)
return gen_array
def many_games():
gen_output = []
numbers = range(1,51)
for initial in numbers:
print(initial/100.0)
gen_array = one_game(initial/100.0)
gen_output.append(gen_array)
return gen_output
gen_output = many_games()
#one_game(0.5)
fig, ax = plt.subplots(1)
x = np.array( gen_output )
p = ax.pcolormesh(x)
fig.colorbar(p)
plt.show()
That is just code modified from your count function to the end of the file. Basically you just need to return the output from the functions that you're calling into the right kind of data structures, I think...