Trying to program a basic snake game in python (keeps "crashing") - python

So I'm trying to teach myself a little bit of python for fun following this tutorial: https://www.youtube.com/watch?v=8dfePlONtls
I made it to the point where the dot keeps moving automatically (left, right, up or down) depending on which arrow key I pressed last. But for some reason it all stops after a few seconds unless I keep inputting random keys or even just hovering the mouse over the window will do the trick. If I stop doing anything, the program "stops".
I also noticed the CPU usage going up to 22% for the program when I stop doing anything, but goes back down when I start inputting keys or moving the mouse again.
I've tried following exactly what was done in the video but I can't find any error and pycharm doesn't detect any obvious error in my code.
Here is what I have so far:
import pygame
from pygame.locals import *
import time
class Snake:
def __init__(self, parent_screen):
self.parent_screen = parent_screen
self.block = pygame.image.load("resources/block.jpg").convert()
self.x = 100
self.y = 100
self.direction = 'down'
def move_left(self):
self.direction = 'left'
def move_right(self):
self.direction = 'right'
def move_up(self):
self.direction = 'up'
def move_down(self):
self.direction = 'down'
def draw(self):
self.parent_screen.fill((117, 17, 8))
self.parent_screen.blit(self.block, (self.x, self.y))
pygame.display.update()
def walk(self):
if self.direction == 'left':
self.x -= 10
if self.direction == 'right':
self.x += 10
if self.direction == 'up':
self.y -= 10
if self.direction == 'down':
self.y += 10
self.draw()
class Game:
def __init__(self):
pygame.init()
self.surface = pygame.display.set_mode((500, 500))
self.surface.fill((117, 17, 8))
self.snake = Snake(self.surface)
self.snake.draw()
def run(self):
running = True
while running:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
if event.key == K_UP:
self.snake.move_up()
if event.key == K_DOWN:
self.snake.move_down()
if event.key == K_LEFT:
self.snake.move_left()
if event.key == K_RIGHT:
self.snake.move_right()
elif event.type == QUIT:
running = False
self.snake.walk()
time.sleep(0.2)
if __name__ == "__main__":
game = Game()
game.run()
Thanks in advance!

while running:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
if event.key == K_UP:
self.snake.move_up()
if event.key == K_DOWN:
self.snake.move_down()
if event.key == K_LEFT:
self.snake.move_left()
if event.key == K_RIGHT:
self.snake.move_right()
elif event.type == QUIT:
running = False
self.snake.walk()
time.sleep(0.2)
Your original code indentation means that you are handling keypresses every pygame event, but also updating your snake every pygame event. Removing the indent on the last two lines which move the snake means they will execute in the outer while loop instead of the event loop. This means the snake will update its position each time the loop runs instead of only when an event occurs.

Related

Sprite not moving despite inputs in pygame

I'm facing a problem in pygame where the sprite is not moving regardless the inputs, i've checked it several times but have no clue what's wrong with it. There are 3 pages, first contains the main loop of the game, second contains the code of player sprite and third contains some game functions.
main---------page-01
import pygame
import sys
from player import Player
import game_functions as gf
def run_game():
# Intialise the game and start the screen
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("AmarCreep")
player = Player(screen)
# Main loop
while True:
# Check if user wants to quit
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Navy screen
screen.fill((0,0,128))
gf.responses(screen, player)
player.p_movements()
# Make the player appear
player.draw_player()
# Make the newly made screen visible
pygame.display.flip()
run_game()
Player--------------------page-02
import pygame
from pygame.sprite import Sprite
class Player(Sprite):
# Initialise the main player
def __init__(self, screen):
super(Player, self).__init__()
self.screen = screen
self.screen_rect = screen.get_rect()
# Specifying the position of the player at start
self.rect = pygame.Rect(0, 0, 30, 30)
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = 590
self.moving_left = False
self.moving_right = False
self.moving_up = False
def p_movements(self):
if self.moving_left:
self.rect.x -= 5
if self.moving_right:
self.rect.x += 5
if self.moving_up:
self.rect.y -= 30
def draw_player(self):
''' Draw the player on the screen'''
pygame.draw.rect(self.screen, (255,255,255), self.rect)
game_functions------------------page-03
import pygame
def responses(screen, player):
''' Check for responses'''
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
player.moving_up == True
elif event.key == pygame.K_LEFT:
player.moving_left == True
elif event.key == pygame.K_RIGHT:
player.moving_right == True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
player.moving_up == False
elif event.key == pygame.K_LEFT:
player.moving_left == False
elif event.key == pygame.K_RIGHT:
player.moving_right == False
The first thing you appear to do in your main event loop is to totally clear the queue while looking for a quit event. That's not going to necessarily go down well with your second event loop in the game functions file.
In fact, it's quite messy to have two event loops, one of which throws away anything that's not a quit event - this may well lead to lost events.
I suspect a better way would be to provide a response game function which just handles one event (and doesn't touch the event queue at all):
def response(screen, player, evt):
if evt.type == pygame.KEYDOWN:
blah blah blah
and have the main (now the only) event loop call that for events it does not want to handle itself:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
gf.response(screen, player, event)
You may also want to consider what you would expect when both the up and down button are pressed (or the left and right, for that matter). At a minimum, you probably want to make that a test case :-)

