Python Canvas bbox returns None - python

so i am making a game (Minecraft 2D (scuffed edition)) and if you are underground in a cave, i want the background to be dark, not that you can see the sun if you are in a cave, so what i do is i want to check for the lowest grass block on screen (witch is ground level)
and then place the cave background atthat location, but when i trie to get the bbox or the coords of a block object on the canvas, it just gives me None.
So i have this now:
(the camera function gets run every time the player moves)
def camera(self):
for i in self.block_sprites:
self.canvas.delete(i.get_image())
for i in self.sprites:
self.canvas.delete(i)
self.draw_current_pos()
self.draw_background()
def add_block_sprite(self, x, y, block_type):
self.current_sprite = Block(x, y, self, block_type)
self.block_sprites.append(self.current_sprite)
def draw_current_pos(self):
self.sprites = []
self.block_sprites = []
for x in range(0, 20):
for y in range(0, 21):
if self.world.world[self.posx+x][self.posy+y] == 0 :
continue
else:
self.add_block_sprite(x, y, self.world.world[self.posx + x][self.posy + y])
def draw_background(self):
grass_y = 1000
test_for_grass = False
for block in self.block_sprites:
if block.get_type() == 1:
print(self.canvas.bbox(block)) #prints "None"
if self.canvas.bbox(block)[1] > grass_y: ####error here####
grass_y = self.canvas.coords(block)[1]
test_for_grass = True
if not test_for_grass and self.posy < 30:
grass_y = 1000 - grass_y
cave = self.canvas.create_image(0, grass_y, image=self.cavebg, anchor="nw")
self.sprites.append(cave)
Do you know what the problem is?
I hope you can help!
(if this it to little code for the problem tell me)

Related

(Pygame) What is wrong with this function?

