Add working snake
This commit is contained in:
commit
abbfac7573
7 changed files with 491 additions and 0 deletions
199
.gitignore
vendored
Normal file
199
.gitignore
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/python,pycharm+all,osx
|
||||
|
||||
### OSX ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### PyCharm+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Ruby plugin and RubyMine
|
||||
/.rakeTasks
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### PyCharm+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule.*
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/python,pycharm+all,osx
|
12
Pipfile
Normal file
12
Pipfile
Normal file
|
@ -0,0 +1,12 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
pygame = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
46
Pipfile.lock
generated
Normal file
46
Pipfile.lock
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "04d5136a2e3e1a7589c6313b58b439879c213afd077d2d6580213f4255dcef7d"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"pygame": {
|
||||
"hashes": [
|
||||
"sha256:22d5195a72b9a300cb7e87d7bde1373930a02b1054e95bc431d26a321ae15d07",
|
||||
"sha256:2a8292d0b5a67e0b96d2cae09bbd8252e99daffe95fd88a00166b92fb6497b40",
|
||||
"sha256:2ee479b0120cc82f04f7c41a09d75fc85c9992ed1a26c9527fb2b7b0637e6739",
|
||||
"sha256:4847550da37d3bc702e0201a035de005f184b19e86625a3c70ab1d318317ee91",
|
||||
"sha256:4d4985d0e0e5470c5ef872d65a863f1722d85a136327eb3bb5dcafc2853f2e55",
|
||||
"sha256:5cf670378c8aabdf1bdf283eeec690c93f95dfe8de77c66bc837c91274c8ca49",
|
||||
"sha256:643de45518d11cb7784e3fe77aef43fe3cb7e0d6b495ad4361ade931f16c334b",
|
||||
"sha256:66746c32d21d8193ab066fdf90da96ed993c4818adb13bf4eaa472f306f7f421",
|
||||
"sha256:73c86fdbbfc5ae77d0271fb0964f4a3fc639cc2277ffeb60ee4e981fb0d27640",
|
||||
"sha256:751021819bdc0cbe5cbd51904abb6ff9e9aee5b0e8955af02284d0e77d6c9ec2",
|
||||
"sha256:792f8d1a455d5d1de9c8f2cc051c6dac2b90705afc4cf9fe4933aeb296b0ae3f",
|
||||
"sha256:831c906d4a70aa58f9382e79671525805583b58ac8c895b4c4bd066e8858a1a1",
|
||||
"sha256:834ee27388563f15e6c736aa4850e4cf95609f67056ee73ff646528bb658e79e",
|
||||
"sha256:925e6be8e8c3f1cec016f5a68f369491ae34600e04633759e0e07780ce7bd083",
|
||||
"sha256:96a8f61c729d82576ee3d5cfe3b0b91e7b52a4fbf3d618a3b659347eeb4fd937",
|
||||
"sha256:97615fb075808b4709f6115e3a11a253bfac78614f430bededbc0ac76a5100af",
|
||||
"sha256:c49ece0e4ba71be90ed4bf6a905c35e5c1a95c4c0b0b42eaff74ab69c5793847",
|
||||
"sha256:e8626d4d4e7617ebf103d96a493b9cbe53f9b39cd46ecbff232e33ba4a082530",
|
||||
"sha256:ea7da8dd7fbc12becccf691b4c76fbb381e369e124c30e21f9ed263b6e7e139a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.9.3"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
10
config.py
Normal file
10
config.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
BACKGROUND_COLOR = 0, 0, 0
|
||||
APPLE_COLOR = 255, 0, 0
|
||||
SNAKE_COLOR = 0, 255, 0
|
||||
FONT_COLOR = 250, 250, 250
|
||||
|
||||
MAP_SIZE = 41, 31
|
||||
TILE_SIZE = 20
|
||||
RESOLUTION = MAP_SIZE[0] * TILE_SIZE, MAP_SIZE[1] * TILE_SIZE
|
||||
|
||||
INITIAL_SNAKE_SIZE = 3
|
98
main.py
Normal file
98
main.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
import logging
|
||||
|
||||
import pygame
|
||||
from pygame import locals as pglocals
|
||||
|
||||
from config import RESOLUTION, SNAKE_COLOR, BACKGROUND_COLOR, FONT_COLOR, INITIAL_SNAKE_SIZE
|
||||
from objects import Snake, Apple
|
||||
from utils import get_score_text, Direction
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
pygame.init()
|
||||
|
||||
|
||||
def main():
|
||||
clock = pygame.time.Clock()
|
||||
logger.debug('pygame initialized')
|
||||
|
||||
if not pygame.font:
|
||||
logger.warning('Fonts disabled')
|
||||
if not pygame.mixer:
|
||||
logger.warning('Sound disabled')
|
||||
|
||||
screen = pygame.display.set_mode(RESOLUTION) # type: pygame.Surface
|
||||
pygame.display.set_caption('Snake')
|
||||
|
||||
snake = Snake()
|
||||
screen.fill(SNAKE_COLOR, snake.head)
|
||||
apple = Apple()
|
||||
apple.display(screen)
|
||||
|
||||
score = 0
|
||||
score_text, score_rect = get_score_text(score)
|
||||
screen.blit(score_text, score_rect)
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pglocals.QUIT:
|
||||
logger.warning('Received QUIT event')
|
||||
return
|
||||
if event.type == pglocals.KEYDOWN:
|
||||
if event.key == pglocals.K_DOWN:
|
||||
snake.direction = Direction.DOWN
|
||||
elif event.key == pglocals.K_LEFT:
|
||||
snake.direction = Direction.LEFT
|
||||
elif event.key == pglocals.K_UP:
|
||||
snake.direction = Direction.UP
|
||||
elif event.key == pglocals.K_RIGHT:
|
||||
snake.direction = Direction.RIGHT
|
||||
|
||||
dirty_rects = snake.move(screen, apple)
|
||||
if snake.dead:
|
||||
logger.info(f'Vous avez perdu ! Score : {score}')
|
||||
break
|
||||
|
||||
if snake.head.colliderect(apple.rect):
|
||||
apple.renew()
|
||||
score += apple.score
|
||||
logger.info(f'Apple eaten, new score : {score}')
|
||||
|
||||
screen.fill(BACKGROUND_COLOR, score_rect)
|
||||
old_score_rect = score_rect
|
||||
|
||||
score_text, score_rect = get_score_text(score)
|
||||
screen.blit(score_text, score_rect)
|
||||
dirty_rects.append(score_rect.union(old_score_rect))
|
||||
|
||||
dirty_rects.append(apple.display(screen))
|
||||
|
||||
pygame.display.update(dirty_rects)
|
||||
|
||||
# Run faster as snake grows
|
||||
clock.tick(10 - INITIAL_SNAKE_SIZE + len(snake.slots))
|
||||
|
||||
screen.fill(BACKGROUND_COLOR)
|
||||
|
||||
font = pygame.font.Font(None, 60)
|
||||
text = font.render(f"PERDU ! Score : {score}", 1, FONT_COLOR)
|
||||
text_rect = text.get_rect() # type: pygame.Rect
|
||||
text_rect.center = screen.get_rect().center
|
||||
|
||||
screen.blit(text, text_rect)
|
||||
pygame.display.flip()
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pglocals.QUIT:
|
||||
logger.info('Received QUIT event')
|
||||
return
|
||||
|
||||
clock.tick(5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
81
objects.py
Normal file
81
objects.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import logging
|
||||
|
||||
import pygame
|
||||
|
||||
from config import INITIAL_SNAKE_SIZE, RESOLUTION, TILE_SIZE, SNAKE_COLOR, BACKGROUND_COLOR, APPLE_COLOR
|
||||
from utils import make_slot, Direction, random_slot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Snake:
|
||||
def __init__(self, ):
|
||||
self.slots = [make_slot(21, 16)] * INITIAL_SNAKE_SIZE
|
||||
self._direction = None # type: Direction
|
||||
self.dead = False
|
||||
|
||||
def add_slot(self, left: int, top: int):
|
||||
self.slots.append(make_slot(left, top))
|
||||
|
||||
def move(self, screen: pygame.Surface, apple: 'Apple'):
|
||||
if self.direction is None:
|
||||
return []
|
||||
|
||||
new_head = self.slots[0].move(*self.direction.value)
|
||||
|
||||
if new_head.collidelist(self.slots[1:]) > -1:
|
||||
self.dead = True
|
||||
return []
|
||||
|
||||
if new_head.right > RESOLUTION[0]:
|
||||
new_head = pygame.Rect(0, new_head.top, TILE_SIZE, TILE_SIZE)
|
||||
elif new_head.left < 0:
|
||||
new_head = pygame.Rect(RESOLUTION[0] - TILE_SIZE, new_head.top, TILE_SIZE, TILE_SIZE)
|
||||
if new_head.bottom > RESOLUTION[1]:
|
||||
new_head = pygame.Rect(new_head.left, 0, TILE_SIZE, TILE_SIZE)
|
||||
elif new_head.top < 0:
|
||||
new_head = pygame.Rect(new_head.left, RESOLUTION[1] - TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
||||
|
||||
self.slots.insert(0, new_head)
|
||||
screen.fill(SNAKE_COLOR, new_head)
|
||||
|
||||
if not new_head.colliderect(apple.rect):
|
||||
old_tail = self.slots.pop()
|
||||
screen.fill(BACKGROUND_COLOR, old_tail)
|
||||
return [old_tail, new_head]
|
||||
|
||||
return [new_head]
|
||||
|
||||
@property
|
||||
def head(self) -> pygame.Rect:
|
||||
return self.slots[0]
|
||||
|
||||
@head.setter
|
||||
def head(self, value: pygame.Rect):
|
||||
self.slots.pop(0)
|
||||
self.slots.insert(0, value)
|
||||
|
||||
@property
|
||||
def direction(self) -> Direction:
|
||||
return self._direction
|
||||
|
||||
@direction.setter
|
||||
def direction(self, value: Direction):
|
||||
if not value.is_opposed_to(self.direction):
|
||||
self._direction = value
|
||||
else:
|
||||
logger.debug('Move prohibited : tried to change to an opposed direction')
|
||||
|
||||
|
||||
class Apple:
|
||||
def __init__(self):
|
||||
self.rect = random_slot()
|
||||
self.score = 10
|
||||
|
||||
def display(self, screen: pygame.Surface) -> pygame.Rect:
|
||||
screen.fill(APPLE_COLOR, self.rect)
|
||||
return self.rect
|
||||
|
||||
def renew(self):
|
||||
self.rect = random_slot()
|
||||
logger.debug(f'Apple generated at {self.rect.left / TILE_SIZE} {self.rect.top / TILE_SIZE}')
|
45
utils.py
Normal file
45
utils.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import random
|
||||
from enum import Enum
|
||||
|
||||
import pygame
|
||||
|
||||
from config import FONT_COLOR, RESOLUTION, MAP_SIZE, TILE_SIZE
|
||||
|
||||
|
||||
def get_score_text(score):
|
||||
font = pygame.font.Font(None, 30)
|
||||
text = font.render(f"Score : {score}", 1, FONT_COLOR)
|
||||
text_rect = text.get_rect() # type: pygame.Rect
|
||||
text_rect.left = 0
|
||||
text_rect.bottom = RESOLUTION[1]
|
||||
return text, text_rect
|
||||
|
||||
|
||||
def make_slot(left: int, top: int) -> pygame.Rect:
|
||||
if left >= MAP_SIZE[0] or left < 0:
|
||||
raise ValueError(f'left must be between 0 and {MAP_SIZE[0]}')
|
||||
if top >= MAP_SIZE[1] or top < 0:
|
||||
raise ValueError(f'top must be between 0 and {MAP_SIZE[1]}')
|
||||
|
||||
return pygame.Rect(left * TILE_SIZE, top * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
||||
|
||||
|
||||
def random_slot() -> pygame.Rect:
|
||||
return make_slot(random.randint(0, MAP_SIZE[0] - 1), random.randint(0, MAP_SIZE[1] - 1))
|
||||
|
||||
|
||||
class Direction(Enum):
|
||||
UP = 0, -TILE_SIZE
|
||||
RIGHT = TILE_SIZE, 0
|
||||
DOWN = 0, TILE_SIZE
|
||||
LEFT = -TILE_SIZE, 0
|
||||
|
||||
@staticmethod
|
||||
def are_opposed(d1, d2):
|
||||
return (d1 == Direction.UP and d2 == Direction.DOWN
|
||||
or d1 == Direction.DOWN and d2 == Direction.UP
|
||||
or d1 == Direction.LEFT and d2 == Direction.RIGHT
|
||||
or d1 == Direction.RIGHT and d2 == Direction.LEFT)
|
||||
|
||||
def is_opposed_to(self, other):
|
||||
return Direction.are_opposed(self, other)
|
Loading…
Reference in a new issue