Ursina Engine game not working as expected - python

I'm developing a little game with Ursina and for some reason it doesn't show all the elements and ignores an update. The game is about clicking the randomly generated points. This is my code:
menu.py (Main file)
from ursina import *
from point import *
from game import play, play_2
class Menu(Entity):
def __init__(self, **kwargs):
super().__init__(parent = camera.ui, ignore_paused = True)
self.main_menu = Entity(parent = self, enabled = True)
# More menus
Text(text = 'Main Menu', parent = self.main_menu, y=0.4, x=0, origin=(0,0))
def starting(): # Game starts
self.main_menu.disable()
play()
def update(): # The program ignores this update, so the following line isn't executed
play_2()
ButtonList(button_dict = {
'Start':Func(starting),
'Settings':Func(print, 'Settings was clicked')
}, y = 0, parent = self.main_menu)
app = Ursina(fullscreen = True)
menu = Menu()
app.run()
point.py
from ursina import *
class Point(Entity):
def __init__(self, position = (0,0,0), parent = camera, **kwargs):
super().__init__(
parent = parent,
model = 'sphere',
scale = .04,
position = position,
collider = 'sphere'
)
def input(self, key):
global generate
global score
global missed
if key == 'left mouse down' and self.hovered:
score += 1
self.disable()
generate = True
if key == 'left mouse down' and self.hovered == False:
missed += 1
generate = False
score = 0
missed = 0
game.py
from ursina import *
from point import Point, generate, score, missed
from random import uniform
score_text = Text(text = 'h')
missed_text = Text(text = 'h')
points = []
def play(): # Generates all the needed things and an init point
global score_text
global missed_text
camera.position = (0,0,-4)
camera.rotation_x = 10
board = Entity(parent = camera, model = 'quad', scale = 2, color = color.rgb(0,0,0), position = (0,0,3))
score_text.scale = 1
score_text.position = (.66,.43)
missed_text.scale = 1
missed_text.position = (.66,.4)
point = Point(parent = board)
def play_2(): # The actual game starts
global generate
global score
global missed
global score_text
global missed_text
global points
if generate == True:
point = Point(position = (uniform(-.44,.44), uniform(-.265,.265),-.1))
points.append(point)
generate = False
score_text.text = f'Score: {score}'
missed_text.text = f'Missed: {missed}'
The output is this:
In the second image you can see one of the points you need to click, however, it is supposed to appear another one in a random location, but it doesn't. Also, the score and missed clicks texts should appear, but they doesn't.
All help is appreciated.

ursina calls update on every Entity with an update function. So you should assign the function to an Entity.

Related

Window is not displaying in python curses

