Related
I can't find a source on how to add play again for my python code. I am currently using a snake game that i found on YouTube but I honestly am having trouble figuring this out.
github.com/Amfuhr/Snake-game.git
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2,
font=('consolas',70), text="GAME OVER", fill="red", tag="gameover")
The full script:
#The tkinter package (“Tk interface”) is the standard Python interface to the Tcl/Tk GUI toolkit.
#Tkinter are available on most Unix platforms, including macOS, as well as on Windows systems.
from tkinter import *
import random
#box size, game speed, and color schemes
#classes for the game settings
GAME_WIDTH = 700
GAME_HEIGHT = 700
SPEED = 400
#Snake and food size
SPACE_SIZE = 50
BODY_PARTS = 3
#class for snake and food
#RGB for the color of the backgrund, snake, and food
SNAKE_COLOR = "white"
FOOD_COLOR = "blue"
BACKGROUND_COLOR = "#000000"
class Snake:
#set the body size of the snake, a list of square graphics
def __init__(self):
self.body_size = BODY_PARTS
self.coordinates = []
self.squares = []
#list of coordinates
for i in range(0, BODY_PARTS):
self.coordinates.append([0, 0])
for x, y in self.coordinates:
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR, tag="snake")
self.squares.append(square)
class Food:
def __init__(self):
#food is set into random location using the random module
#based on orientation. We use fill to set the food color
x = random.randint(0, (GAME_WIDTH / SPACE_SIZE)-1) * SPACE_SIZE
y = random.randint(0, (GAME_HEIGHT / SPACE_SIZE) - 1) * SPACE_SIZE
self.coordinates = [x, y]
canvas.create_oval(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=FOOD_COLOR, tag="food")
def next_turn(snake, food):
#head of the snake and the direction of the snake movements
x, y = snake.coordinates[0]
if direction == "up":
y -= SPACE_SIZE
elif direction == "down":
y += SPACE_SIZE
elif direction == "left":
x -= SPACE_SIZE
elif direction == "right":
x += SPACE_SIZE
snake.coordinates.insert(0, (x, y))
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR)
snake.squares.insert(0, square)
if x == food.coordinates[0] and y == food.coordinates[1]:
global score
score += 1
label.config(text="Score:{}".format(score))
canvas.delete("food")
food = Food()
else:
del snake.coordinates[-1]
canvas.delete(snake.squares[-1])
del snake.squares[-1]
if check_collisions(snake):
game_over()
else:
window.after(SPEED, next_turn, snake, food)
def change_direction(new_direction):
global direction
if new_direction == 'left':
if direction != 'right':
direction = new_direction
elif new_direction == 'right':
if direction != 'left':
direction = new_direction
elif new_direction == 'up':
if direction != 'down':
direction = new_direction
elif new_direction == 'down':
if direction != 'up':
direction = new_direction
def check_collisions(snake):
x, y = snake.coordinates[0]
if x < 0 or x >= GAME_WIDTH:
return True
elif y < 0 or y >= GAME_HEIGHT:
return True
for body_part in snake.coordinates[1:]:
if x == body_part[0] and y == body_part[1]:
return True
return False
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2,
font=('consolas',70), text="GAME OVER", fill="red", tag="gameover")
#Window sizing so it does not game
window = Tk()
window.title("Snake game")
window.resizable(False, False)
score = 0
direction = 'down'
#this is to show the score in a specific font size
label = Label(window, text="Score:{}".format(score), font=('consolas', 40))
label.pack()
#canvas sets the background and opens the window
canvas = Canvas(window, bg=BACKGROUND_COLOR, height=GAME_HEIGHT, width=GAME_WIDTH)
canvas.pack()
window.update()
window_width = window.winfo_width()
window_height = window.winfo_height()
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
x = int((screen_width/2) - (window_width/2))
y = int((screen_height/2) - (window_height/2))
# Geometry method is used to set the dimensions of the
# Tkinter window and is used to set the position of the main
# window on the user’s desktop.
window.geometry(f"{window_width}x{window_height}+{x}+{y}")
window.bind('<Left>', lambda event: change_direction('left'))
window.bind('<Right>', lambda event: change_direction('right'))
window.bind('<Up>', lambda event: change_direction('up'))
window.bind('<Down>', lambda event: change_direction('down'))
snake = Snake()
food = Food()
next_turn(snake, food)
window.mainloop()
Think about it this way. Your app works upon running the first time around, and you just want to repeat, that behavior, (aka run that portion of the code again), once the initial game is over without having to restart the app.
This is precisely what functions are for.
The portion of your code that executes the game is just the last three lines of your script.
snake = Snake()
food = Food()
next_turn(snake, food)
So what you can do is stick those 3 lines into a function, and put the function call just before the mainloop call where the lines used to be.
def start_game(*arg):
canvas.delete(ALL)
snake = Snake()
food = Food()
next_turn()
start_game()
window.mainloop()
So now in order to restart the game, all you need to do is call the start_game function. Adding the canvas.delete(ALL) ensures that the "GAME OVER" message is removed before the new game starts.
Now all you need is a trigger. For this you can add a double-click signal on the canvas so that when it says "GAME OVER" all you need to do is double-click on the window and the new game starts. You can even add an instruction for the user.
To do this just add a window.bind call for the double-click action gesture:
window.bind('<Double-Button-1>', start_game)
Then to add the note to your "Game Over" message in your game_over function:
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2,
font=('consolas',70), text="GAME OVER",
fill="red", tag="gameover")
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/1.8,
font=('consolas',20), text="double click for new game",
fill='green', tag='newgame')
And that should do it. This solution does create a new issue though, since there are no conditions on the new event binder, double clicking the screen will start a new game even if the current game is still ongoing.
I will leave this for you to try to solve. Or if you prefer you can use a different trigger.
I'm making a simple calculator using tkinter. I have managed to get everything to work except for my backsapce and clear buttons.The error message I get is: 'TypeError: backspace() takes 0 positional arguments but 1 was given' for backspace and 'TypeError: clear() takes 0 positional arguments but 1 was given' for clear. I have looked around online tutorials but most of the tutorial I've found don't seem to have this problem.
from tkinter import *
root = Tk()
root.geometry("275x300")
root.title("Calculator")
def calculatorTitle():
labelTitle=Label(root, text="Calculator", bg = "black", fg = "white")
labelTitle.config(font = ("Verdana", 12, "bold"))
labelTitle.grid(row=0, column=0, columnspan=4, padx=55, pady=5)
e=Entry(root,width=20,font="Arial 12",justify='right', bg='yellow', fg = 'blue')
e.grid(row=1,column=0,columnspan=4, pady = 5)
def addEntry(ch):
e.insert(20, ch)
def CalculateEntry(expression):
expression = e.get()
expression = eval(expression)
e.delete(0, END)
e.insert(20, expression)
def clear():
e.delete(0, END)
return
def backspace():
current = e.get()
lenght = len(current)-1
e.delete(lenght, END)
def calculatorBoard():
b1=Button(root,text='1',width=5,command=lambda:addEntry(1))
b2=Button(root,text='2',width=5,command=lambda:addEntry(2))
b3=Button(root,text='3',width=5,command=lambda:addEntry(3))
bAddition=Button(root,text='+',width=5,command=lambda:addEntry('+'))
b4=Button(root,text='1',width=5,command=lambda:addEntry(4))
b5=Button(root,text='5',width=5,command=lambda:addEntry(5))
b6=Button(root,text='6',width=5,command=lambda:addEntry(6))
bSubtract=Button(root,text='-',width=5,command=lambda:addEntry('-'))
b7=Button(root,text='7',width=5,command=lambda:addEntry(7))
b8=Button(root,text='8',width=5,command=lambda:addEntry(8))
b9=Button(root,text='9',width=5,command=lambda:addEntry(9))
bMultiply=Button(root,text='*',width=5,command=lambda:addEntry('*'))
bClear=Button(root,text='CE',width=5,command=lambda:clear('CE'))
b0=Button(root,text='0',width=5,command=lambda:addEntry(0))
bBackspace=Button(root,text='<-',width=5,command=lambda:backspace('<-'))
bDivide=Button(root,text='/',width=5,command=lambda:addEntry('/'))
bEqual=Button(root,text='=',width=27,command=lambda:CalculateEntry('='))
b1.grid(row=2,column=0,pady = 10 )
b2.grid(row=2,column=1,pady = 10 )
b3.grid(row=2,column=2,pady = 10 )
bAddition.grid(row=2,column=3,pady = 10 )
b4.grid(row=3,column=0,pady = 10 )
b5.grid(row=3,column=1,pady = 10 )
b6.grid(row=3,column=2,pady = 10 )
bSubtract.grid(row=3,column=3,pady = 10 )
b7.grid(row=4,column=0,pady = 10 )
b8.grid(row=4,column=1,pady = 10 )
b9.grid(row=4,column=2,pady = 10 )
bMultiply.grid(row=4,column=3,pady = 10 )
bClear.grid(row=5,column=0,pady = 10 )
b0.grid(row=5,column=1,pady = 10 )
bBackspace.grid(row=5,column=2,pady = 10 )
bDivide.grid(row=5,column=3,pady = 10 )
bEqual.grid(row= 6, column= 0, columnspan= 4, pady= 10)
calculatorTitle()
calculatorBoard()
root.mainloop()
Edit: This part if an assignment where I have to follow some instructions.
def clear() and def backspace(): you define the functions with zero arguments.
clear('CE') and backspace('<-'): You call the functions with one argument
To fix the problem, either redefine the functions to take an argument,
def clear(arg):
# do whatever
def backspace(arg):
# do whatever
or change the call to not pass any.
bClear=Button(root,text='CE',width=5,command=clear)
bBackspace=Button(root,text='<-',width=5,command=backspace)
Be sure not to do both -- you'll run into the opposite problem.
I've got a weather display for a Pi which uses pygame to display the data
The issue I have is when the internet dies for any reason there is no update and the display shows blank data
What I'd like to do is if there is no update then it keeps the previous data on the screen
is this possible?
This is an example of the code that displays the data
if forecastData.status == forecast.STATUS_OK:
ren = font.render("Solar Radiation: {} W/m2".format(forecastData.solar_radiation), 1, pg.Color('black'), pg.Color(185,208,240))
else:
ren = font.render("Solar Radiation: ", 1, pg.Color('black'), pg.Color(185,208,240))
screen.blit(ren, (5*HRES//1600, 430*VRES//900-ren.get_height()//2))
When there is no update this displays Solar Radiation: only - I'd like this to use the previous data - ie DONT update this section of the display
It seems like what you should do is check the status before updating self.data in your Forecast class:
#Now make it available to outside world.
if data.status == STATUS_OK:
self.lock.acquire()
self.data = data
self.lock.release()
Can't be sure as it's not possible to run any of your code samples.
Here is a minimal example that uses a thread to simulate data retrieval with the occasional error.
import random
import threading
import time
import pygame
class ForecastThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True
self.start() # starts on instantiation!
def retrieve_data(self):
global data
# make request to forecast service and parse response
response = random.randint(1, 5)
if response == 1:
# Request failed
data = """(○o◌!*^##!#"""
else: # success
data = random.choice(("Cloudy", "Rainy", "Stormy", "Sunny", "Windy"))
def run(self):
while True:
if not running: # exit thread if main loop exits
return
else:
self.retrieve_data()
time.sleep(1) # sleep for a second
WIDTH = 480
HEIGHT = 240
FPS = 30
random.seed(98765432)
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# grab the first installed font
sys_font = pygame.font.SysFont(pygame.font.get_fonts()[0], 100)
data = "" # create globals before thread
running = True
forecaster_thread = ForecastThread() # note: thread is auto-starting
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
running = False
# update title bar
pygame.display.set_caption(f"Forecaster FPS: {clock.get_fps():.1f}")
# fill background to clear
window.fill(pygame.Color("lightsteelblue2"))
# create image
image = sys_font.render(data, True, pygame.Color("black"))
window.blit(image, (50, 50)) # blit close to centre
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
pygame.quit()
Running this code will initially display Cloudy then change to the gibberish data indicating a failed request.
If you change the retrieve_data() function to not update data on failure, then no gibberish will be displayed.
def retrieve_data(self):
global data
# make request to forecast service and parse response
response = random.randint(1, 5)
if response == 1:
# Request failed
print("Failed Request") # log the error
else: # success
data = random.choice(("Cloudy", "Rainy", "Stormy", "Sunny", "Windy"))
"import random" I have tried your solution so many ways but it always fails Does this help?... this is the full code to collect the forecast
import json
import urllib.request
import urllib.error
import time
from datetime import datetime
from datetime import timedelta
import pdb
import threading
import pygame as pg
import logging
import os.path
import socket
FORECAST_URL = "https://swd.weatherflow.com/swd/rest/better_forecast?api_key=20c70eae-e62f-4d3b-b3a4-8586e90f3ac8&station_id=44303&lat=53.440&lon=-2.105"
TIMEOUT = 15
STATUS_TIMEOUT = "Timeout"
STATUS_OK = "OK"
def nullis0hook(d):
"""This is a hook for the JSON decoder, replacing null with 0."""
for k in d.keys():
if d[k]==None:
d[k]=0
return d
class ForecastData:
def __init__(self):
self.status = STATUS_OK
self.conditions=""
self.iconnow = ""
self.updateTime="946684800"
self.tempnow=0
self.templow=30
self.temphigh=-10
self.sea_level_pressure=0
self.station_pressure=0
self.pressure_trend=""
self.relative_humidity=0
self.wind_avg=0
self.wind_direction_cardinal=""
self.angle=0
self.wind_gust=0
self.solar_radiation=0
self.uv=0
self.feels_like=0
self.dew_point=0
self.wet_bulb_temperature=""
self.delta_t=0
self.air_density=0
self.lightning_strike_last_distance="0"
self.lightning1=""
self.lightning_strike_last_epoch="946684800"
self.precip_accum_local_yesterday="0"
self.precip_accum_local_day="0"
self.condday=[""]*6
self.icon=[""]*6
self.iconday_filename = [os.path.join("images",os.path.join("forecast_icons","--_"))]*6
self.iconday = [pg.image.load(os.path.join("images",os.path.join("forecast_icons","--_1.bmp"))).convert()]*6
self.thighday=[0]*6
self.tlowday=[0]*6
self.sunriseday=[""]*6
self.sunsetday=[""]*6
self.precprday=[0]*6
self.precpiconday=[""]*6
self.preciptypeday=[""]*6
self.conditionshour=[""]*9
self.iconhour=[""]*9
self.precipprhour=[""]*9
self.preciptypehour=[""]*9
self.feelslikehour=[0]*9
self.conditionshour=[""]*9
self.iconhour=[""]*9
self.precipprhour=[""]*9
self.preciptypehour=[""]*9
self.feelslikehour=[0]*9
self.airtemphour=[0]*9
self.windavghour=[0]*9
self.forecast_icon_filename = os.path.join("images",os.path.join("weather_icons","--_.bmp"))
self.forecast_icon = pg.image.load(self.forecast_icon_filename).convert()
self.kia = pg.image.load(os.path.join("images",os.path.join("weather_icons","kia.png"))).convert()
#The Forecast class retrieves the weatherflow.com forecast and extracts relevant forecast data.
#Note: pygame display must be initialized before constructing a Forecast object
class ForeCastHour:
def __init__(self):
self.conditions = [""]*6
self.icon = ["--_"]*6
self.precipr = [""]*6
self.precip_type = [""]*6
self.feels_like = [""]*6
class Forecast:
def __init__(self):
self.data = ForecastData()
self.lock = threading.Lock()
self.max_wind_gust=0
self.templow=30
self.temphigh=-10
self.updateTime = ""
def getData(self):
"""Get most recently retrieved consistent set of data."""
self.lock.acquire()
data = self.data
self.lock.release()
return data
def midnightReset(self):
"""Reset any values that require resetting at midnight"""
self.lock.acquire()
self.data.wind_gust=0
self.lock.release()
def update(self):
"""Update the forecast data"""
data = ForecastData()
try:
req = urllib.request.Request(FORECAST_URL)
with urllib.request.urlopen(req, timeout=TIMEOUT) as response:
raw = response.read()
forecast_json = json.loads(raw, object_hook=nullis0hook)
data.status = STATUS_OK
self.updateTime = forecast_json["current_conditions"]["time"]
data.updateTime = time.strftime("%H:%M:%S", time.localtime(self.updateTime))
data.conditions = forecast_json["current_conditions"]["conditions"]
iconnow = forecast_json["current_conditions"]["icon"]
if iconnow == "null":
iconnow = "--_" #icon replacement
if iconnow == "":
iconnow = "--_" #icon replacement
data.iconnow_filename = os.path.join("images",os.path.join("weather_icons",iconnow+".bmp"))
if os.path.exists(data.iconnow_filename):
data.iconnow = pg.image.load(data.iconnow_filename).convert()
else:
logging.warning("Weather icon file {} not found.".format(data.iconnow_filename))
data.tempnow = forecast_json["current_conditions"]["air_temperature"]
if data.tempnow < self.templow:
self.templow = data.tempnow
if data.tempnow > self.temphigh:
self.temphigh = data.tempnow
data.templow = self.templow
data.temphigh = self.temphigh
data.sea_level_pressure = forecast_json["current_conditions"]["sea_level_pressure"]
data.station_pressure = forecast_json["current_conditions"]["station_pressure"]
data.pressure_trend = forecast_json["current_conditions"]["pressure_trend"]
data.relative_humidity = forecast_json["current_conditions"]["relative_humidity"]
data.wind_avg = forecast_json["current_conditions"]["wind_avg"]* 2.23694 #Convert mps to mph
data.wind_gust = forecast_json["current_conditions"]["wind_gust"] * 2.23694 #Convert mps to mph
data.angle = forecast_json["current_conditions"]["wind_direction"]
if data.angle <= 180:
data.angle = data.angle + 180
else:
data.angle = data.angle - 180
data.wind_direction_cardinal = forecast_json["current_conditions"]["wind_direction_cardinal"]
data.solar_radiation = forecast_json["current_conditions"]["solar_radiation"]
data.uv = forecast_json["current_conditions"]["uv"]
data.feels_like = forecast_json["current_conditions"]["feels_like"]
lightning_strike_last_distance = forecast_json["current_conditions"].get("lightning_strike_last_distance", 0)
lightning1 = lightning_strike_last_distance*0.621371 #Convert kph to mph
data.lightning_strike_last_distance = "{0:.1f} miles away ".format(lightning1)
lightning_strike_last_epoch = forecast_json["current_conditions"].get("lightning_strike_last_epoch")
data.lightning_strike_last_epoch = time.strftime("%d %b", time.localtime(lightning_strike_last_epoch))
data.precip_accum_local_yesterday = forecast_json["current_conditions"]["precip_accum_local_yesterday"]
data.precip_accum_local_day = forecast_json["current_conditions"]["precip_accum_local_day"]
for day in range(6):
data.sunriseday[day] = forecast_json["forecast"]["daily"][day]["sunrise"]
data.sunriseday[day] = time.strftime("%H:%M:%S", time.localtime(data.sunriseday[day]))
data.sunsetday[day] = forecast_json["forecast"]["daily"][day]["sunset"]
data.sunsetday[day] = time.strftime("%H:%M:%S", time.localtime(data.sunsetday[day]))
data.condday[day] = forecast_json["forecast"]["daily"][day]["conditions"]
icon = forecast_json["forecast"]["daily"][day]["icon"]
data.iconday_filename[day] = os.path.join("images",os.path.join("forecast_icons",icon+"1.bmp"))
if os.path.exists(data.iconday_filename[day]):
iconimage = pg.image.load(data.iconday_filename[day]).convert()
data.iconday[day] = iconimage
else:
logging.warning("Forecast icon file {} not found.".format(data.iconday_filename[day]))
data.thighday[day] = forecast_json["forecast"]["daily"][day]["air_temp_high"]
data.tlowday[day] = forecast_json["forecast"]["daily"][day]["air_temp_low"]
data.precprday[day] = forecast_json["forecast"]["daily"][day]["precip_probability"]
if data.precprday[day] != 0:
data.precpiconday[day] = forecast_json["forecast"]["daily"][day]["precip_icon"]
data.preciptypeday[day] = forecast_json["forecast"]["daily"][day]["precip_type"]
data.forecast_icon_filename = os.path.join("images",os.path.join("weather_icons",iconnow+".bmp"))
if os.path.exists(data.forecast_icon_filename):
data.forecast_icon = pg.image.load(data.forecast_icon_filename).convert()
else:
logging.warning("Forecast icon file {} not found.".format(data.forecast_icon_filename))
for hours in range(9):
ps = forecast_json["forecast"]["hourly"][hours]["conditions"]
if ps == "Wintry Mix Possible":
ps = "Winty-P"
if ps == "Wintry Mix Likely":
ps = "Winty-L"
if ps == "Rain Likely":
ps = "Rain-L"
if ps == "Rain Possible":
ps ="Rain-P"
if ps == "Snow Possible":
ps = "Snow-P"
if ps == "Thunderstorms Likely":
ps = "ThundrL"
if ps == "Thunderstorms Possible":
ps = "ThundrP"
if ps == "Partly Cloudy":
ps = "Clouds"
if ps == "Very Light Rain":
ps = "drizzle"
data.conditionshour[hours] = "{}".format(ps)
data.iconhour[hours] = forecast_json["forecast"]["hourly"][hours]["icon"]
pp = forecast_json["forecast"]["hourly"][hours]["precip_probability"]
data.precipprhour[hours] = "{}%".format(pp)
if pp == 0:
data.preciptypehour[hours] = "0"
else:
data.preciptypehour[hours] = forecast_json["forecast"]["hourly"][hours]["precip_type"]
data.feelslikehour[hours] = "{} C".format(forecast_json["forecast"]["hourly"][hours]["feels_like"])
data.airtemphour[hours] = forecast_json["forecast"]["hourly"][hours]["air_temperature"]
data.windavghour[hours] = forecast_json["forecast"]["hourly"][hours]["wind_avg"]*0.621371 #Convert kph to mph
#datetime object containing current date and time
now = datetime.now()
data.updateTime = now.strftime("%H:%M:%S")
self.updateTime = now
except (socket.timeout, socket.gaierror, urllib.error.URLError, json.decoder.JSONDecodeError, KeyError):
logging.warning("Error retrieving forecast data")
#declare timeout only after timeout period
if datetime.now() - self.updateTime > timedelta(seconds=TIMEOUT):
data.status = STATUS_TIMEOUT
data.updateTime = self.data.updateTime #Use old update time value
else: #If timeout period has not elapsed yet, use previous data
logging.info("Not timing out yet")
self.data = self.data #Use old value
if datetime.now() - self.updateTime > timedelta(seconds=TIMEOUT):
data.status = STATUS_TIMEOUT
else: #If timeout period has not elapsed yet, use previous data
logging.info("Not timing out yet")
data = self.data
#Now make it available to outside world.
self.lock.acquire()
self.data = data
self.lock.release()
And this is what works when there is internet, but when the internet goes off it doesn't use the old data
'''THIS IS THE FORECAST SECTION THAT WORKS WHEN THERE IS INTENET AND FORECAST UPDATES
BUT WHEN THERE IS NO UPDATE IT JUST DISPLAYS THE TEXT AND DOES NOT USE THE OLD DATA VALUE'''
if forecastData.status == forecast.STATUS_OK:
ren = font.render("battery voltage : " + "{} V".format(forecastData.battery), 1, pg.Color('white'), pg.Color(162, 160, 160))
else:
ren = font.render("", 1, pg.Color('white'), pg.Color(162, 160, 160))
screen.blit(ren, (700*HRES//1600, VRES//60))
if forecastData.status == forecast.STATUS_OK:
ren = font.render("Conditions: {} ".format(forecastData.conditions), 1, pg.Color('black'), pg.Color(185,208,240))
else:
ren = font.render("Conditions: ", 1, pg.Color('black'), pg.Color(185,208,240))
screen.blit(ren, (5*HRES//1600, 70*VRES//900-ren.get_height()//2))
'''THIS IS THE ERROR MESSAGE I GET LOGGED'''
2021-04-15 13:28:41,057 - root - INFO - Updating forecast.
2021-04-15 13:28:41,087 - root - WARNING - Error retrieving forecast data
2021-04-15 13:29:11,049 - root - WARNING - Previous every-minute thread still running. Not relaunching.
2021-04-15 13:29:26,602 - root - INFO - t key pressed. Toggle fullscreen.
2021-04-15 13:29:41,085 - root - WARNING - Previous every-minute thread still running. Not relaunching.
2021-04-15 13:30:11,089 - root - WARNING - Previous every-minute thread still running. Not relaunching.
'''THIS IS THE FUNCTION THAT THE ERROR REFERS TO'''
def everyMinuteThreadFunction():
"""This thread function executes once every minute."""
global initialWeatherUpdateReceived, everyMinuteThreadRunning
everyMinuteThreadRunning = True
assert(forecastObj)
checkMidnightRollOver()
logging.info("Updating forecast.")
forecastObj.update()
forecastData = forecastObj.getData()
if forecastData.status == forecast.STATUS_OK:
logging.info("Forecast data: {}".format(vars(forecastData)))
#The first time we pass here is a good time to kick off the five minute task. We now have our first
#forecast evice data available
if not initialWeatherUpdateReceived:
#program a periodic timer used to kick off the everyFiveMinutesThreadFunction.
pg.time.set_timer(EVERY_FIVE_MINUTES_THREAD_FUNCTION_EVENT, 5*60000)
#Kick off the task now, for the initial interval.
t = threading.Thread(target=everyFiveMinutesThreadFunction, args=())
t.daemon = True
t.start()
updateGauge()
initialWeatherUpdateReceived = True
everyMinuteThreadRunning = False
So, I've been studying programming for a short time and decided to make the snake game in pygame. However, while making the base of the program I realized that the rectangle (snake) controlled by the player is teleporting (maybe by the lag) every second while moving. Here is the code:
import pygame
pygame.init()
# Window
window = (1280, 720)
center = (window[0]//2, window[1]//2)
screen = pygame.display.set_mode(window)
pygame.display.set_caption("Snake")
# Colors
COLOR_LIGHT_GREY = (200, 200, 200)
COLOR_DARK_GREY = pygame.Color('gray12')
# Game loop
game_loop = True
game_clock = pygame.time.Clock()
# Create image
def img(name):
img_path = "./assets/natanael.lucena_" + name + ".png"
return pygame.image.load(img_path).convert_alpha()
# Set object coordinates
def set_obj_coordinates(obj, x, y):
obj.x = x
obj.y = y
# Check player key press
def check_player_key(b):
global snake_direction
if event.key == pygame.K_w or event.key == pygame.K_s or event.key == pygame.K_a or event.key == pygame.K_d:
snake_direction[event.key] = b
# Check key events in-game
def event_conditional():
global game_loop
if event.type == pygame.QUIT:
game_loop = False
elif event.type == pygame.KEYDOWN:
check_player_key(True)
elif event.type == pygame.KEYUP:
check_player_key(False)
# Check if the snake collided and the game is over
def game_over():
if snake.y < 0 or snake.y > 720 or snake.x < 0 or snake. x > 1280:
return True
# Snake
snake_img = img("snake")
snake = snake_img.get_rect()
move_keys = [pygame.K_w, pygame.K_d, pygame.K_s, pygame.K_a]
snake_direction = {k: False for k in move_keys}
snake_score = 0
snake_vel = 10
set_obj_coordinates(snake, center[0], center[1])
# Apple
apple_img = img("apple")
apple = apple_img.get_rect()
apple_eaten = False
set_obj_coordinates(apple, 40, 40)
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
for i in range(4):
if i % 2:
coord_aux = "x "
else:
coord_aux = "y "
if i % 3:
op = "+= "
else:
op = "-= "
if snake_direction[move_keys[i]]:
exec("snake." + coord_aux + op + "snake_vel")
# the for loop above is equivalent to :
# if snake_direction[move_keys[0]]:
# snake.y -= snake_vel
# if snake_direction[move_keys[1]]:
# snake.x += snake_vel
# if snake_direction[move_keys[2]]:
# snake.y += snake_vel
# if snake_direction[move_keys[3]]:
# snake.x -= snake_vel
screen.fill(COLOR_DARK_GREY)
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
# Update screen
pygame.display.flip()
game_clock.tick(60)
pygame.quit()
If someone can tell me the reason for the problem, I really appreciate it.
Edit: looks like the problem is just happening with me
Thats a common problem with pygame, especially since pygame 2/sdl 2 where you can't use the directx video driver and enable vsync anymore.
You should do the following:
keep track of the sub-pixel coordinates of your moving game objects. A Rect can only store integer values in its x and y attributes, so you'll need another variable to store this. I usually use a Vector2 because it's easy to use and the performance hit usually does not matter anyway.
use an accurate clock. Pygame's clock object only uses milliseconds, which is not accurate enough for really smooth movement. If you're on windows, usually the best method for timing is the GetSystemTimePreciseAsFileTime function.
use a delta timing.
You could also use different threads for the parts of your game that needs different timing methods (e.g. your game logic expects fixed 30 or 60 FPS and your drawing code wants to run as fast as possible), but that's overkill for your small game.
So here's an example I hacked together that gives you smooth movement (at least that's what I use usually since it works fine for me. Note that it's windows specific):
import pygame
import ctypes.wintypes
pygame.init()
screen = pygame.display.set_mode((1280, 720))
center = screen.get_rect().center
pygame.display.set_caption("Snake")
game_loop = True
# https://stackoverflow.com/a/28574340/142637
def utcnow_microseconds():
system_time = ctypes.wintypes.FILETIME()
ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(system_time))
large = (system_time.dwHighDateTime << 32) + system_time.dwLowDateTime
return large // 10 - 11644473600000000
# Snake
snake_img = pygame.Surface((40, 40))
snake_img.fill('white')
snake = snake_img.get_rect()
snake_vel = 10
snake_pos = pygame.Vector2(center[0], center[1])
snake.topleft = snake_pos.x, snake_pos.y
# Apple
apple_img = pygame.Surface((40, 40))
apple_img.fill('red')
apple = apple_img.get_rect(topleft=(40, 40))
dt = 0
while game_loop:
t1 = utcnow_microseconds()
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_loop = False
keys = pygame.key.get_pressed()
snake_pos.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel * dt
snake_pos.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel * dt
snake.topleft = snake_pos.x, snake_pos.y
screen.fill('darkgrey')
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
pygame.display.flip()
t2 = utcnow_microseconds()
dt = (t2 - t1) / 1000. / 1000. * 30
pygame.quit()
Further reading.
Very likely the bottleneck is the line
exec("snake." + coord_aux + op + "snake_vel")
exec has to parse and interpret the text in the argument.
This code can be easily improved
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
if i % 2:
snake.x += sign * snake_vel
else:
snake.y += sign * snake_vel
Since snake is a pygame.Rect object, you can even do the following:
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
snake[(i+1) % 2] += sign * snake_vel
However:
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or a step-by-step movement.
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
Use pygame.key.get_pressed() for a smooth continuous movement:
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
keys = pygame.key.get_pressed()
snake.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel
snake.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel
# [...]
I was unsure whether to post the full code or not, but here's what I have:
from tkinter import *
from random import randint
HEIGHT = 500
WIDTH = 800
MID_X = WIDTH/2
MID_Y = HEIGHT/2
SHIP_R = 15
SHIP_SPD = 10
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 6
GAP = 100
window = Tk()
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
def move_ship(event):
fixed = True
while fixed == True:
ship_x, ship_y = event.x, event.y
c.move(ship_id, ship_x, ship_y)
c.move(ship_id2, ship_x, ship_y)
sleep(0.01)
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x-r, y-r, x+r, y+r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def del_bubble(i):
del bub_r[i]
del bub_speed[i]
c.delete(bub_id[i])
del bub_id[i]
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[i])
if x < -GAP:
del_bubble(i)
from math import sqrt
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2-x1)**2 + (y2-y1)**2)
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R+bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
c.create_text(50, 30, text='TIME', fill='white')
c.create_text(150, 30, text='SCORE', fill='white')
time_text = c.create_text(50, 50, fill='white')
score_text = c.create_text (150, 50, fill='white')
def show_score(score):
c.itemconfig(score_text, text=str(score))
def show_time(time_left):
c.itemconfig(time_text, text=str(time_left))
from time import sleep, time
BUB_CHANCE = 20
TIME_LIMIT = 30
BONUS_SCORE = 1000
# MAIN GAME LOOP
c.bind("<B1_Motion>", move_ship)
score = 0
bonus = 0
end = time() + TIME_LIMIT
while time() < end:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
window.update()
sleep(0.01)
c.create_text(MID_X, MID_Y, \
text='PARTY TIME, EXCELLENT', fil='white', font=('Helvetica', 30))
c.create_text(MID_X, MID_Y + 30, \
text='Score: ' + str(score), fill='white')
c.create_text(MID_X, MID_Y + 45, \
text='BONU TIME: ' + str(bonus*TIME_LIMIT), fill='white')
I'm a complete beginner when it comes to python, and have been given an assignment to only use tkinter and the standard libraries to give mouse movement to this "game". I just can't seem to get the right grasp of it. Any suggestions would be appreciated!
The first step is to remove your while loop, and put most of your functionality into a function. Put everything you want to do in a single frame of animation into this function.
Next, call this function on a regular interval using the after command. This allows the event loop to run continuously, which is important for your UI to be responsive. You can do your time() < end calculation inside this function, and use the result to break the cycle once time is up.
It looks something like this:
def draw_one_frame():
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
# move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
if time() < end:
after(10, draw_one_frame)
You can use the first parameter to after to control how many frames per second you wish your program to run at.
Next, you need to handle mouse movement. You do this by creating a binding to the movement of the mouse. You do this outside of the draw_one_frame method. It looks something like this:
c.bind("<B1-Motion>", move_ship)
You also need to remove the infinite loop from move_ship, and also remove the sleep. You simply need to do all the calculations for the current mouse position. The function will be already be looping -- being called once each time the mouse moves.
Finally, you need to call window.mainloop() after all your other code, so that the program can process events. This should be the very last line of your program. It will run until the window is destroyed.