So, i have been working on a snake game made in Pygame. So far, everything is fine, except for one problem: When the snake eats the fruit, the fruit (that randomly spawns) sometimes appears inside the snake's body. So, to avoid this, i made this function:
def random_fruit(body_pos):
global general_fruit_x, general_fruit_y # Fruit rectangle coordinates
while True:
general_fruit_x = randrange(window[0] // snake.w) * snake.w # (Snake is a pygame.Rect)
general_fruit_y = randrange(window[1] // snake.h) * snake.h
if len(list(filter(lambda z: body_pos == (general_fruit_x, general_fruit_y), body_pos))) > 0:
continue # If the spawning position of the fruit is the same as the snake's body, we continue the loop
else:
break # If not, we are done
set_obj_coordinates(general_fruit, general_fruit_x, general_fruit_y) # set fruit random position
And implemented it in the main game loop:
if fruit_eated:
random_ind1 = random_ind2
snake_len += 1
apple_sound.play()
random_fruit(snake_pos) # snake_pos is a list of tuples with all snake's body coordinates
for m in range(3):
snake_imgs[random_ind1][m] = img("snake_" + snake_colors[random_ind1] + str(m + 1)) # Reset snake image
random_ind2 = randint(0, 3)
if x_move < 0:
rotate_imgs(90, random_ind1)
if x_move > 0:
rotate_imgs(-90, random_ind1)
if y_move > 0:
rotate_imgs(180, random_ind1)
if y_move < 0:
pass
But it seems that the random_fruit function ignores the condition of the snake's body.
Here is the complete code: https://github.com/n4tm/PySnake/tree/main/snake
You have to check if any position of the body is equal to the new random position of the fruit:
if len(list(filter(lambda z: body_pos == (general_fruit_x, general_fruit_y), body_pos))) > 0:`
if any(pos == (general_fruit_x, general_fruit_y) for pos in body_pos):
random_fruit function:
def random_fruit(body_pos):
global general_fruit_x, general_fruit_y
while True:
general_fruit_x = randrange(window[0] // snake.w) * snake.w
general_fruit_y = randrange(window[1] // snake.h) * snake.h
if not any(pos == (general_fruit_x, general_fruit_y) for pos in body_pos):
break
set_obj_coordinates(general_fruit, general_fruit_x, general_fruit_y)

Cant figure out how to kill a checker when it is jumped over

I am trying to make a game of checkers in pygame, and can't think of a way to do the logic for killing a checker once I jump over it. If you need any more information let me know. Here is the function in the checker sprite class:
# Determine if move is valid, and if so snap to the grid
# and tell the game logic that there is a checker there
def snap_to_grid(self):
global turn
x, y = self.rect.topleft
coord_x = round(x / square_size)
coord_y = round(y / square_size)
new_x = coord_x * square_size + 5
new_y = coord_y * square_size + 5
# get all valid moves from the starting position
valid_moves = self.valid_moves()
# check if it is valid, or if it is going back to the same spot. If either is true,
# then reset. If not, move to that spot.
if [new_x, new_y] == self.location or not [coord_x, coord_y] in valid_moves:
self.set_pos(self.location, 'topleft')
else:
# move to new position
self.set_pos((new_x, new_y), 'topleft')
# tell game data that the checker moved from the old square
board[self.coords[1]][self.coords[0]] = None
# the next few lines are to determine if the checker jumped or not
old_x, old_y = self.coords
self.location = [new_x, new_y]
self.coords = [int(self.location[0]//square_size), int(self.location[1]//square_size)]
distance = abs((old_x - self.coords[0])^2 + (old_y - self.coords[1])*2)
# check if checker jumped
if not distance == 1 and not distance == 5:
print("jumped")
"""
this code here should trigger when a jump happens.
I need to kill the checker it jumped over
"""
# tell game data the checker moved to the new square
board[self.coords[1]][self.coords[0]] = self
# set to be the next turn
if self.color == white:
turn = dark_gray
else:
turn = white
ok I think I figured it out, if there is a better way let me know (the distances in the if statements are the output if it is a jump in a specific direction):
old_x, old_y = self.coords
self.location = [new_x, new_y]
self.coords = [int(self.location[0]//square_size), int(self.location[1]//square_size)]
distance = (old_x - self.coords[0])^2 + (old_y - self.coords[1])*2
# check if checker jumped top right
if distance == -8:
board[self.coords[1]+1][self.coords[0]-1].kill()
# check if checker jumped top left
elif distance == 4:
board[self.coords[1]+1][self.coords[0]+1].kill()
# check if checker jumped bottom right
elif distance == 0:
board[self.coords[1]-1][self.coords[0]-1].kill()
# check if checker jumped bottom left
elif distance == -4:
board[self.coords[1]-1][self.coords[0]+1].kill()

Global variable set and then reset in Python Canvas

Good afternoon,
I'm building out (suppose to be) a relatively simple q & a experiment for a psychology experiment. I'm using Pythons canvas for drawing and painting but have hit a bit of brick wall and a classic update scenario, I think. Here's the code:
# Replace with 60000 for 1 minute a question
screen_timeout = 10000
start_time = clock.time()
# Create a canvas, mouse & keyboard object
canvas = canvas()
mouse = mouse()
kb = keyboard(timeout=0)
question = '1) Which two chemicals are discussed?'
answer = ''
show_circle = False
global circle_clicked
circle_clicked = False
def draw_question_and_answer(c, a):
c.text('%s<br />(Just start typing; press enter to submit)<br /><br />%s' % (question, a))
def draw_mouse(c, (x, y)):
c.fixdot(x, y)
def draw_circle(c):
c['circle'] = Circle(0, 0, 50, fill=True, color='red')
def paint(c, a, s, (x, y)):
c.clear()
# show_circle_every(s, 2500)
# NOTE Drawing order matters here
if s:
draw_question_and_answer(c, a)
draw_circle(c)
draw_mouse(c, (x, y))
if (x, y) in c['circle']:
circle_clicked = True
else:
draw_question_and_answer(c, a)
c.show()
def game_loop(c, m, a, s):
while True:
if clock.time() - start_time >= screen_timeout:
break
# if clock.time() - start_time >= 2500 and s == False:
# s = True
response, timestamp_kb = kb.get_key()
(x, y), timestamp_m = m.get_pos()
# TODO Extrapolate to function
if s == False:
if response == 'return':
var.gq1 = a
log.write_vars()
break
if response != None and response != 'right shift' and response != 'left shift':
if response == 'space':
a += ' '
elif response == 'backspace':
a = a[:-1]
else:
a += response
paint(c, a, s, (x, y))
# If the user enters the circle it should disappear
print circle_clicked
if clock.time() - start_time >= 2500 and circle_clicked == False:
s = True
game_loop(canvas, mouse, answer, show_circle)
What I'm trying to do here is show a red circle every 2.5 seconds and keep the circle there until the users mouse enters the boundary of the circle. In these lines here:
if clock.time() - start_time >= 2500 and circle_clicked == False:
s = True
I'm setting the s variable (show) to True to show the circle which works. And in this line:
if (x, y) in c['circle']:
circle_clicked = True
if the user enters the circle I'm setting clicked to true. But if I print the values I can see circle_clicked changing from True to back False - How come? Does the loop get it's own version of circle_clicked? If so how?
What am I doing wrong here as I think it's quite a simple problem? Coming from a purely Javascript background also complicates things as I'm trying to learn Python to do it.
Thanks
Change your paint function to this
def paint(c, a, s, (x, y)):
global circle_clicked
c.clear()
# show_circle_every(s, 2500)
# NOTE Drawing order matters here
if s:
draw_question_and_answer(c, a)
draw_circle(c)
draw_mouse(c, (x, y))
if (x, y) in c['circle']:
circle_clicked = True
else:
draw_question_and_answer(c, a)
c.show()

How do I call out a class object?

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.

How can I change this to use a q table for reinforcement learning

I am working on learning q-tables and ran through a simple version which only used a 1-dimensional array to move forward and backward. now I am trying 4 direction movement and got stuck on controlling the person.
I got the random movement down now and it will eventually find the goal. but I want it to learn how to get to the goal instead of randomly stumbling on it. So I would appreciate any advice on adding a qlearning into this code. Thank you.
Here is my full code as it stupid simple right now.
import numpy as np
import random
import math
world = np.zeros((5,5))
print(world)
# Make sure that it can never be 0 i.e the start point
goal_x = random.randint(1,4)
goal_y = random.randint(1,4)
goal = (goal_x, goal_y)
print(goal)
world[goal] = 1
print(world)
LEFT = 0
RIGHT = 1
UP = 2
DOWN = 3
map_range_min = 0
map_range_max = 5
class Agent:
def __init__(self, current_position, my_goal, world):
self.current_position = current_position
self.last_postion = current_position
self.visited_positions = []
self.goal = my_goal
self.last_reward = 0
self.totalReward = 0
self.q_table = world
# Update the totoal reward by the reward
def updateReward(self, extra_reward):
# This will either increase or decrese the total reward for the episode
x = (self.goal[0] - self.current_position[0]) **2
y = (self.goal[1] - self.current_position[1]) **2
dist = math.sqrt(x + y)
complet_reward = dist + extra_reward
self.totalReward += complet_reward
def validate_move(self):
valid_move_set = []
# Check for x ranges
if map_range_min < self.current_position[0] < map_range_max:
valid_move_set.append(LEFT)
valid_move_set.append(RIGHT)
elif map_range_min == self.current_position[0]:
valid_move_set.append(RIGHT)
else:
valid_move_set.append(LEFT)
# Check for Y ranges
if map_range_min < self.current_position[1] < map_range_max:
valid_move_set.append(UP)
valid_move_set.append(DOWN)
elif map_range_min == self.current_position[1]:
valid_move_set.append(DOWN)
else:
valid_move_set.append(UP)
return valid_move_set
# Make the agent move
def move_right(self):
self.last_postion = self.current_position
x = self.current_position[0]
x += 1
y = self.current_position[1]
return (x, y)
def move_left(self):
self.last_postion = self.current_position
x = self.current_position[0]
x -= 1
y = self.current_position[1]
return (x, y)
def move_down(self):
self.last_postion = self.current_position
x = self.current_position[0]
y = self.current_position[1]
y += 1
return (x, y)
def move_up(self):
self.last_postion = self.current_position
x = self.current_position[0]
y = self.current_position[1]
y -= 1
return (x, y)
def move_agent(self):
move_set = self.validate_move()
randChoice = random.randint(0, len(move_set)-1)
move = move_set[randChoice]
if move == UP:
return self.move_up()
elif move == DOWN:
return self.move_down()
elif move == RIGHT:
return self.move_right()
else:
return self.move_left()
# Update the rewards
# Return True to kill the episode
def checkPosition(self):
if self.current_position == self.goal:
print("Found Goal")
self.updateReward(10)
return False
else:
#Chose new direction
self.current_position = self.move_agent()
self.visited_positions.append(self.current_position)
# Currently get nothing for not reaching the goal
self.updateReward(0)
return True
gus = Agent((0, 0) , goal)
play = gus.checkPosition()
while play:
play = gus.checkPosition()
print(gus.totalReward)
I have a few suggestions based on your code example:
separate the environment from the agent. The environment needs to have a method of the form new_state, reward = env.step(old_state, action). This method is saying how an action transforms your old state into a new state. It's a good idea to encode your states and actions as simple integers. I strongly recommend setting up unit tests for this method.
the agent then needs to have an equivalent method action = agent.policy(state, reward). As a first pass, you should manually code an agent that does what you think is right. e.g., it might just try to head towards the goal location.
consider the issue of whether the state representation is Markovian. If you could do better at the problem if you had a memory of all the past states you visited, then the state doesn't have the Markov property. Preferably, the state representation should be compact (the smallest set that is still Markovian).
once this structure is set-up, you can then think about actually learning a Q table. One possible method (that is easy to understand but not necessarily that efficient) is Monte Carlo with either exploring starts or epsilon-soft greedy. A good RL book should give pseudocode for either variant.
When you are feeling confident, head to openai gym https://www.gymlibrary.dev/ for some more detailed class structures. There are some hints about creating your own environments here: https://www.gymlibrary.dev/content/environment_creation/

Categories