I'm pretty new to curses in python. I've tried to create race selection menu for my RPG game in python, but I've come to a problem with displaying the window.
Function, that uses class for window creation:
def character_race_scene(player_name):
# Clear window
stdscr.erase()
# Create character panels and making their borders
label = "What race are you?"
stdscr.addstr(hrows - 16, hcols - (len(label) // 2), label)
# Window creation for race select
stdscr.refresh()
race_select = Menu(10, 20, 5, 20)
race_select.win.addstr("Hello")
# adding races to select
#race_select.add_menu_label("Human", "Elf", "Org")
# printing select on the screen
#race_select.print_menu(1, 1, 0, 0, 1)
char_race = True
while char_race:
try:
key = stdscr.getkey()
if key == "z":
game(player_name, "Human", "Warrior")
except:
pass
Window creation class:
class Menu:
def __init__(self, height, width, y, x):
self.height = height
self.width = width
self.y = y
self.x = x
self.highlight = 0
self.menu_arr = []
self.win = curses.newwin(self.height, self.width, self.y, self.x)
#self.max_y_x = self.win.getmaxyx()
#self.max_y = self.max_y_x[0]
#self.max_x = self.max_y_x[1]
#self.hmax_y = self.max_y_x[0]//2
#self.hmax_x = self.max_y_x[1]//2
logging.debug(f"Menu was created.")
def print_menu(self, y, x, y_off=0, x_off=0, spacing=0):
if len(self.menu_arr) > 0:
for index, item in enumerate(self.menu_arr):
self.win.addstr((index+spacing) + y+y_off, x+x_off, item)
def add_menu_label(self, *argv):
for arg in argv:
self.menu_arr += arg
Honestly, don't know the reason, that the window isn't displaying on the screen.
When I open the game, there is only the label shown "What race are you?".
Thank you for any answers!
Edit: Here is the source code of the whole program.
https://github.com/MrEll3n/ASCIItor

TKinter dealing with multiple monitors and image display

I have functioning code but there are a few things which I would like to change about it but don't know how to so thought i'd ask here. My code is as follows:
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#Define the target, source and output arrays. Source has to be completely white otherwise it kills everything
def initialize(x,y):
xarr = np.zeros(x)
yarr = np.zeros(y)
target = np.meshgrid(xarr,yarr)
target = target[0]
source = np.meshgrid(xarr,yarr)
source = source[0]
output = np.meshgrid(xarr,yarr)
output = output[0]
for i in range(x):
for n in range(y):
source[n][i] = 1
return target, source, output
# creates trap between XTrapMin-XTrapMax and YTrapMin-YTrapMax on Array
def trap(xtmi,xtma,xs,ytmi,ytma,ys,array):
for i in range(xs):
if xtmi < i < xtma:
for n in range(ys):
if ytmi < n < ytma:
array[n][i] = 255
return
#Returns the amplitude of a complex number
def Amplitude(x):
if isinstance(x, complex):
return np.sqrt(x.real**2+x.imag**2)
else:
return np.abs(x)
#Returns the phase of a complex number
def Phase(z):
return np.angle(z)
#Main GS algorithm implementation using numpy FFT package
#performs the GS algorithm to obtain a phase distribution for the plane, Source
#such that its Fourier transform would have the amplitude distribution of the plane, Target.
def GS(target,source):
A = np.fft.ifft2(target)
for i in range(5):
B = Amplitude(source) * np.exp(1j * Phase(A))
C = np.fft.fft2(B)
D = Amplitude(target) * np.exp(1j * Phase(C))
A = np.fft.ifft2(D)
output = Phase(A)
return output
#Make array into PIL Image
def mkPIL(array):
im = Image.fromarray(np.uint8(array))
return im
def up():
global ytmi
global ytma
ytmi -= 10
ytma -= 10
return
def down():
global ytmi
global ytma
ytmi += 10
ytma += 10
return
def right():
global xtmi
global xtma
xtmi += 10
xtma += 10
return
def left():
global xtmi
global xtma
xtmi -= 10
xtma -= 10
return
xtmi = 125
xtma = 130
xs = 1024
ytmi = 0
ytma = 5
ys = 768
root = tk.Tk()
root.attributes('-fullscreen', True)
def main():
app = Lower(root)
root.mainloop()
class Lower:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master).pack()
self.displayimg = tk.Button(self.frame, text = 'Display', width = 25, command = self.plot)
self.displayimg.pack()
self.makewidg()
def makewidg(self):
self.fig = plt.figure(figsize=(100,100), frameon=False) #changing figsize doesnt cange the size of the plot display
self.fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
self.fig.tight_layout()
self.ax = self.fig.add_subplot(111)
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
self.canvas.get_tk_widget().pack(expand=True)
self.canvas.figure.tight_layout()
self.canvas.draw()
self.new_window()
def new_window(self):
self.newWindow = tk.Toplevel()
self.app = Display(self.newWindow)
def plot(self):
global xtmi, xtma, xs, ytmi, ytma, ys, i
target,source,output=initialize(xs,ys)
trap(xtmi,xtma,xs,ytmi,ytma,ys,target)
output = GS(target,source)
self.ax.imshow(output, cmap='gray')
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas.draw()
self.ax.clear()
def kill(self):
root.destroy()
class Display:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
self.up = tk.Button(self.frame, text = 'Up', width = 25, command = up)
self.up.pack()
self.down = tk.Button(self.frame, text = 'Down', width = 25, command = down)
self.down.pack()
self.right = tk.Button(self.frame, text = 'Right', width = 25, command = right)
self.right.pack()
self.left = tk.Button(self.frame, text = 'Left', width = 25, command = left)
self.left.pack()
self.kill = tk.Button(self.frame, text = 'Kill', width = 25, command = self.kill)
self.kill.pack()
def kill(self):
root.destroy()
main()
Currently the button displayimg from the class Lower is displayed above the image, is there a way in which I can have the display button on the Display class and still have it manipulate the image on the Lower screen? Also, I intend to display the window opened by Lower on a separate monitor, but can't drag it seeing as it is fullscreen, is there a way around that I can get it on my second monitor?
I try that as such:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = Lower.plot(Lower))
self.displayimg.pack()
But this causes a misreference I think as I get an error code
AttributeError: type object 'Lower' has no attribute 'ax'
Call to Lower.plot
You are using Lower.plot as your button command. It needs one argument, self which must be an instance of Lower - so Lower.plot(Lower) is passing a class where an instance is expected. Instead you need to use the app instance you've made, and call app.plot(). The arguement self is automatically the instance itself, this is fundamental to OOP in python. Calling the method on an instance passes self as the first arg, so it's missing from the call. Calling Lower.plot(...) is calling the method on the class Lower, so there is no instance, and you have to supply your own. I'd avoid calling methods without an instance like this in this situation, and use your app instance.
Command for the display button
Your button creation becomes something like:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = app.plot)
If you need to pass additional args to plot, you need to delay the function call so it happens on click, not on creation of the button. You can use lambda : app.plot("red", 10, whatever) to make a nameless function, taking no arguments, that when called will go on to call app.plot with the given args.
Positioning the window
You can control the position of the app window using wm_geometry:
app.wm_geometry("200x200+100+500")
Will cause the app window to be 200px by 200px, positioned 100px left and 500px down from the origin, and on a windows machine, this is the top left corner of your primary monitor. You can keep the width and height the same and just move the window with eg
app.wm_geometry("+100+500")
You can use more scripting to build the string +{xpos}+{ypos} with whichever values you like to match your desktop layout.

