inteligentny-traktor/main.py
2023-06-15 14:03:24 +02:00

664 lines
24 KiB
Python

import pygame
import random
import torch
from torch import nn
from torchvision import datasets, transforms
from torchvision.transforms import Lambda
from PIL import Image
import astar
from classes import Field, Player
from bfs import Istate, succ
from bfs import graphsearch
from board import Grid, Box, Obstacle, getGridBoxes, gridObjects
from screen import SCREEN
# pygame.init()
# Game Constants
Ucelu = False
SCREENX = 500
SCREENY = 500
device = torch.device('cpu')
model1 = nn.Sequential(nn.Linear(30000, 10000), nn.ReLU(), nn.Linear(10000, 10000), nn.ReLU(), nn.Linear(10000, 10000), nn.Linear(10000, 4), nn.LogSoftmax(dim=-1)).to(device)
model1.load_state_dict(torch.load("./NN/trained"))
pygame.display.set_caption('Inteligentny Traktor')
plants = [[], [], []]
plants[0].append(Image.open("NN/w1.png"))
plants[0].append(Image.open("NN/w2.png"))
plants[0].append(Image.open("NN/w3.png"))
plants[1].append(Image.open("NN/c1.png"))
plants[1].append(Image.open("NN/c2.png"))
plants[1].append(Image.open("NN/c3.png"))
plants[2].append(Image.open("NN/ca1.png"))
plants[2].append(Image.open("NN/ca2.png"))
plants[2].append(Image.open("NN/ca3.png"))
b = [Image.open("NN/b1.png").convert('RGBA'), Image.open("NN/b2.png").convert('RGBA'), Image.open("NN/b3.png").convert('RGBA')]
def generate(water, fertilizer, plantf):
if water == 1:
new_im = Image.new('RGB', (100, 100),
(160 + random.randint(-10, 10), 80 + random.randint(-10, 10), 40 + random.randint(-10, 10)))
tmp = plants[plantf][random.randint(0, 2)].resize(
(25 + random.randint(-10, 25), 25 + random.randint(-10, 25))).rotate(random.randint(0, 359))
new_im.paste(tmp, (random.randint(0, 50), random.randint(0, 50)), tmp)
if fertilizer:
tmp = b[random.randint(0, 2)].resize(
(20 + random.randint(0, 25), 20 + random.randint(0, 25))).rotate(random.randint(0, 359))
new_im.paste(tmp, (random.randint(25, 75), random.randint(25, 75)), tmp)
else:
if fertilizer:
new_im = Image.new('RGB', (100, 100),
(
50 + random.randint(-10, 10), 25 + random.randint(-10, 10),
0 + random.randint(-10, 10)))
tmp = plants[plantf][random.randint(0, 2)].resize(
(25 + random.randint(-10, 25), 25 + random.randint(-10, 25))).rotate(random.randint(0, 359))
new_im.paste(tmp, (random.randint(0, 50), random.randint(0, 50)), tmp)
tmp = b[random.randint(0, 2)].resize(
(20 + random.randint(0, 25), 20 + random.randint(0, 25))).rotate(random.randint(0, 359))
new_im.paste(tmp, (random.randint(25, 75), random.randint(25, 75)), tmp)
else:
if random.randint(0, 1) == 1:
new_im = Image.new('RGB', (100, 100),
(50 + random.randint(-10, 10), 25 + random.randint(-10, 10),
0 + random.randint(-10, 10)))
else:
new_im = Image.new('RGB', (100, 100),
(160 + random.randint(-10, 10), 80 + random.randint(-10, 10),
40 + random.randint(-10, 10)))
if random.randint(0, 1) == 1: # big
tmp = plants[plantf][random.randint(0, 2)].resize(
(75 + random.randint(-10, 25), 75 + random.randint(-10, 25))).rotate(random.randint(0, 359))
new_im.paste(tmp, (random.randint(0, 15), random.randint(0, 15)), tmp)
else:
tmp = plants[plantf][random.randint(0, 2)].resize(
(random.randint(10, 80), random.randint(10, 80))).rotate(random.randint(0, 359))
datas = tmp.getdata()
new_image_data = []
for item in datas:
# change all white (also shades of whites) pixels to yellow
if item[0] in list(range(190, 256)):
new_image_data.append(
(random.randint(0, 10), 255 + random.randint(-150, 0), random.randint(0, 10)))
else:
new_image_data.append(item)
# update image data
tmp.putdata(new_image_data)
new_im.paste(tmp, (random.randint(0, 30), random.randint(0, 30)), tmp)
return new_im
# COLORS
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0, 0)
BLUE = (0, 0, 255, 0)
GREY = (128, 128, 128)
CLOCK = pygame.time.Clock()
FPS = 30
DELAY = 300
# np. 10 pól x 10 pól = 100 pól
GRIDX = 10
GRIDY = 10
obstacleObjects = {} # Store the obstacle objects (Blocks on the path) from Obstacle class
# global gridObjects
# gridObjects = {} # Store grid-box objects from Grid Class
gridObstacle = {} # Store the grid:obstacle pair stuck together
boxObjects = {}
boxes = 1
obstacles = 1
# BFS Variables
startNode = Istate(1, 1, 1)
goalNode = [1, 1]
graph = dict()
pathFound = [] # Store the path in a list box index to draw on later
def drawGrid(sizex, sizey):
spaceX = SCREENX // sizex
spaceY = SCREENY // sizey
counter = 1
for i in range(sizex):
for j in range(sizey):
# g = Grid(i*spaceX, j*spaceY, spaceX, spaceY)
g = Grid(50 + i*50, 50 + j*50, spaceX, spaceY)
gridObjects[counter] = g
counter += 1
def generateGraph(row, col):
# This function generates a graph based on the gridObjects instantiated!
# sample_graph = {'A': ['B', 'C', 'E'],
# 'B': ['A', 'D', 'E'],
# 'C': ['A', 'F', 'G'],
# 'D': ['B'],
# 'E': ['A', 'B', 'D'],
# 'F': ['C'],
# 'G': ['C']
# }
miniG = {}
for grid in range(len(gridObjects)):
grid += 1 # Synchronize index
mod = grid % col # Used to check the Top and Bottom Grid Boxes!
gN = grid - 1
gS = grid + 1
gE = grid + col
gW = grid - col
# CHECK THE NEIGHBORS TO THE GRID-BOXES, ACCOUNTING FOR THE EXTREME GRID-BOXES(BORDERS)
if mod == 0: # 5,10,15,20,25 - You can't go south from here (Bottom Boxes)
if grid > col: # Away from the Left Border of the Screen
if grid > (col*row)-col: # You are on the Right Border of the screen - You can't go East
miniG[grid] = [gN, gW]
else: # Away from the Right Border of the Screen - You can go East
miniG[grid] = [gN, gE, gW]
else: # You are on the Left Edge of the screen - You can't go West
miniG[grid] = [gN, gE]
elif mod == 1: # 6,11,16,21 :> You can't go North from here (Top Boxes)
if grid > col: # Away from the Left Border of the Screen
if grid > (col*row)-col: # You are on the Right Border of the screen - You can't go East
miniG[grid] = [gS, gW]
else: # Away from the Right Border of the Screen - You can go east
miniG[grid] = [gS, gE, gW]
else: # You are on the Left Edge of the screen - You can't go West
miniG[grid] = [gS, gE]
else: # All the rest (Not Top or Bottom Boxes) - You can go North or South
if grid > col: # Away from the Left Border of the Screen
if grid > (col*row)-col: # You are on the Right Border of the screen - You can't go East
miniG[grid] = [gN, gS, gW]
else: # Away from the Right Border of the Screen - You can go East
miniG[grid] = [gN, gS, gE, gW]
else: # You are on the Left Edge of the screen - You can't go West
miniG[grid] = [gN, gS, gE]
# FILTER OUT OBSTACLES FROM THE GRAPH
miniG2 = {}
for grid in range(len(gridObjects)):
grid += 1
if grid not in gridObstacle:
# gridObjects.remove(grid) # Dict object has no attribute : 'remove'
# HACK
miniG2[grid] = miniG[grid] # Created a new dictionary that stored the values required
# IN-DEPTH FILTER - Filter out obstacles from the neighbors-list
for neigbor in miniG2[grid]:
if neigbor in gridObstacle:
miniG2[grid].remove(neigbor)
# Filtering again as the first Filter block didn't clear out everything
# Filtering through the neighbors
for grid in miniG2:
for item in miniG2[grid]:
if item in gridObstacle:
miniG2[grid].remove(item)
return miniG2
def drawGraph(pathF):
# Draws the path given the path-list
global Ucelu
# print(pathF)
if not Ucelu:
for grid in pathF:
# g = gridObjects[grid] # Get the grid-box object mentioned in the path
# x = g.x
# y = g.y
# sx = g.sx
# sy = g.sy
# a = 0
# pygame.draw.rect(SCREEN, GREEN, pygame.Rect(x, y, sx, sy))
if grid == 'rotate_right':
player.rotation = (player.rotation - 90) % 360
if grid == 'rotate_left':
player.rotation = (player.rotation + 90) % 360
# (player.rotation)
if grid == 'move':
if player.rotation == 0:
if player.x < 9:
player.x = player.x + 1
if player.rotation == 180:
if player.x > 0:
player.x = player.x - 1
if player.rotation == 270:
if player.y < 9:
player.y = player.y + 1
if player.rotation == 90:
if player.y > 0:
player.y = player.y - 1
i = 0
while i < len(T):
j = 0
while j < len(T[i]):
# color = (255, 255, 255, 0)
if T[i][j].isWet == 0:
# a = 1
color = (160, 80, 40, 0)
else:
# a = 1
color = (50, 25, 0, 0)
# Covers 'player' on the way
pygame.draw.rect(SCREEN, color, pygame.Rect(50 + 50 * i, 50 + 50 * j, 50, 50))
if T[i][j].plantType == 1:
SCREEN.blit(imgWheat, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 2:
SCREEN.blit(imgCarrot, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 3:
SCREEN.blit(imgCabbage, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 4:
SCREEN.blit(imgTree, (50 + 50 * i, 50 + 50 * j))
j = j + 1
i = i + 1
# Render the trees
for obs in obstacleObjects:
obstacleObjects[obs].draw()
for bx in boxObjects:
boxObjects[bx].draw()
i = 0
while i < len(T)+1:
pygame.draw.line(SCREEN, (0, 0, 0), (50 + i * 50, 50), (50 + i * 50, 50 + len(T) * 50), 1)
pygame.draw.line(SCREEN, (0, 0, 0), (50, 50 + i * 50), (50 + len(T) * 50, 50 + i * 50), 1)
i = i + 1
tmpImg = pygame.transform.rotate(imgPlayer, player.rotation)
if player.rotation == 180:
tmpImg = pygame.transform.flip(tmpImg, True, True)
tmpImg = pygame.transform.flip(tmpImg, True, False)
# player is seen on the way
SCREEN.blit(tmpImg, (55 + 50 * player.x, 55 + 50 * player.y))
pygame.display.update()
pygame.time.wait(300)
SCREEN.fill(WHITE)
# pygame.time.wait(50)
# pygame.draw.rect(SCREEN, WHITE, pygame.Rect(x, y, sx, sy))
Ucelu = True
def UIHandler():
# drawGrid(GRIDX, GRIDY)
global Ucelu
drawGrid(10, 10)
for grid in gridObjects:
gridObjects[grid].draw()
for bx in boxObjects:
boxObjects[bx].draw()
for obs in obstacleObjects:
obstacleObjects[obs].draw()
if pathFound:
drawGraph(pathFound)
def eventHandler(kbdObj, mouseObj):
global boxes
global obstacles
global startNode
global goalNode
global pathFound
global Ucelu
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
pygame.time.wait(DELAY)
if event.key == pygame.K_LEFT:
if player.x > 0:
player.x = player.x - 1
player.rotation = 180
if event.key == pygame.K_UP:
if player.y > 0:
player.y = player.y - 1
player.rotation = 90
if event.key == pygame.K_RIGHT:
if player.x < 9:
player.x = player.x + 1
player.rotation = 0
if event.key == pygame.K_DOWN:
if player.y < 9:
player.y = player.y + 1
player.rotation = 270
# Aga start lewo prawo, naprzód
if event.key == pygame.K_a:
player.rotation = (player.rotation + 90) % 360
if event.key == pygame.K_d:
player.rotation = (player.rotation - 90) % 360
if event.key == pygame.K_w:
if player.rotation == 0:
if player.x < 9:
player.x = player.x + 1
if player.rotation == 180:
if player.x > 0:
player.x = player.x - 1
if player.rotation == 270:
if player.y < 9:
player.y = player.y + 1
if player.rotation == 90:
if player.y > 0:
player.y = player.y - 1
# If Key_f is pressed, set goal node
if kbdObj[pygame.K_f]:
gBox = getGridBoxes(int(len(gridObjects)))
sx = gBox.sx
sy = gBox.sy
mseX = mouseObj[0]
mseY = mouseObj[1]
for grid in gridObjects:
g = getGridBoxes(grid)
x = g.x
y = g.y
sx = g.sx
sy = g.sy
if x < mseX < x + sx:
if y < mseY < y + sy:
posX = x
posY = y
bo = Box(posX, posY, sx, sy, BLUE)
boxObjects[boxes] = bo
# boxes += 1
boxes = 1
# goalNode = GRIDX*GRIDX
# goalNode = (10 * (x + 1) + (y + 1) - 10)
# goalNode.state = int(10 * (posX/50 ) + (posY/50) - 10)
# goalNode[0] = int((posX/50)
# goalNode[1] = int(posY/50) - 10
goalNode = [int(posX/50), int(posY/50)]
# goalNode = [10,10]
print(' goalNode x=', goalNode[0], 'goalNode y=', goalNode[1])
# pygame.display.update()
# goalNode = (x/sx) * (y/sy)
# Delay to avoid multiple spawning of objects
pygame.time.wait(DELAY)
# If Key_x is pressed, spawn tree
if kbdObj[pygame.K_t]:
w = random.randint(0, 1)
f = random.randint(0, 1)
print(w)
print(f)
img = generate(w, f, random.randint(0, 2))
img.save('./test/00/test.png')
data_transform = transforms.Compose([
transforms.Resize(size=(100, 100)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ToTensor(),
Lambda(lambda x: x.flatten())
])
datasets.ImageNet
train_data = datasets.ImageFolder(root="./test",
transform=data_transform,
target_transform=None)
model1.eval()
res = model1(train_data[0][0])
if res[0] == res.max():
print("0 0")
if res[1] == res.max():
print("0 1")
if res[2] == res.max():
print("1 0")
if res[3] == res.max():
print("1 1")
# img.show()
if kbdObj[pygame.K_x]:
obs = Obstacle(mouseObj)
obstacleObjects[obstacles] = obs
# print(obs.gridBox)
obstacles += 1
# print(obstacleObjects)
gridObstacle[obs.gridBox] = obstacles
# Delay to avoid multiple spawning of objects
mseX = mouseObj[0]
mseY = mouseObj[1]
for grid in gridObjects:
g = getGridBoxes(grid)
x = g.x
y = g.y
sx = g.sx
sy = g.sy
if x < mseX < x + sx:
if y < mseY < y + sy:
posX = x
posY = y
T[int((posX/50)-1)][int((posY/50)-1)].plantType = 4
pygame.display.update()
pygame.time.wait(DELAY)
# if Key_SPACE is pressed, start the magic
if kbdObj[pygame.K_SPACE]:
Ucelu = False
boxes = 1
startNode.x = player.x + 1
startNode.y = player.y + 1
if player.rotation == 0:
startNode.direction = 1
elif player.rotation == 90:
startNode.direction = 2
elif player.rotation == 180:
startNode.direction = 3
elif player.rotation == 270:
startNode.direction = 4
print(' startNode x=', startNode.x, 'startNode y= ', startNode.y, 'startNode direction =', startNode.direction)
graph = generateGraph(GRIDY, GRIDX)
print(graph)
if startNode.x != goalNode[0] or startNode.y != goalNode[1]:
move_list = (graphsearch(goalNode, startNode)) # przeszukiwanie grafu wszerz
pathFound = move_list
# pathFound = bfs.graphsearch()
print('akcje które wykonuję by znalezc sie u celu')
print(move_list)
print('\n')
# Delay to avoid multiple spawning of objects
pygame.time.wait(DELAY)
# startNode = goalNode
if kbdObj[pygame.K_b]:
Ucelu = False
boxes = 1
startNode.x = player.x + 1
startNode.y = player.y + 1
if player.rotation == 0:
startNode.direction = 1
elif player.rotation == 90:
startNode.direction = 2
elif player.rotation == 180:
startNode.direction = 3
elif player.rotation == 270:
startNode.direction = 4
print(' startNode x=', startNode.x, 'startNode y= ', startNode.y, 'startNode direction =', startNode.direction)
graph = generateGraph(GRIDY, GRIDX)
print(graph)
# if startNode != goalNode:
if startNode.x != goalNode[0] or startNode.y != goalNode[1]:
move_list = (astar.graphsearch([], astar.f, [], goalNode, startNode, T, succ)) # przeszukiwanie grafu wszerz
pathFound = move_list
# pathFound = bfs.graphsearch()
print('akcje które wykonuję by znalezc sie u celu')
print(move_list)
print('\n')
# Delay to avoid multiple spawning of objects
pygame.time.wait(DELAY)
# With it it keeps going, if without it turns off
T = [[Field(1,0,0,0,0,0),Field(0,0,1,0,0,0),Field(1,2,1,0,0,0),Field(1,3,0,0,0,0),Field(0,3,0,0,0,0),Field(0,0,1,0,0,0),Field(0,0,0,0,0,0),Field(1,3,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,0,0,0,0),Field(0,0,1,0,0,0),Field(1,0,1,0,0,0),Field(1,3,1,0,0,0),Field(0,0,0,0,0,0),Field(0,0,0,0,0,0),Field(0,0,0,0,0,0),Field(1,3,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(0,2,1,0,0,0),Field(0,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,0,0,0,0),Field(0,2,1,0,0,0),Field(0,1,1,0,0,0),Field(0,0,0,0,0,0),Field(1,3,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,1,0,0,0),Field(0,0,1,0,0,0),Field(1,1,1,0,0,0),Field(1,2,0,0,0,0),Field(0,3,1,0,0,0),Field(0,1,0,0,0,0),Field(0,0,0,0,0,0),Field(1,3,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,3,0,0,0,0),Field(0,3,1,0,0,0),Field(1,2,1,0,0,0),Field(1,1,1,0,0,0),Field(0,1,1,0,0,0),Field(0,0,0,0,0,0),Field(0,0,0,0,0,0),Field(1,3,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,0,0,0,0),Field(0,0,1,0,0,0),Field(1,1,1,0,0,0),Field(1,2,0,0,0,0),Field(0,1,0,0,0,0),Field(0,2,0,0,0,0),Field(0,1,0,0,0,0),Field(1,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,0,0,0,0),Field(0,0,0,0,0,0),Field(1,1,1,0,0,0),Field(1,2,0,0,0,0),Field(0,0,1,0,0,0),Field(0,0,1,0,0,0),Field(0,1,0,0,0,0),Field(1,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,0,0,0,0),Field(0,0,1,0,0,0),Field(1,3,1,0,0,0),Field(1,2,1,0,0,0),Field(0,0,1,0,0,0),Field(0,0,0,0,0,0),Field(0,1,0,0,0,0),Field(1,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,0,0,0,0),Field(0,2,0,0,0,0),Field(1,1,0,0,0,0),Field(1,0,1,0,0,0),Field(0,2,1,0,0,0),Field(0,3,0,0,0,0),Field(0,0,0,0,0,0),Field(1,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)],
[Field(1,0,1,0,0,0),Field(0,0,0,0,0,0),Field(1,1,1,0,0,0),Field(1,0,0,0,0,0),Field(0,1,1,0,0,0),Field(0,0,1,0,0,0),Field(0,0,0,0,0,0),Field(1,0,1,0,0,0),Field(1,0,0,0,0,0),Field(1,0,1,0,0,0)]]
# =========================================================================================
# no i tutaj mamy główna pętlę programu
pygame.init()
player = Player()
running = True
# clock = pygame.time.Clock()
SCREEN.fill(WHITE)
while running:
for event in pygame.event.get():
kbd = pygame.key.get_pressed()
mse = pygame.mouse.get_pos()
UIHandler()
eventHandler(kbd, mse)
pygame.display.update()
# CLOCK.tick(FPS)
# screen.fill((175, 255, 50, 0))
# SCREEN.fill((WHITE))
imgWheat = pygame.image.load('img/wheat.png')
imgCarrot = pygame.image.load('img/carrot.png')
imgCabbage = pygame.image.load('img/cabbage.png')
imgPlayer = pygame.image.load('img/player.png')
global imgTree
imgTree = pygame.image.load('img/tree.png')
# pygame.display.update()
i = 0
while i < len(T):
j = 0
while j < len(T[i]):
# color = (255, 255, 255, 0)
if T[i][j].isWet == 0:
# a = 1
color = (160, 80, 40, 0)
else:
# a = 1
color = (50, 25, 0, 0)
# colour from the beginning
pygame.draw.rect(SCREEN, color, pygame.Rect(50 + 50 * i, 50 + 50 * j, 50, 50))
if T[i][j].plantType == 1:
SCREEN.blit(imgWheat, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 2:
SCREEN.blit(imgCarrot, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 3:
SCREEN.blit(imgCabbage, (50 + 50 * i, 50 + 50 * j))
if T[i][j].plantType == 4:
SCREEN.blit(imgTree, (50 + 50 * i, 50 + 50 * j))
j = j + 1
i = i + 1
i = 0
while i < len(T)+1:
pygame.draw.line(SCREEN, (0, 0, 0), (50 + i * 50, 50), (50 + i * 50, 50 + len(T) * 50), 1)
pygame.draw.line(SCREEN, (0, 0, 0), (50, 50 + i * 50), (50 + len(T) * 50, 50 + i * 50), 1)
i = i + 1
for obs in obstacleObjects:
obstacleObjects[obs].draw()
# if startNode.state != goalNode.state:
if startNode.x != goalNode[0] or startNode.y != goalNode[1]:
for bx in boxObjects:
boxObjects[bx].draw()
tmpImg = pygame.transform.rotate(imgPlayer, player.rotation)
if player.rotation == 180:
tmpImg = pygame.transform.flip(tmpImg, True, True)
tmpImg = pygame.transform.flip(tmpImg, True, False)
# player seen at the beginning
SCREEN.blit(tmpImg, (55 + 50 * player.x, 55 + 50 * player.y))
font = pygame.font.SysFont('comicsans', 22)
label = font.render('F - cel | X - drzewo', True, (0, 0, 0))
label1 = font.render('ARROWS - ręczne poruszanie', True, (0, 0, 0))
label2 = font.render('A - lewo | D - prawo | W - ruch', True, (0, 0, 0))
label3 = font.render('SPACE - start BFS | B - start A*', True, (0, 0, 0))
SCREEN.blit(label, (10, 555))
SCREEN.blit(label1, (10, 580))
SCREEN.blit(label2, (10, 605))
SCREEN.blit(label3, (10, 630))
# pygame.display.flip()
pygame.display.update()
CLOCK.tick(FPS)
# Done! Time to quit.
pygame.quit()