trying to rotate an image in pygame for movement

im new to programming so im following some tutorials online to make a basic game. in order to create the movement for my character, i followed this tutorial
https://www.youtube.com/watch?v=4aZe84vvE20&t=692s&ab_channel=ClearCode
after following the rotation part of the video, i tried testing it out. however, it does not work for some reason. im not sure why this is. i do not get an error, but the character stays in one place and does not rotate once i press the left and right arrow keys. could somebody have a look at my code and let me know what i have done wrong? thanks a lot
import pygame
pygame.init()
window = pygame.display.set_mode((650, 650))
pygame.display.set_caption ("Game")
green = (0,255,0)
window.fill(green)
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.og_image = pygame.image.load("player1.png")
self.image = self.og_image
self.rect = self.image.get_rect (center = (345,345))
self.angle = 0
self.rotate_speed = 1
self.direction = 0
def rotate(self):
if self.direction == 1:
self.angle -= self.rotate_speed
print(self.angle)
elif self.direction == -1:
self.angle += self.rotate_speed
print(self.angle)
self.image = pygame.transform.rotozoom(self.og_image, self.angle, 1)
self.rect = self.image.get_rect(center = (self.rect.center))
def update(self):
self.rotate()
player_1 = Player()
players = pygame.sprite.GroupSingle()
players.add(player_1)
players.draw(window)
run = True
while run == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
players.sprite.direction += 1
if event.key == pygame.K_LEFT:
players.sprite.direction -= 1
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
players.sprite.direction -= 1
if event.key == pygame.K_LEFT:
players.sprite.direction += 1
players.update()
pygame.display.update()
pygame.quit()
You have to draw the Sprites in the Group (players.draw(window)) and you have to clear the display (window.fill(green)) before drawing the objects. Note, you have to do this in the application loop rather than the event loop (It's a matter of Indentation):
run = True
while run == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
players.sprite.direction += 1
if event.key == pygame.K_LEFT:
players.sprite.direction -= 1
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
players.sprite.direction -= 1
if event.key == pygame.K_LEFT:
players.sprite.direction += 1
# INDENTATION
#<--|
players.update()
window.fill(green)
players.draw(window)
pygame.display.update()

How to use time.Time() to control projectile fire rate

I'm trying to use time.Time() to control the fire rate of my projectiles, but after putting in the time logic, my player can no longer shoot any projectiles at all. How can I make it so that my player only fires once every half a second or so?
The following is a workable example;
main game module
import pygame
from constants import *
from player import Player
from Projectile import Projectiles
import time
pygame.init()
screen = pygame.display.set_mode([500, 500])
pygame.display.set_caption('Labyrinth')
# Spawn player
player = Player(50, 50)
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(player)
projectile_list = pygame.sprite.Group()
clock = pygame.time.Clock()
done = False
# ----- Event Loop
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == ord('a'):
player.changespeed(-3, 0)
elif event.key == ord('d'):
player.changespeed(3, 0)
elif event.key == ord('w'):
player.changespeed(0, -3)
elif event.key == ord('s'):
player.changespeed(0, 3)
elif event.key == pygame.K_LEFT:
projectile = Projectiles(player.rect.x, player.rect.y, -5, 0)
elif event.key == pygame.K_RIGHT:
projectile = Projectiles(player.rect.x, player.rect.y, 5, 0)
elif event.key == pygame.K_UP:
projectile = Projectiles(player.rect.x, player.rect.y, 0, -5)
elif event.key == pygame.K_DOWN:
projectile = Projectiles(player.rect.x, player.rect.y, 0, 5)
try:
if projectile:
if time.Time() - last_shot > player.fire_rate:
projectile_list.add(projectile)
last_shot = time.Time()
except:
pass
elif event.type == pygame.KEYUP:
if event.key == ord('a'):
player.changespeed(3, 0)
elif event.key == ord('d'):
player.changespeed(-3, 0)
elif event.key == ord('w'):
player.changespeed(0, 3)
elif event.key == ord('s'):
player.changespeed(0, -3)
# ----- Game Logic
all_sprites_list.update()
projectile_list.update()
screen.fill(GREEN)
all_sprites_list.draw(screen)
projectile_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
player module
from constants import *
import pygame
import time
from datetime import datetime, timedelta
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([15, 15])
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.fire_rate = 1
self.change_x = 0
self.change_y = 0
def changespeed(self, x, y):
self.change_x += x
self.change_y += y
def update(self):
self.rect.x += self.change_x
self.rect.y += self.change_y
projectile module
import pygame
from constants import *
class Projectiles(pygame.sprite.Sprite):
def __init__(self, x, y, x_speed, y_speed):
super().__init__()
self.image = pygame.Surface([4, 4])
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.x_speed = x_speed
self.y_speed = y_speed
def update(self):
self.rect.x += self.x_speed
self.rect.y += self.y_speed
constants module defines a few colours. Any help is much appreciated!
Well, strictly speaking, the answer is that you have time.Time() in a try block, and it should be time.time() (all lowercase). This throws an exception, so any time time.Time() - last_shot > player.fire_rate is evaluated, the try block isn't run. This would have been more apparent if you had caught the exception and thrown an error message rather than just passing.
There likely are further issues, but a more in-depth analysis of your code is difficult, as I don't see you initializing last_shot anywhere, nor player.fire_rate. The "minimal complete example" standard isn't just for the answerers, but for the asker as well. Basic debugging in isolating the issue as much as possible.
As another note, your while loop appears to contain nothing but a for loop, making it redundant. If you're doing that to just have the program run through the loop over and over again until the appropriate time, then time.sleep() would be probably be a better way to do that.
A try with a bare except is an anti-pattern and should almost never be used, because it hides all errors in the try clause and makes the code hard to debug. I'd remove it and rearrange the event handling code.
When an arrow key gets pressed, only change the velocity vector depending on the key instead of creating new projectile instances. Then check again if any of the arrow keys were pressed and if the timer is finished, create a projectile instance and pass the current velocity vector.
import pygame as pg
from pygame.math import Vector2
BULLET_IMG = pg.Surface((9, 9))
BULLET_IMG.fill(pg.Color('aquamarine2'))
class Projectile(pg.sprite.Sprite):
def __init__(self, pos, vel):
super().__init__()
self.image = BULLET_IMG
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(vel)
def update(self):
self.rect.move_ip(self.vel)
def main():
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
projectiles = pg.sprite.Group()
previous_time = pg.time.get_ticks()
speed = 12
vel = Vector2(speed, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
# Only change the velocity vector.
if event.key == pg.K_LEFT:
vel = Vector2(-speed, 0)
elif event.key == pg.K_RIGHT:
vel = Vector2(speed, 0)
elif event.key == pg.K_UP:
vel = Vector2(0, -speed)
elif event.key == pg.K_DOWN:
vel = Vector2(0, speed)
# If an arrow keys was pressed, create a new projectile.
if event.key in (pg.K_LEFT, pg.K_RIGHT, pg.K_UP, pg.K_DOWN):
current_time = pg.time.get_ticks()
# We're ready to fire when 500 ms have passed.
if current_time - previous_time > 500:
previous_time = current_time
# Now create the projectile instance and pass
# the position and the velocity.
projectiles.add(Projectile(pg.mouse.get_pos(), vel))
projectiles.update()
screen.fill((30, 30, 30))
projectiles.draw(screen)
pg.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
pg.quit()

Pygame sprite touching

I am attempting to build a simple game with two sprites. I can't figure out how to add code that detects if a sprite is touching the other. I am also having trouble understanding the countless tutorials on this topic. If you could try and explain this to me as simply as possible that would be amazing!
import pygame
import sys
from pygame.locals import *
pygame.init()
black = (0, 0, 0)
white = (255, 255, 255)
bg = black
square = pygame.image.load('square.png')
square1 = pygame.image.load('square1.png')
screen = pygame.display.set_mode((640, 400))
UP = 'UP'
DOWN = 'DOWN'
LEFT = 'LEFT'
RIGHT = 'RIGHT'
direction = RIGHT
movex, movey, movex1, movey1 = 100, 100, 200, 200
class player1(pygame.sprite.Sprite):
"""Player 1"""
def __init__(self, xy):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('square.png')
self.rect = self.image.get_rect()
class player2(pygame.sprite.Sprite):
"""Player 2"""
def __init__(self, xy):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('square1.png')
self.rect = self.image.get_rect()
while True:
screen.fill(black)
collide = pygame.sprite.collide_mask(player1, player2)
if collide == True:
print 'collision!'
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_w:
movey -= 10
elif event.key == K_s:
movey += 10
elif event.key == K_a:
movex -= 10
elif event.key == K_d:
movex += 10
if event.key == K_UP:
movey1 -= 10
elif event.key == K_DOWN:
movey1 += 10
elif event.key == K_LEFT:
movex1 -= 10
elif event.key == K_RIGHT:
movex1 += 10
if event.type == QUIT:
pygame.quit()
sys.exit()
screen.blit(square, (movex, movey))
screen.blit(square1, (movex1, movey1))
pygame.display.update()
Some issues first:
pygame.sprite.collide_mask never returns True. It returns a point or None. So your check collide == True will never be evaluate to True
pygame.sprite.collide_mask excepts two Sprite instances, but you call it with class objects as arguments (collide_mask(player1, player2))
You only need pygame.sprite.collide_mask when you want to do pixel perfect collision detection
You actually don't use the classes player1 and player2 in the rest of your code
If you're using the Sprite class, a simply way for collision detection is to use the Group class. But since you only have two Sprites, you can simple check for an intersection of their Rects using colliderect.
I've updated your code to make use of the Sprite class:
import pygame
import sys
from pygame.locals import *
pygame.init()
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode((640, 400))
class Player(pygame.sprite.Sprite):
def __init__(self, image, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image)
self.rect = self.image.get_rect(x=x, y=y)
player1, player2 = Player('square.png', 100, 100), Player('square1.png', 200, 200)
players = pygame.sprite.Group(player1, player2)
while True:
screen.fill(black)
if player1.rect.colliderect(player2.rect):
print 'collision!'
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_w:
player1.rect.move_ip(0, -10)
elif event.key == K_s:
player1.rect.move_ip(0, 10)
elif event.key == K_a:
player1.rect.move_ip(-10, 0)
elif event.key == K_d:
player1.rect.move_ip(10, 0)
if event.key == K_UP:
player2.rect.move_ip(0, -10)
elif event.key == K_DOWN:
player2.rect.move_ip(0, 10)
elif event.key == K_LEFT:
player2.rect.move_ip(-10, 0)
elif event.key == K_RIGHT:
player2.rect.move_ip(10, 0)
if event.type == QUIT:
pygame.quit()
sys.exit()
players.draw(screen)
pygame.display.update()
Collision detection is a broad topic, but this should get you started.

pygame sprite not moving

I'm faced with a logic error where the sprite simply doesn't move regardless of input.
My code will be below so no context is left out, it's fairly short.
Is there a better way of moving the sprite apart from the blit?
I've seen somewhere stuff about updating the sprite or some such, done quite differently to simply blitting it.
import pygame
pygame.init()
import random
import math
screen=pygame.display.set_mode([700,400])
black = ( 0, 0, 0)
white = ( 255, 255, 255)
red = ( 255, 0, 0)
player_x=350
player_y=200
player_x_vel=0
player_y_vel=0
rot_player=pygame.image
pi=math.pi
class Player(pygame.sprite.Sprite):
def __init__(self):
global player
pygame.sprite.Sprite.__init__(self)
self.pos=(350,200)
self.image=pygame.image.load("arrowtest.png").convert()
self.rect=self.image.get_rect()
screen=pygame.display.get_surface()
self.area=screen.get_rect()
self.speed=10
self.state="still"
self.reinit()
def reinit(self):
self.state="still"
self.movepos=[0,0]
def update(self):
newpos=self.rect.move(self.movepos)
if self.area.contains(newpos):
self.rect=newpos
pygame.event.pump()
def moveup(self):
self.movepos[1]-=(self.speed)
self.state="moveup"
def movedown(self):
self.movepos[1]+=(self.speed)
self.state="movedown"
def moveleft(self):
self.movepos[0]-=(self.speed)
self.state="moveleft"
def moveright(self):
self.movepos[0]+=(self.speed)
self.state="moveright"
def moveupright(self):
self.movepos[1]-=(self.speed)
self.movepos[0]+=(self.speed)
def moveupleft(self):
self.movepos[1]-=(self.speed)
self.movepos[0]-=(self.speed)
def movedownright(self):
self.movepos[1]+=(self.speed)
self.movepos[0]+=(self.speed)
def movedownleft(self):
self.movepos[1]+=(self.speed)
self.movepos[0]-=(self.speed)
def angleplayer(self):
mouse_pos=pygame.mouse.get_pos()
dx=mouse_pos[0]-player_x
dy=mouse_pos[1]-player_y
rads=math.atan2(-dy, dx)
rads %= 2*pi
angle = math.degrees(rads)
print angle
rot_player.image=pygame.transform.rotate(player.image, angle-90)
done=False
clock=pygame.time.Clock()
while done==False:
player = Player()
for event in pygame.event.get():
if event.type == pygame.QUIT:
done=True
if event.type == pygame.MOUSEBUTTONDOWN:
fired_pos=pygame.mouse.get_pos()
fired=True
if event.type == pygame.KEYDOWN:
player.angleplayer()
if event.key == pygame.K_w:
player.moveup()
if event.key == pygame.K_s:
player.movedown()
if event.key == pygame.K_a:
player.moveleft()
if event.key == pygame.K_d:
player.moveright()
print "co ords", player_x,player_y
print "x vel", player_x_vel
print "y vel", player_y_vel
if event.type == pygame.KEYUP:
player.movepos=[0,0]
player.state="still"
player.angleplayer()
screen.fill(white)
screen.blit(player.image, player.pos)
clock.tick(20)
pygame.display.flip()
pygame.quit()
Thanks in advance
First of all, you are creating a new player every iteration of your main loop:
...
while done == False:
player = Player()
...
You want to create the player once, so move the creation outside the loop:
...
player = Player()
while not done:
...
Second: To get the position of the player, you use player.pos:
...
screen.blit(player.image, player.pos)
...
but you never update player.pos (it's always (350,200)), you only change self.rect:
def update(self):
newpos=self.rect.move(self.movepos)
if self.area.contains(newpos):
self.rect=newpos
pygame.event.pump()
either use player.rect to get the player position, or update player.pos accordingly:
def update(self):
newpos=self.rect.move(self.movepos)
if self.area.contains(newpos):
self.rect = newpos
self.pos = self.rect.topleft
# don't call pygame.event.pump() here. Doesn't make any sense :-)
Third, you update the player position in the update function, but you never call it. Call it before drawing the player:
...
while not done:
...
player.update()
screen.fill(white)
screen.blit(player.image, player.pos)
...
You can simplify your code alot by setting movepos directly and removing the move... functions.
working example:
import pygame
pygame.init()
colors = pygame.color.THECOLORS
screen = pygame.display.set_mode([700,400])
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.surface.Surface((32,32))
self.image.fill(colors['green'])
self.rect = self.image.get_rect().move(350, 200)
self.speed = 4
def update(self):
dx, dy = self.movepos[0] * self.speed, self.movepos[1] * self.speed
self.rect = self.rect.move(dx, dy)
done=False
clock = pygame.time.Clock()
player = Player()
while not done:
if pygame.event.get(pygame.QUIT):
break
pressed = pygame.key.get_pressed()
l, r, u, d = [pressed[k] for k in pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s]
player.movepos = -l + r, -u + d
player.update()
screen.fill(colors['white'])
screen.blit(player.image, player.rect.topleft)
pygame.display.flip()
clock.tick(60)
You have a lot of move functions. Try combining them into a simple two function class like this example:
class Puck(pygame.sprite.Sprite):
def __init__(self, image_file, speed, location):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
The two main controllers are the self.rect.left and self.rect.top. By changing their values, you can move the sprites like in this example.
#Name of varible# = Puck('#Image File#', [#value of self.rect.left#, value of self.rect.top]
Then you can use events to change them like:
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
exit(0)
elif event.type == pygame.KEYDOWN:
if event.key == K_UP:
random.rect.top =random.rect.top - 75
elif event.key == K_LEFT:
random.rect.left = random.rect.left - 75
elif event.key == K_DOWN:
random.rect.top = random.rect.top + 75
elif event.key == K_RIGHT:
randoml.rect.left = randoml.rect.left + 75
elif event.key == K_w:
random.rect.top = random.rect.top - 50
elif event.key == K_a:
random.rect.left = random.rect.left - 50
elif event.key == K_s:
random.rect.top = random.rect.top + 50
elif event.key == K_d:
random.rect.left = random.rect.left + 50
This should be able to move your sprite.

Categories

Resources