Update multiple oval coordinates on canvas in python using tkinter

I am working on a gui to animate coin flips one at a time continuously.
I have two classes cointoss.py and flipbell.py.
Cointoss class generates the value change for coins and flipbell is used to animate the process.
As if now I have the code working to animate one coin at a time but not all the coins at a time.
When I say all coins this is the logic: first one coin comes down according to the value change, next another one comes down but the first coin value also gets updated accordingly and so on.
I need help how to move forward with what I have tried so far. I have used for loops to animate the process and I was thinking of using recursive method to have the logic part.
Any help with existing code or ideas to move forward would be great.
flipbell.py
from tkinter import Tk, Canvas, Button, W, E
import random
from math import pi, sin, cos
from cointoss import *
class FlipBell(object):
"""
GUI to simulate cointoss.
"""
def __init__(self, wdw, dimension, increment, delay):
"""
Determines the layout of the GUI.
wdw : top level widget, the main window,
dimension : determines the size of the canvas,
increment : step size for a billiard move,
delay : time between updates of canvas.
"""
wdw.title('Coin flips and Bell Curve')
self.dim = dimension # dimension of the canvas
self.inc = increment
self.dly = delay
self.togo = False # state of animation
# initial coordinates of the ball
self.xpt = self.dim/2
self.ypt = 0
self.cnv = Canvas(wdw, width=self.dim,\
height=self.dim, bg='white')
self.cnv.grid(row=0, column=0, columnspan=2)
self.bt0 = Button(wdw, text='start',\
command=self.start)
self.bt0.grid(row=1, column=0, sticky=W+E)
self.bt1 = Button(wdw, text='stop',\
command=self.stop)
self.bt1.grid(row=1, column=1, sticky=W+E)
def map2table(self, pnt):
"""
Keeps the ball on the canvas table.
"""
if pnt < 0:
(quo, rest) = divmod(-pnt, self.dim)
else:
(quo, rest) = divmod(pnt, self.dim)
return rest
def placecoin(self, xpt, ypt):
self.cnv.create_oval(xpt-1, ypt-1, xpt+1, ypt+1,\
width=2, outline='red', fill='red', tags='coin')
def drawball(self):
"""
Draws the ball on the canvas.
"""
xpt = self.map2table(self.xpt)
ypt = self.map2table(self.ypt)
self.cnv.delete('dot')
self.cnv.create_oval(xpt-1, ypt-1, xpt+1, ypt+1,\
width=1, outline='black', fill='red', tags='dot')
def animate(self):
"""
Performs the animation.
"""
self.drawball()
val = []
for k in range(400):
val1 = CoinToss.cointoss(3,k,self.dim//2)
val.append(val1)
points = {}
for i in range(1,401):
points[i] = 0
for i in range(0,400):
for j in range(0,400):
(xpt, ypt) = (self.xpt, self.ypt)
self.xpt = val[i][1][j]
# print("x %d",self.xpt)
self.ypt = ypt + 1
# print("y %d",self.ypt)
self.cnv.after(self.dly)
self.drawball()
self.cnv.update()
#Puts the coin on top each other
if self.ypt == 400:
if points[self.xpt]>=1:
self.placecoin(val[i][1][-1],400-points[self.xpt])
else:
self.placecoin(val[i][1][-1],400)
points[self.xpt]+=3
self.ypt = 0
def start(self):
"""
Starts the animation.
"""
self.togo = True
self.animate()
def stop(self):
"""
Stops the animation.
"""
self.togo = False
def main():
"""
Defines the dimensions of the canvas
and launches the main event loop.
"""
top = Tk()
dimension = 400 # dimension of canvas
increment = 10 # increment for coordinates
delay = 1 # how much sleep before update
num_flips = 3
num_value = dimension//2
FlipBell(top, dimension, increment, delay)
top.mainloop()
if __name__ == "__main__":
main()
cointoss.py
from random import randint
import random
class CoinToss:
coin = 0
def __init__(self, value,num_flip):
# self.id = 1
self.v = value
self.state = 1
self.flip = num_flip
CoinToss.coin += 1
def cointoss(self,coin,value):
print('The ball at the start: ball: %d, state: %d, value: %d' % (coin, self, value))
value_change = value
coin_change = []
for i in range(1,400+1):
value = value_change
value_change = CoinToss.flip(value)
print('after flip %d, ball: %d, state: %d, value: %d' % (i,coin, i, value_change))
coin_change.append(value_change)
return([coin,coin_change])
def flip(self):
rand_value = randint(0, 1)
if rand_value == 1:
self +=1
else:
self -=1
return self
You have named both a function and a variable "flip" in CoinToss which is confusing. Also, you use the "tags" keyword and it should be "tag". There is more than one way to code this. The code below is not a complete solution but a simple example that shows how to use the CoinToss class to create and move an individual ball (doesn't check for move off of canvas). The FlipBell class stores each CoinToss instance in a list and calls the "flip" function for each class each time a ball is created. You could also use "after" within the CoinToss class to have the flip function call itself repeatedly.
from tkinter import *
from random import randint
class FlipBell(object):
"""
GUI to simulate cointoss.
"""
def __init__(self, wdw, dimension, delay):
"""
Determines the layout of the GUI.
wdw : top level widget, the main window,
dimension : determines the size of the canvas,
increment : step size for a billiard move,
delay : time between updates of canvas.
"""
wdw.title('Coin flips and Bell Curve')
self.cnv = Canvas(wdw, width=dimension,
height=dimension, bg='white')
self.cnv.grid()
self.ct_instances=[]
self.colors=["blue", "red", "yellow", "gray", "green"]
self.delay=delay
self.offset=0
self.create_next()
def create_next(self):
""" create one ball for each color in self.colors
and call each existing ball's flip function to
move it a random amount
"""
x=5
y=5
incr=10*self.offset
CT=CoinToss(self.cnv, x+incr, y+incr, self.colors[self.offset])
##save each CoinToss (ball) instance in a list
self.ct_instances.append(CT)
self.offset += 1
## call flip (move ball) for each instance
for instance in self.ct_instances:
instance.flip()
if self.offset < len(self.colors):
self.cnv.after(self.delay, self.create_next)
class CoinToss:
def __init__(self, canvas, start_x, start_y, color):
self.cnv=canvas
self.cointoss(start_x, start_y, color)
def cointoss(self, start_x, start_y, color):
self.this_ball=self.cnv.create_oval(start_x-5, start_y-5, start_x+5, start_y+5,
outline='black', fill=color, tag="dot")
def flip(self):
""" move the ball created for this class instance by a random amount
"""
rand_value = randint(10, 50)
self.cnv.move(self.this_ball, rand_value, rand_value)
if __name__ == "__main__":
top = Tk()
dimension = 400 # dimension of canvas
delay = 500 # how much sleep before update --> 1/2 second
num_flips = 3
FP=FlipBell(top, dimension, delay)
top.mainloop()

Make intro screen with pyglet

I am trying to make a simple game with pyglet, and it has to include an intro screen. Unfortunately, it's been proving more difficult than I expected.
The following code is a simpler version of what I am trying to do.
import pyglet
from game import intro
game_window = pyglet.window.Window(800, 600)
intro.play(game_window)
#game_window.event
def on_draw():
game_window.clear()
main_batch.draw()
def update(dt):
running = True
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1/120.0)
main_batch = pyglet.graphics.Batch()
score_label = pyglet.text.Label(text = 'RUNNING GAME', x = 400, y = 200, batch=main_batch)
pyglet.app.run()
Where game/intro.py has the following written in it:
import pyglet
from time import sleep
def play(game_window):
game_window.clear()
studio = pyglet.text.Label('foo studios', font_size=36, font_name='Arial', x=400, y=300)
studio.draw()
sleep(5)
This opens a window (the intro window) and waits 5 seconds, after which the message "RUNNING GAME" appears, but the "foo studios" message does not appear.
Clearly I am doing something wrong.
I am not very experienced with pyglet, but I managed to get the game running (needs a bit of tweaking, but it's essentially done). All I need left is the intro screen.
If anyone knows a good way of doing an intro screen (just with text, I don't need any animations of music for now), I would be very grateful.
You're better off creating classes based on for instance pyglet.sprite.Sprite and using those objects as "windows" or "screens".
Feels like i'm pasting this code everywhere but use this, and in "def render()` put the different "scenarios"/"windows" you'd wish to be rendered at the time.
import pyglet
from time import time, sleep
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.frames = 0
self.framerate = pyglet.text.Label(text='Unknown', font_name='Verdana', font_size=8, x=10, y=10, color=(255,255,255,255))
self.last = time()
self.alive = 1
self.refreshrate = refreshrate
def on_draw(self):
self.render()
def render(self):
self.clear()
if time() - self.last >= 1:
self.framerate.text = str(self.frames)
self.frames = 0
self.last = time()
else:
self.frames += 1
self.framerate.draw()
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events() # <-- This is the event queue
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()
What does it is the fact that you have a rendering function that clears and flips the entire graphical memory X times per second and you descide which objects are included in that render perior in the render function.
Try it out and see if it helps.
Here is a example using the above example, it consists of 3 things:
* A main window
* A Intro screen
* A Menu screen
You can ignore class Spr() and def convert_hashColor_to_RGBA(), these are mere helper functions to avoid repetative code further down.
I will also go ahead and mark the important bits that actually do things, the rest are just initation-code or positioning things.
import pyglet
from time import time, sleep
__WIDTH__ = 800
__HEIGHT__ = 600
def convert_hashColor_to_RGBA(color):
if '#' in color:
c = color.lstrip("#")
c = max(6-len(c),0)*"0" + c
r = int(c[:2], 16)
g = int(c[2:4], 16)
b = int(c[4:], 16)
color = (r,g,b,255)
return color
class Spr(pyglet.sprite.Sprite):
def __init__(self, texture=None, width=__WIDTH__, height=__HEIGHT__, color='#000000', x=0, y=0):
if texture is None:
self.texture = pyglet.image.SolidColorImagePattern(convert_hashColor_to_RGBA(color)).create_image(width,height)
else:
self.texture = texture
super(Spr, self).__init__(self.texture)
## Normally, objects in graphics have their anchor in the bottom left corner.
## This means that all X and Y cordinates relate to the bottom left corner of
## your object as positioned from the bottom left corner of your application-screen.
##
## We can override this and move the anchor to the WIDTH/2 (aka center of the image).
## And since Spr is a class only ment for generating a background-image to your "intro screen" etc
## This only affects this class aka the background, so the background gets positioned by it's center.
self.image.anchor_x = self.image.width / 2
self.image.anchor_y = self.image.height / 2
## And this sets the position.
self.x = x
self.y = y
def _draw(self):
self.draw()
## IntoScreen is a class that inherits a background, the background is Spr (our custom background-image class)
## IntoScreen contains 1 label, and it will change it's text after 2 seconds of being shown.
## That's all it does.
class IntroScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(IntroScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.intro_text = pyglet.text.Label('Running game', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y, multiline=False, width=width, height=height, color=(100, 100, 100, 255), anchor_x='center')
self.has_been_visible_since = time()
def _draw(self): # <-- Important, this is the function that is called from the main window.render() function. The built-in rendering function of pyglet is called .draw() so we create a manual one that's called _draw() that in turn does stuff + calls draw(). This is just so we can add on to the functionality of Pyglet.
self.draw()
self.intro_text.draw()
if time() - 2 > self.has_been_visible_since:
self.intro_text.text = 'foo studios'
## Then we have a MenuScreen (with a red background)
## Note that the RED color comes not from this class because the default is black #000000
## the color is set when calling/instanciating this class further down.
##
## But all this does, is show a "menu" (aka a text saying it's the menu..)
class MenuScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(MenuScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.screen_text = pyglet.text.Label('Main menu screen', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y+height/2-20, multiline=False, width=300, height=height, color=(100, 100, 100, 255), anchor_x='center')
def _draw(self):
self.draw()
self.screen_text.draw()
## This is the actual window, the game, the glory universe that is graphics.
## It will be blank, so you need to set up what should be visible when and where.
##
## I've creates two classes which can act as "screens" (intro, game, menu etc)
## And we'll initate the Window class with the IntroScreen() and show that for a
## total of 5 seconds, after 5 seconds we will swap it out for a MenuScreeen().
##
## All this magic is done in __init__() and render(). All the other functions are basically
## just "there" and executes black magic for your convencience.
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.alive = 1
self.refreshrate = refreshrate
self.currentScreen = IntroScreen(x=320, y=__HEIGHT__/2, width=50) # <-- Important
self.screen_has_been_shown_since = time()
def on_draw(self):
self.render()
def on_key_down(self, symbol, mod):
print('Keyboard down:', symbol) # <-- Important
def render(self):
self.clear()
if time() - 5 > self.screen_has_been_shown_since and type(self.currentScreen) is not MenuScreen: # <-- Important
self.currentScreen = MenuScreen(x=320, y=__HEIGHT__-210, color='#FF0000') # <-- Important, here we switch screen (after 5 seconds)
self.currentScreen._draw() # <-- Important, draws the current screen
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events()
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()

Connect Four game error with self (AttributeError: type object 'GUI' has no attribute 'canvas')

I'm making a game of Connect Four and I am trying to get so that when you click on a button it creates an image of a red/black circle, depending on the turn, in specific coordinates using tkinter canvas. I created a logic and GUI class, but I can't get the logic class to see the canvas because for some reason it's saying that canvas isn't in GUI.
AttributeError: type object 'GUI' has no attribute 'canvas'
Logic class:
class ConnectFour:
def __init__(self):
self.__grid = []
self.__value = ""
self.__turn = ""
#self.canvas = GUI.canvas
def buttonOneCallback(self):
self.__value = 0
self.getColumnNumber()
self.runGame()
def findLowest(self, grid, columnNum):
board = grid
for y in range(BOARD_Y-1, -1, -1):
if board[y][columnNum] == 0:
return y
def changeValue(self, grid, row, turn, columnNum):
grid[row][columnNum] = turn
return turn
def getPieceColor(self):
color = ""
if self.__turn == PLAYER1:
color = "self.__redImage"
if self.__turn == PLAYER2:
color = "self.__blackImage"
return color
def runGame(self):
#from GUI import GUI
#gui = GUI()
turn = self.setPlayerOrder()
grid = self.makeGrid()
gameInPlay = True
if gameInPlay:
turn = self.getTurnNumber()
if turn == PLAYER1:
#columnNum = int(input("Pick a column: "))
columnNum = self.getColumnNumber()
#print(grid)
#print(columnNum)
row = self.getSpace(grid, columnNum)
self.changeValue(grid, row, PLAYER1, columnNum)
#print(grid)
GUI.placePiece(GUI.canvas,columnNum, row)
if self.isWinner(grid, PLAYER1):
gameInPlay = False
result = messagebox.askyesno("YOU WON! Play again?")
if result == True:
gameInPlay = True
grid = self.makeGrid()
turn = PLAYER2
#print(grid)
elif turn == PLAYER2:
#columnNum = int(input("Pick a column: "))
columnNum = self.getColumnNumber()
#print(columnNum)
row = self.getSpace(grid, columnNum)
self.changeValue(grid, row, PLAYER2, columnNum)
GUI.placePiece(GUI.canvas,columnNum, row)
if self.isWinner(grid, PLAYER2):
gameInPlay = False
result = messagebox.askyesno("YOU WON! Play again?")
if result == True:
gameInPlay = True
grid = self.makeGrid()
turn = PLAYER1
#print(grid)
if self.isBoardFull(grid):
gameInPlay = False
result = messagebox.askyesno("It's a tie! Play again?")
if result == True:
gameInPlay = True
board = self.makeGrid()
GUI class:
class GUI:
def __init__(self):
#Created the main window and sets the window to a certain length/height
self.__win = Tk()
self.__win.title("Connect Four")
#self.__win.geometry("625x600+30+30")
#self.__win.configure(background='white')
#self.__win.grid(rowspan=7)
#Created an instance of the ConnectFour class
game = ConnectFour()
#Buttons to choose where to drop the chip
self.redImage = PhotoImage(file = "red.gif")
self.blackImage = PhotoImage(file = "black.gif")
self.__buttonOne = Button(self.__topFrame, width = 10, command = game.buttonOneCallback)
#self.__redButton.grid(row=0, column=0)
#self.__blackButton.grid(row=1, column=0)
self.canvas = Canvas(self.__win, height = 467, width = 725)
self.canvas.pack()
#Pieces on board
#self.__canvas.create_image(116, 430, image = self.__redImage)
#self.__canvas.create_image(194, 352, image = self.__blackImage)
self.__win.mainloop()
def placePiece(canvas,columnNum, row):
game = ConnectFour()
placementY = (468/6) * (columnNum+1)
placementX = (546/7) * (row+1)
color = game.getPieceColor()
#print(color)
#print(self)
canvas.create_image(placementX, placementY, image = self.redImage)
GUI()
Thank you!
You are creating the ConnectFour object before you are setting the canvas attribute in your GUI class. You need to make sure you've created all the dependencies before creating the ConnectFour instance.

Categories

Resources