From 567caccdbd027509dc15210e011ed7709132220d Mon Sep 17 00:00:00 2001 From: Alexander Gusakov Date: Wed, 22 Jul 2020 17:04:37 +0300 Subject: [PATCH] First release --- .gitignore | 129 ++++ .travis.yml | 15 + LICENSE | 21 + README.md | 101 ++++ poetry.lock | 293 ++++++++++ pyproject.toml | 20 + setup.cfg | 4 + .../menu_files/console_img/byebye.txt | 19 + .../menu_files/console_img/controls.txt | 23 + .../menu_files/console_img/loading.txt | 20 + .../menu_files/console_img/snake.txt | 24 + .../extra/game_environment/menu_files/menu.py | 268 +++++++++ .../score_files/records/date_n_time.txt | 0 .../score_files/records/players.txt | 0 .../score_files/records/scores.txt | 0 .../game_environment/score_files/score.py | 113 ++++ snake_project/extra/tools/asker.py | 49 ++ .../tools/clean_console_command_setter.py | 10 + snake_project/extra/tools/console_cleaner.py | 10 + snake_project/extra/tools/git_repo_opener.py | 10 + snake_project/extra/tools/key_setup.py | 28 + snake_project/extra/tools/linux_functions.py | 17 + snake_project/extra/tools/os_initializer.py | 6 + snake_project/extra/tools/validators.py | 166 ++++++ snake_project/gamemodes/battle_mode.py | 552 ++++++++++++++++++ snake_project/gamemodes/classic_mode.py | 329 +++++++++++ snake_project/gamemodes/survival_mode.py | 550 +++++++++++++++++ snake_project/snake_game.py | 43 ++ snake_project/tests/__init__.py | 0 snake_project/tests/test_game_records.py | 29 + snake_project/tests/test_gamemodes.py | 451 ++++++++++++++ 31 files changed, 3300 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 snake_project/extra/game_environment/menu_files/console_img/byebye.txt create mode 100644 snake_project/extra/game_environment/menu_files/console_img/controls.txt create mode 100644 snake_project/extra/game_environment/menu_files/console_img/loading.txt create mode 100644 snake_project/extra/game_environment/menu_files/console_img/snake.txt create mode 100644 snake_project/extra/game_environment/menu_files/menu.py create mode 100644 snake_project/extra/game_environment/score_files/records/date_n_time.txt create mode 100644 snake_project/extra/game_environment/score_files/records/players.txt create mode 100644 snake_project/extra/game_environment/score_files/records/scores.txt create mode 100644 snake_project/extra/game_environment/score_files/score.py create mode 100644 snake_project/extra/tools/asker.py create mode 100644 snake_project/extra/tools/clean_console_command_setter.py create mode 100644 snake_project/extra/tools/console_cleaner.py create mode 100644 snake_project/extra/tools/git_repo_opener.py create mode 100644 snake_project/extra/tools/key_setup.py create mode 100644 snake_project/extra/tools/linux_functions.py create mode 100644 snake_project/extra/tools/os_initializer.py create mode 100644 snake_project/extra/tools/validators.py create mode 100644 snake_project/gamemodes/battle_mode.py create mode 100644 snake_project/gamemodes/classic_mode.py create mode 100644 snake_project/gamemodes/survival_mode.py create mode 100644 snake_project/snake_game.py create mode 100644 snake_project/tests/__init__.py create mode 100644 snake_project/tests/test_game_records.py create mode 100644 snake_project/tests/test_gamemodes.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# 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/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c761685 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: python + +dist: bionic + +python: + - "3.8" + +install: + - pip install poetry + - poetry install + +script: + - pytest -s + - flake8 snake_project + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b0703f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alexander Gusakov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b88ab5e --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +``` +:'######::'##::: ##::::'###::::'##:::'##:'########:::::::'##::::'#######::'########::'#######:: +'##... ##: ###:: ##:::'## ##::: ##::'##:: ##.....::::::'####:::'##.... ##: ##.. ##:'##.... ##: + ##:::..:: ####: ##::'##:. ##:: ##:'##::: ##:::::::::::.. ##::: ##:::: ##:..:: ##::: ##::::..:: +. ######:: ## ## ##:'##:::. ##: #####:::: ######::::::::: ##:::: ########:::: ##:::: ########:: +:..... ##: ##. ####: #########: ##. ##::: ##...:::::::::: ##::::...... ##::: ##::::: ##.... ##: +'##::: ##: ##:. ###: ##.... ##: ##:. ##:: ##::::::::::::: ##:::'##:::: ##::: ##::::: ##:::: ##: +. ######:: ##::. ##: ##:::: ##: ##::. ##: ########:::::'######:. #######:::: ##:::::. #######:: +:......:::..::::..::..:::::..::..::::..::........::::::......:::.......:::::..:::::::.......::: +``` + +[![Build Status](https://travis-ci.org/GonnaFlyMethod/snake1976.svg?branch=master)](https://travis-ci.org/github/GonnaFlyMethod/snake1976) +[![flake8 Status](https://img.shields.io/badge/flake8-enabled-green)](https://github.com/GonnaFlyMethod/snake1976/blob/master/setup.cfg) +[![Supporting OS](https://img.shields.io/badge/OS-Linux%20%7C%20Windows-blue)](https://github.com/GonnaFlyMethod/snake1976) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/GonnaFlyMethod/snake1976/blob/master/LICENSE) +[![Join the chat at https://gitter.im/snake1976/community](https://badges.gitter.im/snake1976/community.svg)](https://gitter.im/snake1976/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +--- + +Snake 1976 is a console game that follows the logic of the classic snake.The game also contains some new game modes and features. Enjoy the game! :snake: + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +## Requirements + +``` +Python (3.8), pip, poetry +``` + +## Installing + +### Clone the project to your local machine + +``` +git clone https://github.com/GonnaFlyMethod/snake1976 +``` + +### Install poetry + +``` +pip install poetry +``` + +### Install all dependencies + +You need to get to the directory with project 'snake1976' and then write in the console: + +``` +poetry install +``` +### Install flake8 pre-commit hook + +``` +flake8 --install-hook git +git config --bool flake8.strict true +``` +Now flake8 will respond to each of your commits. It's necessary to make your code cleaner. +If you see errors then make sure that you write these commands while you are in 'snake1976' folder. + +## Running the game + +You should be in the 'snake_project' folder, then the game can be started: + +``` +python snake_game.py +``` + +## Before commit + +### Delete your game results + +Before commit you need to make sure that there are 3 files in the 'records' folder and they are all empty: + +``` +snake_project/extra/game_environment/score_files/records/date_n_time.txt +snake_project/extra/game_environment/score_files/records/players.txt +snake_project/extra/game_environment/score_files/records/scores.txt +``` + +These files were generated in order to store player's name, his score, date and time when he played. +Of course, if you wanna save your results before removal then you can copy them to another place on you PC. + +### Running the tests + +When you get to the directory 'snake1976' just run the command: + +``` +pytest -s +``` + +## Authors + +* **Alexander Gusakov** - *Initial work* - [GonnaFlyMethod](https://github.com/GonnaFlyMethod) + +See also the list of [contributors](https://github.com/GonnaFlyMethod/snake1976-win/graphs/contributors/) who participated in this project. + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/GonnaFlyMethod/snake1976-win/blob/master/LICENSE) file for details diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..190a507 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,293 @@ +[[package]] +category = "main" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + +[[package]] +category = "main" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "main" +description = "Cross-platform colored terminal text." +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "the modular source code checker: pep8 pyflakes and co" +name = "flake8" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.3" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "main" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + +[[package]] +category = "main" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.4.0" + +[[package]] +category = "main" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.4" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "main" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.9.0" + +[[package]] +category = "main" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" + +[[package]] +category = "main" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.2.0" + +[[package]] +category = "main" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "main" +description = "Tool for creating new python packages" +name = "pyproject" +optional = false +python-versions = "*" +version = "1.3.1" + +[package.dependencies] +Jinja2 = "*" + +[[package]] +category = "main" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.4.3" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "main" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.2.5" + +[metadata] +content-hash = "66a5b39f9c80346e15ea47d7e1f22c9a079e7f88979baf2d18b71b38083efa33" +python-versions = "^3.8" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +flake8 = [ + {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, + {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, +] +jinja2 = [ + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +more-itertools = [ + {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, + {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, +] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pyproject = [ + {file = "pyproject-1.3.1.tar.gz", hash = "sha256:594679f8c77396198f96f560f3b91e144903d6cc0ee2af70227e0e2fcf128e19"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4db9b1a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "snake1976" +version = "0.1.0" +description = "Snake 1976 is a console game that follows the logic of the classic snake.The game also contains some new game modes and features." +authors = ["Alexander Gusakov "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +colorama = "^0.4.3" +pytest = "^5.4.3" +pyproject = "^1.3.1" +flake8 = "^3.8.3" + +[tool.poetry.dev-dependencies] +flake8 = "^3.8.3" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..224a327 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 80 +ignore = D100,D101,D102,D103,D104,D107,D202,D205,D400, + D401,E125,E128,E129,E402,E701,F403,F405,I100,W504 diff --git a/snake_project/extra/game_environment/menu_files/console_img/byebye.txt b/snake_project/extra/game_environment/menu_files/console_img/byebye.txt new file mode 100644 index 0000000..7b401d0 --- /dev/null +++ b/snake_project/extra/game_environment/menu_files/console_img/byebye.txt @@ -0,0 +1,19 @@ + + + + + + + + + + .########..##....##.########....########..##....##.######## + .##.....##..##..##..##..........##.....##..##..##..##...... + .##.....##...####...##..........##.....##...####...##...... + .########.....##....######......########.....##....######.. + .##.....##....##....##..........##.....##....##....##...... + .##.....##....##....##..........##.....##....##....##...... + .########.....##....########....########.....##....######## + + + diff --git a/snake_project/extra/game_environment/menu_files/console_img/controls.txt b/snake_project/extra/game_environment/menu_files/console_img/controls.txt new file mode 100644 index 0000000..fb3e612 --- /dev/null +++ b/snake_project/extra/game_environment/menu_files/console_img/controls.txt @@ -0,0 +1,23 @@ + Snake 1 + _____ + | | + | W | < UP + |_____| + _____ _____ _____ +| | | | | | +| A | | S | | D | +|_____| |_____| |_____| + ^ ^ ^ + LEFT DOWN RIGHT +__________________________ + Snake 2 + _____ + | | + | ^ | < UP + |_____| + _____ _____ _____ +| | | | | | +| < | | v | | > | +|_____| |_____| |_____| + ^ ^ ^ + LEFT DOWN RIGHT \ No newline at end of file diff --git a/snake_project/extra/game_environment/menu_files/console_img/loading.txt b/snake_project/extra/game_environment/menu_files/console_img/loading.txt new file mode 100644 index 0000000..afa1448 --- /dev/null +++ b/snake_project/extra/game_environment/menu_files/console_img/loading.txt @@ -0,0 +1,20 @@ + + + + + + + + + + .##........#######.....###....########..####.##....##..######................. + .##.......##.....##...##.##...##.....##..##..###...##.##....##................ + .##.......##.....##..##...##..##.....##..##..####..##.##...................... + .##.......##.....##.##.....##.##.....##..##..##.##.##.##...####............... + .##.......##.....##.#########.##.....##..##..##..####.##....##................ + .##.......##.....##.##.....##.##.....##..##..##...###.##....##.....###.###.### + .########..#######..##.....##.########..####.##....##..######......###.###.### + + + + \ No newline at end of file diff --git a/snake_project/extra/game_environment/menu_files/console_img/snake.txt b/snake_project/extra/game_environment/menu_files/console_img/snake.txt new file mode 100644 index 0000000..fdbd06f --- /dev/null +++ b/snake_project/extra/game_environment/menu_files/console_img/snake.txt @@ -0,0 +1,24 @@ +@@@@@@@@@@@@@@@@@@@@\/%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@)(%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@&#########&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@#<.>###<.>#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@###########@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@##########@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@########@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@#########################@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@###########################%@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@############################%@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%##########@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@########@@@@@@@@@@@ +@@@@@@@@@@@@@@@##################################@@@@@@@@@@@ +@@@@@@@@@@@@%###################################@@@@@@@@@@@@ +@@@@@@@@@@%###################################&@@@@@@@@@@@@@ +@@@@@@@@@@##########%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@#########@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@%##########################@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@%############################@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@############################&@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#########&@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@########&@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@########&@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@########@@@@@@@@@@@@@@@@@@@ diff --git a/snake_project/extra/game_environment/menu_files/menu.py b/snake_project/extra/game_environment/menu_files/menu.py new file mode 100644 index 0000000..6566524 --- /dev/null +++ b/snake_project/extra/game_environment/menu_files/menu.py @@ -0,0 +1,268 @@ +import getpass +import os +import sys +import time + +from colorama import Fore, deinit, init + +from extra.tools.validators import Validators +from extra.tools.os_initializer import init_os +from extra.tools.clean_console_command_setter import set_cleaning_command + + +class BeforeTheGame: + """The class is responsible for all operations that occur before the game. + + Class's method are used to show the splash screen(Snake's ASCII art, + Loading...'-label), welcome message. Also it's showing controls for snakes + in the interface of main menu. The class is used to record the + information about the game settings(field size , game speed, default length + of the snake etc.) for game modes. + """ + + def __init__(self): + """Inits class BeforeTheGame with the player name, dictionary of future + player's choice and self.gamemode. + """ + + # The name of the player will be selected according to OS user name + self.player_name = getpass.getuser() + self._player_choice = {} + self.gamemode = 1 + self.platform = init_os() + self.RESET = set_cleaning_command() + self.validators = Validators() + + def set_gamemode(self, gamemode: int): + + self.gamemode = gamemode + + def show_ascii_art_and_loading(self): + """Shows snake's ASCII art and 'Loading...'-label on the splash screen. + + Using ASCII symbols from files console_img/snake.txt, + console_img/loading.txt to show them in the console. + """ + if self.platform == "Windows": + os.system('mode con: cols=61 lines=25') + else: + sys.stdout.write("\x1b[8;{rows};{cols}t".format(rows=25, cols=60)) + + init(autoreset=True) # Initialization of colorama. + file = 'extra/game_environment/menu_files/console_img/snake.txt' + with open(file, 'r') as img: + for line in img: + for symbol in line: + if (symbol == '\\' or symbol == '/' or symbol == '(' or + symbol == ')'): + # Painting of red forked tongue of the snake. + print(Fore.RED + symbol, end='') + elif symbol == '#': + # Painting of snake's Body. + print(Fore.GREEN + symbol, end='') + elif symbol == '<' or symbol == '>' or symbol == '.': + # Painting of snake's eyes. + print(Fore.MAGENTA + symbol, end='') + else: + # Painting of background. + print(symbol, end='') + deinit() # Uninstallation of colorama. + time.sleep(4) + os.system(self.RESET) + + if self.platform == "Windows": + os.system('mode con: cols=90 lines=25') + else: + sys.stdout.write("\x1b[8;{rows};{cols}t".format(cols=90, rows=25)) + + file = 'extra/game_environment/menu_files/console_img/loading.txt' + with open(file) as img: + for line in img: + print(line[:-1]) + + time.sleep(2) + os.system(self.RESET) + + def show_welcome_message_to_player(self): + os.system(self.RESET) + print(str(self.player_name) + ", " + "Welcome to the game!") + + def show_controls_for_snakes(self): + file = 'extra/game_environment/menu_files/console_img/controls.txt' + with open(file) as img: + for line in img: + print(line[:-1]) + + while True: + print("Type 'exit' to return to main menu") + player_input = input() + if 'exit' in player_input.lower(): + os.system(self.RESET) + break + + def ask_player_about_choice_in_menu(self): + print("Please, сhoose one variant from menu: ") + print('1) Classic mode\n2) Survival (2 p on one computer)') + print('3) Battle (2 p on one computer)\n4) My personal score') + print('5) GitHub repo\n6) Controls\n7) Quit the game') + + validator = self.validators.validate_main_menu_selection() + player_input = validator + if player_input == 1: + self._player_choice['menu_melection'] = 1 + elif player_input == 2: + self._player_choice['menu_melection'] = 2 + elif player_input == 3: + self._player_choice['menu_melection'] = 3 + elif player_input == 4: + self._player_choice['menu_melection'] = 4 + elif player_input == 5: + self._player_choice['menu_melection'] = 5 + elif player_input == 6: + self._player_choice['menu_melection'] = 6 + else: + file = 'extra/game_environment/menu_files/console_img/byebye.txt' + with open(file) as img: + for line in img: + print(line[:-1]) + + time.sleep(2) + os.system(self.RESET) + + self._player_choice['menu_melection'] = 7 + + def ask_player_about_field_size(self): + print('Field size: ') + print('1) 20 x 20\n2) 40 x 20\n3) 60 x 20\n4) 78 x 20') + + validator = self.validators.validate_choice_in_field_size() + player_input = validator + if player_input == 1: + self._player_choice['size'] = (20, 20) + elif player_input == 2: + self._player_choice['size'] = (40, 20) + elif player_input == 3: + self._player_choice['size'] = (60, 20) + else: + self._player_choice['size'] = (78, 20) + + def ask_player_about_snake_length(self): + print('Please, select the length of snake in the start of the game') + print('1) 3\n2) 6\n3) 9\n4) Custom') + + validator = self.validators.validate_choice_in_snake_length() + player_input = validator + if player_input == 1: + # Head of a snake is not in snake body. + self._player_choice['length'] = 2 + elif player_input == 2: + self._player_choice['length'] = 5 + elif player_input == 3: + self._player_choice['length'] = 8 + else: + validator = self.validators.validate_choice_in_snake_length(cust=1) + player_input_cust_length = validator + self._player_choice['length'] = player_input_cust_length + + def ask_player_about_snake_and_walls(self): + print('Can snake crawl through the walls?') + print('1) YEAP!\n2) NOPE! ') + + validator = self.validators.validate_choice_in_snake_and_walls() + player_input = validator + if player_input == 1: + self._player_choice['walls'] = "can crawl through the walls" + else: + self._player_choice['walls'] = "can't crawl through the walls" + + def ask_plyaer_about_speed(self): + print('Please, set game speed') + print('1) Slow (+20 for 1 fruit)') + print('2) Normal (+30 for 1 fruit)') + print('3) High (+40 for 1 fruit)') + + validator = self.validators.validate_player_input_in_speed() + player_input = validator + self._player_choice['game_speed'] = player_input + + if player_input == 1: + self._player_choice['game_speed'] = 0.08 + elif player_input == 2: + self._player_choice['game_speed'] = 0.06 + else: + self._player_choice['game_speed'] = 0.03 + + def ask_player_about_game_time(self): + print("Please, set a game time(MIN = 100)") + validator = self.validators.validate_player_input_in_game_time() + player_input = validator + self._player_choice['game_time'] = player_input + + +class SetGameSettings(BeforeTheGame): + """The class sets the game settings for the game core. + + According to the choice that made by the player this class sets the game + settings. + """ + + def set_choice_in_menu(self) -> int: + return self._player_choice['menu_melection'] + + def field_size_set_width(self) -> int: + return self._player_choice['size'][0] + + def field_size_set_height(self) -> int: + return self._player_choice['size'][1] + + def set_snake_and_walls_setting(self) -> str: + return self._player_choice['walls'] + + def set_default_length(self) -> int: + return self._player_choice['length'] + + def set_name_of_player(self) -> str: + return self.player_name + + def set_game_speed(self) -> float: + return self._player_choice['game_speed'] + + def set_game_time(self) -> int: + return self._player_choice['game_time'] + + +class AfterTheGame(SetGameSettings): + """Class is responsible for processing player's choice after the game.""" + + def ask_player_for_further_actions(self) -> int: + """Gives a choices for further actions after the game. + + Provides player with the interface after the game. Shows ASCII-banner + 'BYE BYE' if '4) Quit the game' was selected. + """ + + print('1) Play one more time\n2) Game settings') + print('3) Menu\n4) Quit the game') + + validator = self.validators.validate_input_after_the_game() + player_input = validator + if player_input == 1: + self.after_the_game_status = 1 + elif player_input == 2: + self.after_the_game_status = 2 + elif player_input == 3: + self.after_the_game_status = 3 + else: + file = 'extra/game_environment/menu_files/console_img/byebye.txt' + with open(file) as img: + for line in img: + print(line[:-1]) + + self.after_the_game_status = 4 + time.sleep(2) + os.system(self.RESET) + return self.after_the_game_status + + +class Menu(AfterTheGame): + pass diff --git a/snake_project/extra/game_environment/score_files/records/date_n_time.txt b/snake_project/extra/game_environment/score_files/records/date_n_time.txt new file mode 100644 index 0000000..e69de29 diff --git a/snake_project/extra/game_environment/score_files/records/players.txt b/snake_project/extra/game_environment/score_files/records/players.txt new file mode 100644 index 0000000..e69de29 diff --git a/snake_project/extra/game_environment/score_files/records/scores.txt b/snake_project/extra/game_environment/score_files/records/scores.txt new file mode 100644 index 0000000..e69de29 diff --git a/snake_project/extra/game_environment/score_files/score.py b/snake_project/extra/game_environment/score_files/score.py new file mode 100644 index 0000000..d92da1f --- /dev/null +++ b/snake_project/extra/game_environment/score_files/score.py @@ -0,0 +1,113 @@ +import os +from datetime import datetime + +from extra.tools.os_initializer import init_os + + +class Score: + """The class provides player with the results of his game[only classic mod]. + + Score class writes down player's score and shows to the player his results + as a table. + """ + + def __init__(self, player_name: str): + self.player_name = player_name + self.players = 'extra/game_environment/score_files/records/players.txt' + self.scores = 'extra/game_environment/score_files/records/scores.txt' + self.DnT = 'extra/game_environment/score_files/records/date_n_time.txt' + self.RESET = "cls" if init_os() == "Windows" else "clear" + + def write_down_player_score(self, score: int) -> None: + """Records player data in 3 different files.""" + with open(self.players, 'a') as file: + file.write(str(self.player_name) + '\n') + + with open(self.scores, 'a') as file: + file.write(str(score) + '\n') + + date_n_time_now = datetime.now() + # DD/MM/YYYY /Hours/Minutes/Seconds + dt_string = date_n_time_now.strftime("%d/%m/%Y %H:%M:%S") + with open(self.DnT, 'a') as file: + file.write(str(dt_string) + '\n') + + def show_score(self): + """Shows the data about the game in the form of a table. + + The method extracts data from 3 files which were previously recorded. + The first file contains the players' names. The second one store + players' game scores and the last one contains game date and time. + Method inserts this data into the table that draws by itself. + Shows the data of the top 20 players. + """ + with open(self.players, 'r') as players: + with open(self.scores, 'r') as scores: + with open(self.DnT, 'r') as date_n_time: + list_for_parsing = [] + for line_1 in players: + line_2 = scores.readline() + line_3 = date_n_time.readline() + # There is no need to include No \n. + clean_player_name = line_1[:len(line_1) - 1] + clean_date_n_time = line_3[:len(line_3) - 1] + list_for_parsing.append( + ( + int(line_2), + clean_player_name, + clean_date_n_time + ) + ) + + find_longest_player_name = [] + find_longest_player_score = [] + find_longest_date = [] + + for i in list_for_parsing: + find_longest_player_name.append(len(str(i[1]))) + find_longest_player_score.append(len(str(i[0]))) + find_longest_date.append(len(str(i[2]))) + + longest_player_name = max(find_longest_player_name) + longest_player_score = max(find_longest_player_score) + longest_date = max(find_longest_date) + + player_row = 'Player' + (' ' * (longest_player_name)) + score_row = 'Score' + (' ' * (longest_player_score)) + # Adds one more '-' symbol. + date_row = 'D&T' + (' ' * (longest_date - len('D&T') + 1)) + + print(player_row + '|' + score_row + '|' + date_row) + print('-' * len(player_row) + '+' + '-' * len(score_row) + '+' + '-' * + len(date_row)) + + length_of_player_row = len(player_row) + length_of_score_row = len(score_row) + length_of_date_row = len(date_row) + + num_of_str = 1 + + list_for_parsing.sort(reverse=True) + for i in list_for_parsing: + if num_of_str > 20: + break + + # Takes into account whitespace after the player name in the row + space_player_row = length_of_player_row - (len(str(i[1])) + + (len(str(num_of_str)) + 1)) + + space_score_row = length_of_score_row - (len(str(i[0]))) + space_date_row = length_of_date_row - (len(str(i[2]))) + + print(num_of_str, str(i[1]) + (' ' * (space_player_row)) + '|' + + str(i[0]) + (' ' * (space_score_row)) + '|' + + str(i[2]) + (' ' * space_date_row)) + + num_of_str += 1 + + while True: + print("Type 'exit' to return to main menu") + player_input = input() + if 'exit' in player_input.lower(): + os.system(self.RESET) + break diff --git a/snake_project/extra/tools/asker.py b/snake_project/extra/tools/asker.py new file mode 100644 index 0000000..7c03158 --- /dev/null +++ b/snake_project/extra/tools/asker.py @@ -0,0 +1,49 @@ +def complex_ask(menu_instance, mode: int) -> dict: + """Asks player about the game settings and then returns dictionary + with them. + """ + player_choice = {} + + if mode == 1: + # Classic mode + menu_instance.set_gamemode(1) + menu_instance.ask_player_about_field_size() + menu_instance.ask_player_about_snake_length() + menu_instance.ask_player_about_snake_and_walls() + menu_instance.ask_plyaer_about_speed() + + player_choice['width'] = menu_instance.field_size_set_width() + player_choice['height'] = menu_instance.field_size_set_height() + player_choice['length'] = menu_instance.set_default_length() + player_choice['walls'] = menu_instance.set_snake_and_walls_setting() + player_choice['speed'] = menu_instance.set_game_speed() + return player_choice + + elif mode == 2: + # Survival mode + menu_instance.set_gamemode(2) + menu_instance.ask_player_about_field_size() + menu_instance.ask_player_about_snake_length() + menu_instance.ask_player_about_snake_and_walls() + menu_instance.ask_plyaer_about_speed() + + player_choice['width'] = menu_instance.field_size_set_width() + player_choice['height'] = menu_instance.field_size_set_height() + player_choice['length'] = menu_instance.set_default_length() + player_choice['walls'] = menu_instance.set_snake_and_walls_setting() + player_choice['speed'] = menu_instance.set_game_speed() + return player_choice + + else: + # Battle mode + menu_instance.set_gamemode(3) + menu_instance.ask_player_about_snake_length() + menu_instance.ask_player_about_snake_and_walls() + menu_instance.ask_plyaer_about_speed() + menu_instance.ask_player_about_game_time() + + player_choice['length'] = menu_instance.set_default_length() + player_choice['walls'] = menu_instance.set_snake_and_walls_setting() + player_choice['speed'] = menu_instance.set_game_speed() + player_choice['game_time'] = menu_instance.set_game_time() + return player_choice diff --git a/snake_project/extra/tools/clean_console_command_setter.py b/snake_project/extra/tools/clean_console_command_setter.py new file mode 100644 index 0000000..dd0e6c5 --- /dev/null +++ b/snake_project/extra/tools/clean_console_command_setter.py @@ -0,0 +1,10 @@ +from .os_initializer import init_os + + +def set_cleaning_command(): + command = None + if init_os() == "Windows": + command = "cls" + else: + command = "clear" + return command diff --git a/snake_project/extra/tools/console_cleaner.py b/snake_project/extra/tools/console_cleaner.py new file mode 100644 index 0000000..cf8ef0c --- /dev/null +++ b/snake_project/extra/tools/console_cleaner.py @@ -0,0 +1,10 @@ +import os + +from .os_initializer import init_os + + +def set_the_console_cleaning_command(): + if init_os() == "Windows": + os.system("cls") + else: + os.system("clear") diff --git a/snake_project/extra/tools/git_repo_opener.py b/snake_project/extra/tools/git_repo_opener.py new file mode 100644 index 0000000..30f427f --- /dev/null +++ b/snake_project/extra/tools/git_repo_opener.py @@ -0,0 +1,10 @@ +import webbrowser +import os + +from extra.tools.clean_console_command_setter import set_cleaning_command + + +def open_git_repo_in_browser(): + webbrowser.open("https://github.com/GonnaFlyMethod/snake1976") + clean = set_cleaning_command() + os.system(clean) diff --git a/snake_project/extra/tools/key_setup.py b/snake_project/extra/tools/key_setup.py new file mode 100644 index 0000000..75e61aa --- /dev/null +++ b/snake_project/extra/tools/key_setup.py @@ -0,0 +1,28 @@ +from .os_initializer import init_os + + +def set_keyboard_keys() -> dict: + key_dict = {} + if init_os() == 'Windows': + key_dict["UP_1"] = [119, 87] # w W + key_dict["DOWN_1"] = [115, 83] # s S + key_dict["LEFT_1"] = [97, 65] # a A + key_dict["RIGHT_1"] = [100, 68] # d D + + key_dict["UP_2"] = 72 # ^ + key_dict["DOWN_2"] = 80 # v + key_dict["LEFT_2"] = 75 # < + key_dict["RIGHT_2"] = 77 # > + return key_dict + + else: + key_dict["UP_1"] = [119, 87] # w W + key_dict["DOWN_1"] = [115, 83] # s S + key_dict["LEFT_1"] = [97, 65] # a A + key_dict["RIGHT_1"] = [100, 68] # d D + + key_dict["UP_2"] = 65 # ^ + key_dict["DOWN_2"] = 66 # v + key_dict["LEFT_2"] = 68 # < + key_dict["RIGHT_2"] = 67 # > + return key_dict diff --git a/snake_project/extra/tools/linux_functions.py b/snake_project/extra/tools/linux_functions.py new file mode 100644 index 0000000..dc080b1 --- /dev/null +++ b/snake_project/extra/tools/linux_functions.py @@ -0,0 +1,17 @@ +import sys +import termios + + +fd = sys.stdin.fileno() + +new_term = termios.tcgetattr(fd) +old_term = termios.tcgetattr(fd) +new_term[3] = (new_term[3] & ~termios.ICANON & ~termios.ECHO) + + +def set_normal_term(): + termios.tcsetattr(fd, termios.TCSAFLUSH, old_term) + + +def set_curses_term(): + termios.tcsetattr(fd, termios.TCSAFLUSH, new_term) diff --git a/snake_project/extra/tools/os_initializer.py b/snake_project/extra/tools/os_initializer.py new file mode 100644 index 0000000..42c45b3 --- /dev/null +++ b/snake_project/extra/tools/os_initializer.py @@ -0,0 +1,6 @@ +import platform + + +def init_os() -> str: + os = platform.system() + return os diff --git a/snake_project/extra/tools/validators.py b/snake_project/extra/tools/validators.py new file mode 100644 index 0000000..f7c2812 --- /dev/null +++ b/snake_project/extra/tools/validators.py @@ -0,0 +1,166 @@ +# Validators for all player's inputs +import os + +from .os_initializer import init_os + +from extra.tools.clean_console_command_setter import set_cleaning_command + + +class Validators: + """The class helps the user to enter valid data then the game require it.""" + + def __init__(self): + self.platform = init_os() + self.RESET = set_cleaning_command() + + def validate_main_menu_selection(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, сhoose something from menu[1-7]") + continue + + if player_input < 1 or player_input > 7: + print("Please, сhoose something from menu[1-7]") + continue + else: + os.system(self.RESET) + return player_input + + def validate_choice_in_field_size(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, select one of the available field sizes[1-4]") + continue + if player_input < 1 or player_input > 4: + print("Please, select one of the available field sizes[1-4]") + continue + else: + os.system(self.RESET) + return player_input + + def validate_choice_in_snake_length(self, cust=0) -> int: + if cust == 0: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, select one variant[1-5]") + continue + + if player_input < 1 or player_input > 5: + print("Please, select one variant[1-5]") + continue + else: + os.system(self.RESET) + return player_input + else: + while True: + print('MIN length = 3') + try: + player_input = int(input('~ ')) + + except ValueError: + print("Please, set custom length [MUST BE NUMBER]") + continue + + if player_input < 3: + print("The entered snake length doesn't meet the ") + print("requirements Try again!") + continue + else: + os.system(self.RESET) + return player_input + + def validate_choice_in_snake_and_walls(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print('Please, choose one variant[1 or 2]') + continue + + if player_input < 1 or player_input > 2: + print('Please, choose one variant[1 or 2]') + continue + else: + os.system(self.RESET) + return player_input + + def validate_choice_in_color_of_snake(self, second_player=0) -> int: + if second_player == 0: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, сhoose one color for the 1-st player[1-7]") + continue + + if player_input < 1 or player_input > 7: + print("Please, сhoose one color for the 1-st player[1-7]") + continue + else: + os.system(self.RESET) + return player_input + else: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, сhoose one color for the 2-nd player[1-6]") + continue + + if player_input < 1 or player_input > 6: + print("Please, сhoose one color for the 2-nd player[1-6]") + continue + else: + os.system(self.RESET) + return player_input + + def validate_player_input_in_speed(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, select one variant[1-3]") + continue + + if player_input < 1 or player_input > 3: + print("Please, select one variant[1-3]") + continue + else: + os.system(self.RESET) + return player_input + + def validate_player_input_in_game_time(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, set game time [MUST BE NUMBER]") + continue + + if player_input < 100: + print('MIN TIME IS 100!') + continue + else: + os.system(self.RESET) + return player_input + + def validate_input_after_the_game(self) -> int: + while True: + try: + player_input = int(input('~ ')) + except ValueError: + print("Please, select one variant[1-4]") + continue + + if player_input < 1 or player_input > 4: + print("Please, select one variant[1-4]") + continue + else: + os.system(self.RESET) + return player_input diff --git a/snake_project/gamemodes/battle_mode.py b/snake_project/gamemodes/battle_mode.py new file mode 100644 index 0000000..388ab5e --- /dev/null +++ b/snake_project/gamemodes/battle_mode.py @@ -0,0 +1,552 @@ +import os +import random +import threading +import time + +from extra.tools.os_initializer import init_os +if init_os() == 'Windows': + from msvcrt import getch, kbhit +else: + import curses + from extra.tools.linux_functions import set_normal_term, set_curses_term +from extra.tools.asker import complex_ask +from extra.tools.key_setup import set_keyboard_keys +from extra.tools.clean_console_command_setter import set_cleaning_command + + +class BattleModeGameManager: + """This class provides methods for implementing the Battle mode.""" + + def __init__(self, player_score_instance, game_menu_instance): + """Setting the keys on the keyboard to control the snakes and inits + os, dict-obj, player score instance and menu instance. + + Note: + _1 means something related to the 1-st snake, + _2 means something related to the 2-nd snake. + """ + self.keys = set_keyboard_keys() + self.UP_1 = [self.keys["UP_1"][0], self.keys["UP_1"][1]] # w W + self.DOWN_1 = [self.keys["DOWN_1"][0], self.keys["DOWN_1"][1]] # s S + self.LEFT_1 = [self.keys["LEFT_1"][0], self.keys["LEFT_1"][1]] # a A + self.RIGHT_1 = [self.keys["RIGHT_1"][0], self.keys["RIGHT_1"][1]] # d D + + self.UP_2 = self.keys["UP_2"] # ^ + self.DOWN_2 = self.keys["DOWN_2"] # v + self.LEFT_2 = self.keys["LEFT_2"] # < + self.RIGHT_2 = self.keys["RIGHT_2"] # > + self.keys_player_2 = [self.UP_2, self.DOWN_2, self.LEFT_2, self.RIGHT_2] + + self.RESET = set_cleaning_command() + self.platform = init_os() + self.settings_storage = {} # Dict for set_default_settings method. + self.player_score = player_score_instance + self.menu = game_menu_instance + + def run(self): + """Method starting the mode automatically.""" + plays_current_game_in_current_gamemode = True + + while plays_current_game_in_current_gamemode: + wanna_continue_the_current_game = True + # Survey of the player about the game settings. + self.settings_storage = complex_ask(self.menu, 3) + # Setting default settings according to the choice of the player. + self.set_default_settings() + while wanna_continue_the_current_game: + self.initialize_new_players() + + while (not self.get_game_over_status_player_1() and not + self.get_game_over_status_player_2()): + + self.draw_whole_field() + + if self.platform == "Linux": + for i in range(2): + curses.wrapper(self.process_players_input_linux) + else: + threads = self.create_new_threads() + for t in threads: + t.start() + + self.process_hook_logic_for_player_1() + self.process_hook_logic_for_player_2() + + # If player1 or player2 lose the game. + if (self.get_game_over_status_player_1() or + self.get_game_over_status_player_2() or + self.is_the_time_over()): + + winner = self.determine_who_won() + if winner == 1: + print("Player 1 won the game!") + elif winner == 2: + print("Player 2 won the game!") + else: + print('Draw!') + + print("P1: " + self.get_score_of_players()[0]) + print("P2: " + self.get_score_of_players()[1]) + print('~' * 40) + + player_resp = self.menu.ask_player_for_further_actions() + if player_resp == 1: + self.set_default_settings() + self.set_game_over_false() + break + elif player_resp == 2: + wanna_continue_the_current_game = False + self.set_game_over_false() + break + elif player_resp == 3: + wanna_continue_the_current_game = False + plays_current_game_in_current_gamemode = False + self.set_game_over_false() + break + else: + exit() + + def initialize_new_players(self): + """Method sets the initial length of snakes and sets the adding + bonus for extra speed of the game. + """ + # Setting the default length of the 1-st snake. + self.snake_segments_coord_x_1 = [self.head_x_coord_1 + for i in range(self.num_of_snake_segments_1 + 1)] + + offsets_of_the_snake_segment_y_1 = 1 + for i in range(self.num_of_snake_segments_1 + 1): + self.snake_segments_coord_y_1.append(self.head_y_coord_1 + + offsets_of_the_snake_segment_y_1) + + offsets_of_the_snake_segment_y_1 += 1 + + # Setting the default length of the 2-d snake. + self.snake_segments_coord_x_2 = [self.head_x_coord_2 + for i in range(self.num_of_snake_segments_2 + 1)] + + offsets_of_the_snake_segment_y_2 = 1 + for i in range(self.num_of_snake_segments_2 + 1): + self.snake_segments_coord_y_2.append(self.head_y_coord_2 + + offsets_of_the_snake_segment_y_2) + + offsets_of_the_snake_segment_y_2 += 1 + + # Setting points for one fruit. + if self.game_speed == 0.08: + self.adding_points = 20 + elif self.game_speed == 0.06: + self.adding_points = 30 + else: + self.adding_points = 40 + + def draw_whole_field(self): + """Method constantly redraws the playing fields, snakes, fruits and + score&time indicator. + """ + if self.platform == "Linux": set_curses_term() + + for i in range(self.width + 1): + # Drawing the space between two fields (TOP). + if i > 20 and i < 40: + print(" ", end="") + else: + # Drawing the upper edges. + print("█", end="") + + print(" ") + # Drawing snakes' head, fruit, side edges, snakes' tails, and a void. + for i in range(self.height + 1): + for j in range(self.width + 1): + # Drawing 1-st snake's head. + if i == self.head_y_coord_1 and j == self.head_x_coord_1: + print("0", end="") + + # Drawing 2-nd snake's head. + elif i == self.head_y_coord_2 and j == self.head_x_coord_2: + print("0", end="") + + # Drawing fruit in different game fields. + elif (i == self.y_coord_of_fruit_1 and + j == self.x_coord_of_fruit_1 or + i == self.y_coord_of_fruit_2 and + j == self.x_coord_of_fruit_2): + print("*", end="") + + # Drawing the side edges of two fields. + elif j == 0 or j == 20 or j == 40 or j == self.width: + # Drawing the side borders of the 1-st field. + if j == 0: + print("█", end="") + elif j == 20: + print("█", end="") + # Drawing the side borders of the 2-nd field. + elif j == 40: + print("█", end="") + else: + print("█") + + else: + # Drawing tail of the 1-st snake. + print_tail_1 = False + for k in range(self.num_of_snake_segments_1): + if (self.snake_segments_coord_x_1[k] == j and + self.snake_segments_coord_y_1[k] == i): + print_tail_1 = True + print("o", end="") + + # Drawing tail of the 2-nd snake. + print_tail_2 = False + for t in range(self.num_of_snake_segments_2): + if (self.snake_segments_coord_x_2[t] == j and + self.snake_segments_coord_y_2[t] == i): + print_tail_2 = True + print("o", end="") + + if not print_tail_1 and not print_tail_2: + print(" ", end="") + + # Drawing the space between two fields (Bottom). + for i in range(self.width + 1): + if i > 20 and i < 40: + print(" ", end="") + else: + # Drawing the bottom borders of 2 fields. + print("█", end="") + + print(" ") + print(self.centralize_score_n_time + "P1: " + str(self.score_1) + + " | " + "Time: " + str(self.time - self.countdown) + " | " + + "P2: " + str(self.score_2)) + + self.countdown += 1 + time.sleep(self.game_speed) + os.system(self.RESET) + + def process_players_input_win(self): + """Handles pressing keyboard keys on Windows OS. + + Changes the direction of the snakes (depends on the keys that players + press). + """ + time.sleep(0.085) + if kbhit(): + key = ord(getch()) + if key not in self.keys_player_2: + if key in self.UP_1: + self.direction_1 = 'UP' + elif key in self.DOWN_1: + self.direction_1 = 'DOWN' + + elif key in self.LEFT_1: + self.direction_1 = 'LEFT' + elif key in self.RIGHT_1: + self.direction_1 = 'RIGHT' + else: + if key == self.UP_2: + self.direction_2 = 'UP' + elif key == self.DOWN_2: + self.direction_2 = 'DOWN' + + elif key == self.LEFT_2: + self.direction_2 = 'LEFT' + elif key == self.RIGHT_2: + self.direction_2 = 'RIGHT' + + def process_players_input_linux(self, win): + """Handles pressing keyboard keys on Linux OS. + + Changes the direction of the snake (depends on the key that player + presses). + """ + win.clear() + curses.use_default_colors() + win.nodelay(True) + key = "" + stdscr = curses.initscr() + curses.cbreak() + stdscr.keypad(5) + stdscr.refresh() + win.clear() + key = stdscr.getch() + win.clear() + + if key not in self.keys_player_2: + if key in self.UP_1: + self.direction_1 = "UP" + elif key in self.LEFT_1: + self.direction_1 = "LEFT" + elif key in self.DOWN_1: + self.direction_1 = "DOWN" + elif key in self.RIGHT_1: + self.direction_1 = "RIGHT" + else: + if key == self.UP_2: + self.direction_2 = "UP" + elif key == self.LEFT_2: + self.direction_2 = "LEFT" + elif key == self.DOWN_2: + self.direction_2 = "DOWN" + elif key == self.RIGHT_2: + self.direction_2 = "RIGHT" + + def process_hook_logic_for_player_1(self): + """Handles the 1-st snake logic. + + Handles logic related to the tail of the 1-st snake, direction of + the 1-st snake, logic for eating fruits(the 1-st snake), cases in which + self.game_over_1 = True for the 1-st snake. + + Note: + snake1 = the 1-st snake. + """ + # Snake1's tail logic. + self.snake_segments_coord_x_1.append(0) + self.snake_segments_coord_y_1.append(0) + + prev_coord_x_1 = self.snake_segments_coord_x_1[0] + prev_coord_y_1 = self.snake_segments_coord_y_1[0] + + prev_coord2_x_1 = 0 + prev_coord2_y_1 = 0 + + self.snake_segments_coord_x_1[0] = self.head_x_coord_1 + self.snake_segments_coord_y_1[0] = self.head_y_coord_1 + + for i in range(1, self.num_of_snake_segments_1): + prev_coord2_x_1 = self.snake_segments_coord_x_1[i] + prev_coord2_y_1 = self.snake_segments_coord_y_1[i] + + self.snake_segments_coord_x_1[i] = prev_coord_x_1 + self.snake_segments_coord_y_1[i] = prev_coord_y_1 + + prev_coord_x_1 = prev_coord2_x_1 + prev_coord_y_1 = prev_coord2_y_1 + + # The logic related to the direction of the snake1. + if self.direction_1 == 'LEFT': + self.head_x_coord_1 -= 1 + elif self.direction_1 == 'RIGHT': + self.head_x_coord_1 += 1 + + elif self.direction_1 == 'UP': + self.head_y_coord_1 -= 1 + elif self.direction_1 == 'DOWN': + self.head_y_coord_1 += 1 + + # Snake1 and walls logic. + if self.snake_and_walls == 'can crawl through the walls': + if self.head_x_coord_1 > 19: + self.head_x_coord_1 = 1 + elif self.head_x_coord_1 == 0: + self.head_x_coord_1 = 19 + + if self.head_y_coord_1 > self.height: + self.head_y_coord_1 = 0 + elif self.head_y_coord_1 < 0: + self.head_y_coord_1 = self.height + else: + if (self.head_x_coord_1 > 19 or + self.head_x_coord_1 == 0): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + elif (self.head_y_coord_1 > self.height or + self.head_y_coord_1 < 0): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + # Cases for self.game_over_1 = True (snake1). + for i in range(self.num_of_snake_segments_1): + if (self.snake_segments_coord_x_1[i] == self.head_x_coord_1 and + self.snake_segments_coord_y_1[i] == self.head_y_coord_1): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + # Eating fruit logic (snake1). + if (self.head_x_coord_1 == self.x_coord_of_fruit_1 and + self.head_y_coord_1 == self.y_coord_of_fruit_1): + + self.x_coord_of_fruit_1 = random.randint(1, 19) + self.y_coord_of_fruit_1 = random.randint(1, 19) + + self.x_coord_of_fruit_2 = self.x_coord_of_fruit_1 + 40 + self.y_coord_of_fruit_2 = self.y_coord_of_fruit_1 + + self.num_of_snake_segments_2 += 1 + self.score_1 += self.adding_points + + def process_hook_logic_for_player_2(self): + """Handles the 2-nd Snake logic. + + Handles logic related to the tail of the snake2, direction of + the 2-nd snake, logic for eating fruits(the 2-nd snake), cases in which + self.game_over_2 = True for the 2-nd snake. + + Note: + snake2 == the 2-nd snake. + """ + # Snake2's tail logic. + self.snake_segments_coord_x_2.append(0) + self.snake_segments_coord_y_2.append(0) + + prev_coord_x_2 = self.snake_segments_coord_x_2[0] + prev_coord_y_2 = self.snake_segments_coord_y_2[0] + + prev_coord2_x_2 = 0 + prev_coord2_y_2 = 0 + + self.snake_segments_coord_x_2[0] = self.head_x_coord_2 + self.snake_segments_coord_y_2[0] = self.head_y_coord_2 + + for i in range(1, self.num_of_snake_segments_2): + prev_coord2_x_2 = self.snake_segments_coord_x_2[i] + prev_coord2_y_2 = self.snake_segments_coord_y_2[i] + + self.snake_segments_coord_x_2[i] = prev_coord_x_2 + self.snake_segments_coord_y_2[i] = prev_coord_y_2 + + prev_coord_x_2 = prev_coord2_x_2 + prev_coord_y_2 = prev_coord2_y_2 + + # The logic related to the direction of the snake2. + if self.direction_2 == 'LEFT': + self.head_x_coord_2 -= 1 + elif self.direction_2 == 'RIGHT': + self.head_x_coord_2 += 1 + + elif self.direction_2 == 'UP': + self.head_y_coord_2 -= 1 + elif self.direction_2 == 'DOWN': + self.head_y_coord_2 += 1 + + # Snake2 and walls logic. + if self.snake_and_walls == 'can crawl through the walls': + if self.head_x_coord_2 > self.width - 1: + self.head_x_coord_2 = 41 + elif self.head_x_coord_2 == 40: + self.head_x_coord_2 = self.width - 1 + + if self.head_y_coord_2 > self.height: + self.head_y_coord_2 = 0 + elif self.head_y_coord_2 < 0: + self.head_y_coord_2 = self.height + + else: + if (self.head_x_coord_2 > self.width - 1 or + self.head_x_coord_2 == 40): + self.game_over_2 = True + elif (self.head_y_coord_2 > self.height or + self.head_y_coord_2 < 0): + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + # Cases for self.game_over_2 = True (snake2). + for i in range(self.num_of_snake_segments_2): + if (self.snake_segments_coord_x_2[i] == self.head_x_coord_2 and + self.snake_segments_coord_y_2[i] == self.head_y_coord_2): + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + # Eating fruit logic (snake2). + if (self.head_x_coord_2 == self.x_coord_of_fruit_2 and + self.head_y_coord_2 == self.y_coord_of_fruit_2): + + self.x_coord_of_fruit_2 = random.randint(41, self.width - 1) + self.y_coord_of_fruit_2 = random.randint(1, self.height - 1) + + self.x_coord_of_fruit_1 = self.x_coord_of_fruit_2 - 40 + self.y_coord_of_fruit_1 = self.y_coord_of_fruit_2 + + self.num_of_snake_segments_1 += 1 + self.score_2 += self.adding_points + + def get_game_over_status_player_1(self) -> bool: + return self.game_over_1 + + def get_game_over_status_player_2(self) -> bool: + return self.game_over_2 + + def set_game_mode_false(self): + self.gamemode = False + + def set_default_settings(self): + """Sets attribute settings before the beginning of the game itself.""" + # Basic settings + self.width = 60 + self.height = 20 + self.snake_and_walls = self.settings_storage['walls'] + self.centralize_score_n_time = " " * (int(self.width / 2) - 12) + self.game_speed = self.settings_storage['speed'] + self.time = self.settings_storage['game_time'] + self.countdown = 0 + + # Snake1's settings. + self.game_over_1 = False + self.score_1 = 0 + self.head_x_coord_1 = 10 + self.head_y_coord_1 = 10 + self.direction_1 = 'UP' + self.x_coord_of_fruit_1 = random.randint(1, 19) + self.y_coord_of_fruit_1 = random.randint(1, self.height - 1) + self.num_of_snake_segments_1 = self.settings_storage['length'] + self.snake_segments_coord_x_1 = [] + self.snake_segments_coord_y_1 = [] + + # Snake2's settings. + self.game_over_2 = False + self.score_2 = 0 + self.head_x_coord_2 = 50 + self.head_y_coord_2 = 10 + self.direction_2 = 'UP' + self.x_coord_of_fruit_2 = self.x_coord_of_fruit_1 + 40 + self.y_coord_of_fruit_2 = self.y_coord_of_fruit_1 + self.num_of_snake_segments_2 = self.settings_storage['length'] + self.snake_segments_coord_x_2 = [] + self.snake_segments_coord_y_2 = [] + + def get_score(self) -> int: + return self.score + + def is_the_time_over(self) -> bool: + time = self.time - self.countdown + if time == 0: + return True + return False + + def set_game_over_false(self): + self.game_over_1 = False + self.game_over_2 = False + + def determine_who_won(self) -> int: + if self.score_1 > self.score_2: + if (not self.game_over_1 and self.game_over_2 or not + self.game_over_1 and not self.game_over_2): + return 1 + else: + return 2 + elif self.score_1 < self.score_2: + if (self.game_over_1 and not self.game_over_2 or not + self.game_over_1 and not self.game_over_2): + return 2 + else: + return 1 + elif self.score_1 == self.score_2: + if not self.game_over_1 and self.game_over_2: + return 1 + elif self.game_over_1 and not self.game_over_2: + return 2 + else: + return 3 + + def get_score_of_players(self) -> list: + return [str(self.score_1), str(self.score_2)] + + def get_status_about_snake_and_fruit(self): + return self.another_player_gets_longer_status + + def create_new_threads(self) -> list: + threading_list = [] + t1 = threading.Thread(target=lambda: self.process_players_input_win()) + threading_list.extend([t1, ]) + return threading_list diff --git a/snake_project/gamemodes/classic_mode.py b/snake_project/gamemodes/classic_mode.py new file mode 100644 index 0000000..e36aec6 --- /dev/null +++ b/snake_project/gamemodes/classic_mode.py @@ -0,0 +1,329 @@ +import os +import random +import threading +import time + +from extra.tools.os_initializer import init_os +if init_os() == 'Windows': + from msvcrt import getch, kbhit +else: + import curses + from extra.tools.linux_functions import set_normal_term, set_curses_term +from extra.tools.asker import complex_ask +from extra.tools.key_setup import set_keyboard_keys +from extra.tools.clean_console_command_setter import set_cleaning_command + + +class ClassicModeGameManager: + """This class provides methods for implementing the Classic mod.""" + + def __init__(self, player_score_instance, game_menu_instance): + """Setting the keys on the keyboard for windows to control the snake and + inits os, dict-obj, player score instance and menu instance. + """ + self.keys = set_keyboard_keys() + self.UP = [self.keys["UP_1"][0], self.keys["UP_1"][1]] # w W + self.DOWN = [self.keys["DOWN_1"][0], self.keys["DOWN_1"][1]] # s S + self.LEFT = [self.keys["LEFT_1"][0], self.keys["LEFT_1"][1]] # a A + self.RIGHT = [self.keys["RIGHT_1"][0], self.keys["RIGHT_1"][1]] # d D + + self.RESET = set_cleaning_command() + self.platform = init_os() + self.settings_storage = {} + self.player_score = player_score_instance + self.menu = game_menu_instance + + def run(self): + """Method starting the mode automatically.""" + plays_current_game_in_current_gamemode = True + + while plays_current_game_in_current_gamemode: + wanna_continue_the_current_game = True + # Survey of the player about the game settings. + self.settings_storage = complex_ask(self.menu, 1) + + # Setting default settings according to the choice of the player. + self.set_default_settings() + while wanna_continue_the_current_game: + self.initialize_new_player() + + while not self.get_game_over_status(): + self.draw_whole_field() + + if self.platform == "Linux": + curses.wrapper(self.process_player_input_linux) + else: + threads = self.create_new_threads() + for t in threads: + t.start() + + self.process_hook_logic() + + if self.get_game_over_status(): # If a player loses... + score_of_player = self.get_score() + + # Recording the results. + self.player_score.write_down_player_score( + score_of_player, + ) + + print('Game Over\nYour Score: ' + str(self.get_score())) + print('Time: ' + str(self.get_time())) + print('~' * 40) + + player_resp = self.menu.ask_player_for_further_actions() + + if player_resp == 1: + self.set_default_settings() + self.set_game_over_false() + break + elif player_resp == 2: + wanna_continue_the_current_game = False + self.set_game_over_false() + break + elif player_resp == 3: + wanna_continue_the_current_game = False + plays_current_game_in_current_gamemode = False + self.set_game_over_false() + break + else: + exit() + + def initialize_new_player(self): + """Method sets the initial length of the snake and sets the adding + bonus for extra speed of the game. + """ + # Setting the default length of the snake. + self.snake_segments_coord_x = [self.head_x_coord + for i in range(self.num_of_snake_segments + 1)] + + offsets_of_the_snake_segment_y = 1 + for i in range(self.num_of_snake_segments + 1): + self.snake_segments_coord_y.append(self.head_y_coord + + offsets_of_the_snake_segment_y) + + offsets_of_the_snake_segment_y += 1 + + # Setting points for one fruit. + if self.game_speed == 0.08: + self.adding_points = 20 + elif self.game_speed == 0.06: + self.adding_points = 30 + else: + self.adding_points = 40 + + def draw_whole_field(self): + """Method constantly redraws the playing field.""" + if self.platform == "Linux": set_curses_term() + + # Drawing the upper edges. + for i in range(self.width + 1): + print("█", end="") + + print("") + + # Drawing snake's head, fruits, side edges, snake's tail, and a void. + for i in range(self.height + 1): + for j in range(self.width + 1): + if i == self.head_y_coord and j == self.head_x_coord: + print("0", end="") + + # Drawing fruit. + elif i == self.y_coord_of_fruit and j == self.x_coord_of_fruit: + print("*", end="") + + # Drawing side edges. + elif j == 0 or j == self.width: + if j == 0: + print("█", end="") + else: + print("█") + + else: + # Drawing tail. + print_tail = False + + for k in range(self.num_of_snake_segments): + if (self.snake_segments_coord_x[k] == j and + self.snake_segments_coord_y[k] == i): + print_tail = True + print("o", end="") + + if not print_tail: + print(" ", end="") + + # Drawing bottom edges. + for i in range(self.width + 1): + print("█", end="",) + + print("") + + # Drawing score&time indicator. + print(self.centralize_score_n_time + "Score: " + str(self.score) + + ' | ' + 'Time: ' + str(round(time.time() - self.time))) + + time.sleep(self.game_speed) + os.system(self.RESET) + + def process_player_input_win(self): + """Handles pressing keyboard keys on Windows OS. + + Changes the direction of the snake (depends on the key that player + presses). + """ + time.sleep(0.085) + if kbhit(): + key = ord(getch()) + if key in self.UP: + self.direction = 'UP' + elif key in self.DOWN: + self.direction = 'DOWN' + elif key in self.LEFT: + self.direction = 'LEFT' + elif key in self.RIGHT: + self.direction = 'RIGHT' + + def process_player_input_linux(self, win): + """Handles pressing keyboard keys on Linux OS. + + Changes the direction of the snake (depends on the key that player + presses). + """ + curses.use_default_colors() + win.nodelay(True) + key = "" + win.clear() + try: + key = win.getkey() + win.clear() + except Exception: + # No input + pass + + if key == "w" or key == "W": + self.direction = "UP" + elif key == "a" or key == "A": + self.direction = "LEFT" + elif key == "s" or key == "S": + self.direction = "DOWN" + elif key == "d" or key == "D": + self.direction = "RIGHT" + + def process_hook_logic(self): + """Handles logic that is connected with snake. + + Processes logic related to the tail of the snake, direction of the + snake, logic for eating fruits, touching the snake with walls, cases in + which self.game_over = True. + """ + # Snake's tail logic. + self.snake_segments_coord_x.append(0) + self.snake_segments_coord_y.append(0) + + prev_coord_x = self.snake_segments_coord_x[0] + prev_coord_y = self.snake_segments_coord_y[0] + + prev_coord2_x = 0 + prev_coord2_y = 0 + + self.snake_segments_coord_x[0] = self.head_x_coord + self.snake_segments_coord_y[0] = self.head_y_coord + + for i in range(1, self.num_of_snake_segments): + prev_coord2_x = self.snake_segments_coord_x[i] + prev_coord2_y = self.snake_segments_coord_y[i] + + self.snake_segments_coord_x[i] = prev_coord_x + self.snake_segments_coord_y[i] = prev_coord_y + + prev_coord_x = prev_coord2_x + prev_coord_y = prev_coord2_y + + # The logic related to the direction of the snake. + if self.direction == 'LEFT': + self.head_x_coord -= 1 + elif self.direction == 'RIGHT': + self.head_x_coord += 1 + elif self.direction == 'UP': + self.head_y_coord -= 1 + else: + self.head_y_coord += 1 + + # Snake and walls logic. + if self.snake_and_walls == 'can crawl through the walls': + if self.head_x_coord > self.width - 1: + self.head_x_coord = 1 + elif self.head_x_coord == 0: + self.head_x_coord = self.width - 1 + + if self.head_y_coord > self.height: + self.head_y_coord = 0 + elif self.head_y_coord < 0: + self.head_y_coord = self.height + else: + if self.head_x_coord > self.width - 1 or self.head_x_coord == 0: + self.game_over = True + if self.platform == "Linux": set_normal_term() + + elif self.head_y_coord > self.height or self.head_y_coord < 0: + self.game_over = True + if self.platform == "Linux": set_normal_term() + + # Cases for self.game_over = True. + for i in range(self.num_of_snake_segments): + + if (self.snake_segments_coord_x[i] == self.head_x_coord and + self.snake_segments_coord_y[i] == self.head_y_coord): + self.game_over = True + if self.platform == "Linux": set_normal_term() + + # Eating fruit logic. + if (self.head_x_coord == self.x_coord_of_fruit and + self.head_y_coord == self.y_coord_of_fruit): + + self.score += self.adding_points + + self.x_coord_of_fruit = random.randint(1, self.width - 1) + self.y_coord_of_fruit = random.randint(1, self.height - 1) + + self.num_of_snake_segments += 1 + + def get_game_over_status(self): + return self.game_over + + def set_game_over_false(self): + self.game_over = False + + def set_default_settings(self): + """Sets attribute settings before the beginning of the game itself.""" + # Basic settings. + self.width = self.settings_storage['width'] + self.height = self.settings_storage['height'] + self.snake_and_walls = self.settings_storage['walls'] + self.x_coord_of_fruit = random.randint(1, self.width - 1) + self.y_coord_of_fruit = random.randint(1, self.height - 1) + self.time = time.time() + self.centralize_score_n_time = " " * (int(self.width / 2) - 9) + self.game_speed = self.settings_storage['speed'] + self.score = 0 + self.game_over = False + + # Snake's settings. + self.head_x_coord = (self.width / 2) + self.head_y_coord = (self.height / 2) + 1 + self.direction = 'UP' + self.num_of_snake_segments = self.settings_storage['length'] + self.snake_segments_coord_x = [] + self.snake_segments_coord_y = [] + + def get_score(self) -> int: + return self.score + + def get_time(self) -> int: + return round(time.time() - self.time) + + def create_new_threads(self) -> list: + threading_list = [] + t1 = threading.Thread(target=lambda: self.process_player_input_win()) + threading_list.extend([t1, ]) + return threading_list diff --git a/snake_project/gamemodes/survival_mode.py b/snake_project/gamemodes/survival_mode.py new file mode 100644 index 0000000..e9b4ed9 --- /dev/null +++ b/snake_project/gamemodes/survival_mode.py @@ -0,0 +1,550 @@ +import os +import random +import threading +import time + +from extra.tools.os_initializer import init_os +if init_os() == 'Windows': + from msvcrt import getch, kbhit +else: + import curses + from extra.tools.linux_functions import set_normal_term, set_curses_term +from extra.tools.asker import complex_ask +from extra.tools.key_setup import set_keyboard_keys +from extra.tools.clean_console_command_setter import set_cleaning_command + + +class SurvivalModeGameManager: + """This class provides methods for implementing the Survival mode.""" + + def __init__(self, player_score_instance, game_menu_instance): + """Setting the keys on the keyboard to control the snakes and inits + os, dict-obj, player score instance and menu instance. + + Note: + _1 means something related to the 1-st snake, + _2 means something related to the 2-nd snake. + """ + self.keys = set_keyboard_keys() + self.UP_1 = [self.keys["UP_1"][0], self.keys["UP_1"][1]] # w W + self.DOWN_1 = [self.keys["DOWN_1"][0], self.keys["DOWN_1"][1]] # s S + self.LEFT_1 = [self.keys["LEFT_1"][0], self.keys["LEFT_1"][1]] # a A + self.RIGHT_1 = [self.keys["RIGHT_1"][0], self.keys["RIGHT_1"][1]] # d D + + self.UP_2 = self.keys["UP_2"] # ^ + self.DOWN_2 = self.keys["DOWN_2"] # v + self.LEFT_2 = self.keys["LEFT_2"] # < + self.RIGHT_2 = self.keys["RIGHT_2"] # > + self.keys_player_2 = [self.UP_2, self.DOWN_2, self.LEFT_2, self.RIGHT_2] + + self.RESET = set_cleaning_command() + self.platform = init_os() + self.settings_storage = {} # Dict for set_default_settings method. + self.player_score = player_score_instance + self.menu = game_menu_instance + + def run(self): + """Method starting the mode automatically.""" + plays_current_game_in_current_gamemode = True + + while plays_current_game_in_current_gamemode: + wanna_continue_the_current_game = True + + # Survey of the player about the game settings. + self.settings_storage = complex_ask(self.menu, 2) + # Setting default settings according to the choice of the player. + self.set_default_settings() + while wanna_continue_the_current_game: + self.initialize_new_players() + + while (not self.get_game_over_status_player_1() and not + self.get_game_over_status_player_2()): + self.draw_whole_field() + + if self.platform == "Linux": + for i in range(2): + curses.wrapper(self.process_players_input_linux) + else: + threads = self.create_new_threads() + for t in threads: + t.start() + + self.process_hook_logic_for_player_1() + self.process_hook_logic_for_player_2() + self.process_common_logic_of_2_snakes() + + # If player1 or player2 lose... + if (self.get_game_over_status_player_1() or + self.get_game_over_status_player_2()): + winner = self.determine_who_won() + + if winner == 1: + print("Player 1 won the game!") + elif winner == 2: + print("Player 2 won the game!") + else: + print('Draw!') + + print('Time: ' + str(self.get_time())) + print("P1: " + self.get_score_of_players()[0]) + print("P2: " + self.get_score_of_players()[1]) + print('~' * 40) + + player_resp = self.menu.ask_player_for_further_actions() + + if player_resp == 1: + self.set_default_settings() + self.set_game_over_false() + break + elif player_resp == 2: + wanna_continue_the_current_game = False + self.set_game_over_false() + break + elif player_resp == 3: + wanna_continue_the_current_game = False + plays_current_game_in_current_gamemode = False + self.set_game_over_false() + break + else: + exit() + + def initialize_new_players(self): + """Method sets the initial length of snakes and sets the adding + bonus for extra speed of the game. + """ + # Setting the default length of the 1-st snake. + self.snake_segments_coord_x_1 = [self.head_x_coord_1 + for i in range(self.num_of_snake_segments_1 + 1)] + + offsets_of_the_snake_segment_y_1 = 1 + for i in range(self.num_of_snake_segments_1 + 1): + self.snake_segments_coord_y_1.append(self.head_y_coord_1 + + offsets_of_the_snake_segment_y_1) + offsets_of_the_snake_segment_y_1 += 1 + + # Setting the default length of the 2-d snake. + self.snake_segments_coord_x_2 = [self.head_x_coord_2 + for i in range(self.num_of_snake_segments_2 + 1)] + + offsets_of_the_snake_segment_y_2 = 1 + for i in range(self.num_of_snake_segments_2 + 1): + self.snake_segments_coord_y_2.append(self.head_y_coord_2 + + offsets_of_the_snake_segment_y_2) + + offsets_of_the_snake_segment_y_2 += 1 + + # Setting points for one fruit. + if self.game_speed == 0.08: + self.adding_points = 20 + elif self.game_speed == 0.06: + self.adding_points = 30 + else: + self.adding_points = 40 + + def draw_whole_field(self): + """Method constantly redraws the playing field, snakes, fruit and + score&time indicator + """ + if self.platform == "Linux": set_curses_term() + + # Drawing the upper edges. + for i in range(self.width + 1): + print("█", end="") + + print(" ") + # Drawing snakes' head, fruit, side edges, snakes' tails, and a void. + for i in range(self.height + 1): + for j in range(self.width + 1): + # Drawing 1-st snake's head. + if i == self.head_y_coord_1 and j == self.head_x_coord_1: + print("0", end="") + + # Drawing 2-nd snake's head. + elif i == self.head_y_coord_2 and j == self.head_x_coord_2: + print("@", end="") + + # Drawing fruit. + elif i == self.y_coord_of_fruit and j == self.x_coord_of_fruit: + print("*", end="") + + # Drawing side edges. + elif j == 0 or j == self.width: + if j == 0: + print("█", end="") + else: + print("█") + else: + # Drawing tail of the 1-st snake. + print_tail_1 = False + for k in range(self.num_of_snake_segments_1): + if (self.snake_segments_coord_x_1[k] == j and + self.snake_segments_coord_y_1[k] == i): + print_tail_1 = True + print("o", end="") + + # Drawing tail of the 2-nd snake. + print_tail_2 = False + for t in range(self.num_of_snake_segments_2): + if (self.snake_segments_coord_x_2[t] == j and + self.snake_segments_coord_y_2[t] == i): + print_tail_2 = True + print("o", end="") + + if not print_tail_1 and not print_tail_2: + print(" ", end="") + + # Drawing the bottom edges. + for i in range(self.width + 1): + print("█", end="") + + print(" ") + # Drawing score&time indicator. + print(self.centralize_score_n_time + "P1: " + str(self.score_1) + + " | " + "Time: " + str(round(time.time() - self.time)) + " | " + + "P2: " + str(self.score_2)) + + time.sleep(self.game_speed) + os.system(self.RESET) + + def process_players_input_win(self): + """Handles pressing keyboard keys on Windows OS. + + Changes the direction of the snakes (depends on the keys that players + press). + """ + time.sleep(0.085) + if kbhit(): + key = ord(getch()) + if key not in self.keys_player_2: + if key in self.UP_1: + self.direction_1 = 'UP' + elif key in self.DOWN_1: + self.direction_1 = 'DOWN' + elif key in self.LEFT_1: + self.direction_1 = 'LEFT' + elif key in self.RIGHT_1: + self.direction_1 = 'RIGHT' + else: + if key == self.UP_2: + self.direction_2 = 'UP' + elif key == self.DOWN_2: + self.direction_2 = 'DOWN' + elif key == self.LEFT_2: + self.direction_2 = 'LEFT' + elif key == self.RIGHT_2: + self.direction_2 = 'RIGHT' + + def process_players_input_linux(self, win): + """Handles pressing keyboard keys on Linux OS. + + Changes the direction of the snake (depends on the key that player + presses). + """ + win.clear() + curses.use_default_colors() + win.nodelay(True) + key = "" + stdscr = curses.initscr() + curses.cbreak() + stdscr.keypad(5) + stdscr.refresh() + win.clear() + key = stdscr.getch() + win.clear() + + if key not in self.keys_player_2: + if key in self.UP_1: + self.direction_1 = "UP" + elif key in self.LEFT_1: + self.direction_1 = "LEFT" + elif key in self.DOWN_1: + self.direction_1 = "DOWN" + elif key in self.RIGHT_1: + self.direction_1 = "RIGHT" + else: + if key == self.UP_2: + self.direction_2 = "UP" + elif key == self.LEFT_2: + self.direction_2 = "LEFT" + elif key == self.DOWN_2: + self.direction_2 = "DOWN" + elif key == self.RIGHT_2: + self.direction_2 = "RIGHT" + + def process_hook_logic_for_player_1(self): + """Handles the 1-st snake logic. + + Handles logic related to the tail of the 1-st snake, direction of + the 1-st snake, logic for eating fruits(the 1-st snake), cases in which + self.game_over_1 = True for the 1-st snake. + + Note: + snake1 = the 1-st snake. + """ + # Snake1's tail logic. + self.snake_segments_coord_x_1.append(0) + self.snake_segments_coord_y_1.append(0) + + prev_coord_x_1 = self.snake_segments_coord_x_1[0] + prev_coord_y_1 = self.snake_segments_coord_y_1[0] + + prev_coord2_x_1 = 0 + prev_coord2_y_1 = 0 + + self.snake_segments_coord_x_1[0] = self.head_x_coord_1 + self.snake_segments_coord_y_1[0] = self.head_y_coord_1 + + for i in range(1, self.num_of_snake_segments_1): + prev_coord2_x_1 = self.snake_segments_coord_x_1[i] + prev_coord2_y_1 = self.snake_segments_coord_y_1[i] + + self.snake_segments_coord_x_1[i] = prev_coord_x_1 + self.snake_segments_coord_y_1[i] = prev_coord_y_1 + + prev_coord_x_1 = prev_coord2_x_1 + prev_coord_y_1 = prev_coord2_y_1 + + # The logic related to the direction of the snake1. + if self.direction_1 == 'LEFT': + self.head_x_coord_1 -= 1 + elif self.direction_1 == 'RIGHT': + self.head_x_coord_1 += 1 + + elif self.direction_1 == 'UP': + self.head_y_coord_1 -= 1 + else: + self.head_y_coord_1 += 1 + + # Snake1 and walls logic. + if self.snake_and_walls == 'can crawl through the walls': + if self.head_x_coord_1 > self.width - 1: + self.head_x_coord_1 = 1 + elif self.head_x_coord_1 == 0: + self.head_x_coord_1 = self.width - 1 + + if self.head_y_coord_1 > self.height: + self.head_y_coord_1 = 0 + elif self.head_y_coord_1 < 0: + self.head_y_coord_1 = self.height + + else: + if (self.head_x_coord_1 > self.width - 1 or + self.head_x_coord_1 == 0): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + elif (self.head_y_coord_1 > self.height or + self.head_y_coord_1 < 0): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + # Cases for self.game_over_1 = True (snake1). + for i in range(self.num_of_snake_segments_1): + if (self.snake_segments_coord_x_1[i] == self.head_x_coord_1 and + self.snake_segments_coord_y_1[i] == self.head_y_coord_1): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + # Eating fruit logic (snake1) + if (self.head_x_coord_1 == self.x_coord_of_fruit and + self.head_y_coord_1 == self.y_coord_of_fruit): + + self.x_coord_of_fruit = random.randint(1, self.width - 1) + self.y_coord_of_fruit = random.randint(1, self.height - 1) + + self.num_of_snake_segments_1 += 1 + self.score_1 += self.adding_points + + def process_hook_logic_for_player_2(self): + """Handles the 2-nd Snake logic. + + Handles logic related to the tail of the snake2, direction of + the 2-nd snake, logic for eating fruits(the 2-nd snake), cases in which + self.game_over_2 = True for the 2-nd snake. + + Note: + snake2 == the 2-nd snake. + """ + # Snake2's tail logic. + self.snake_segments_coord_x_2.append(0) + self.snake_segments_coord_y_2.append(0) + + prev_coord_x_2 = self.snake_segments_coord_x_2[0] + prev_coord_y_2 = self.snake_segments_coord_y_2[0] + + prev_coord2_x_2 = 0 + prev_coord2_y_2 = 0 + + self.snake_segments_coord_x_2[0] = self.head_x_coord_2 + self.snake_segments_coord_y_2[0] = self.head_y_coord_2 + + for i in range(1, self.num_of_snake_segments_2): + prev_coord2_x_2 = self.snake_segments_coord_x_2[i] + prev_coord2_y_2 = self.snake_segments_coord_y_2[i] + + self.snake_segments_coord_x_2[i] = prev_coord_x_2 + self.snake_segments_coord_y_2[i] = prev_coord_y_2 + + prev_coord_x_2 = prev_coord2_x_2 + prev_coord_y_2 = prev_coord2_y_2 + + # The logic related to the direction of the snake2. + if self.direction_2 == 'LEFT': + self.head_x_coord_2 -= 1 + elif self.direction_2 == 'RIGHT': + self.head_x_coord_2 += 1 + + elif self.direction_2 == 'UP': + self.head_y_coord_2 -= 1 + else: + self.head_y_coord_2 += 1 + + # Snake2 and walls logic. + if self.snake_and_walls == 'can crawl through the walls': + if self.head_x_coord_2 > self.width - 1: + self.head_x_coord_2 = 1 + elif self.head_x_coord_2 == 0: + self.head_x_coord_2 = self.width - 1 + + if self.head_y_coord_2 > self.height: + self.head_y_coord_2 = 0 + elif self.head_y_coord_2 < 0: + self.head_y_coord_2 = self.height + + else: + if self.head_x_coord_2 > self.width - 1 or self.head_x_coord_2 == 0: + self.game_over_2 = True + elif self.head_y_coord_2 > self.height or self.head_y_coord_2 < 0: + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + # Cases for self.game_over_2 = True (snake2). + for i in range(self.num_of_snake_segments_2): + if (self.snake_segments_coord_x_2[i] == self.head_x_coord_2 and + self.snake_segments_coord_y_2[i] == self.head_y_coord_2): + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + # Eating fruit logic (snake2). + if (self.head_x_coord_2 == self.x_coord_of_fruit and + self.head_y_coord_2 == self.y_coord_of_fruit): + + self.x_coord_of_fruit = random.randint(1, self.width - 1) + self.y_coord_of_fruit = random.randint(1, self.height - 1) + + self.num_of_snake_segments_2 += 1 + self.score_2 += self.adding_points + + def process_common_logic_of_2_snakes(self): + """Method adds some logic because of increased number of snakes in one + game field. + + Method handles the collision of both heads of two snakes. This method + also handles cases when the head of the first snake collides with the + body of the second snake, and Vice versa: cases when the head of the + second snake crashes into the body of the first snake. + """ + # If both heads of snakes collided... + if (self.head_x_coord_1 == self.head_x_coord_2 and + self.head_y_coord_1 == self.head_y_coord_2): + self.game_over_1 = True + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + for i in range(self.num_of_snake_segments_1): + # If the head of the second snake hit the tail of the first snake... + if (self.snake_segments_coord_x_1[i] == self.head_x_coord_2 and + self.snake_segments_coord_y_1[i] == self.head_y_coord_2): + self.game_over_2 = True + if self.platform == "Linux": set_normal_term() + + for i in range(self.num_of_snake_segments_2): + # If the head of the first snake hit the tail of the second snake... + if (self.snake_segments_coord_x_2[i] == self.head_x_coord_1 and + self.snake_segments_coord_y_2[i] == self.head_y_coord_1): + self.game_over_1 = True + if self.platform == "Linux": set_normal_term() + + def get_game_over_status_player_1(self) -> bool: + return self.game_over_1 + + def get_game_over_status_player_2(self) -> bool: + return self.game_over_2 + + def set_game_mode_false(self): + self.gamemode = False + + def set_default_settings(self): + """Sets attribute settings before the beginning of the game itself.""" + # Basic settings. + self.width = self.settings_storage['width'] + self.height = self.settings_storage['height'] + self.snake_and_walls = self.settings_storage['walls'] + self.game_speed = self.settings_storage['speed'] + self.time = time.time() + self.centralize_score_n_time = " " * (int(self.width / 2) - 12) + self.x_coord_of_fruit = random.randint(1, self.width - 1) + self.y_coord_of_fruit = random.randint(1, self.height - 1) + self.adding_points = 0 + + # Snake1's settings. + self.game_over_1 = False + self.score_1 = 0 + self.head_x_coord_1 = (self.width / 2) - 5 + self.head_y_coord_1 = (self.height / 2) + 1 + self.direction_1 = 'UP' + self.num_of_snake_segments_1 = self.settings_storage['length'] + self.snake_segments_coord_x_1 = [] + self.snake_segments_coord_y_1 = [] + + # Snake2's settings. + self.game_over_2 = False + self.score_2 = 0 + self.head_x_coord_2 = (self.width / 2) + 5 + self.head_y_coord_2 = (self.height / 2) + 1 + self.direction_2 = 'UP' + self.num_of_snake_segments_2 = self.settings_storage['length'] + self.snake_segments_coord_x_2 = [] + self.snake_segments_coord_y_2 = [] + + def get_score(self) -> int: + return self.score + + def get_time(self) -> int: + return round(time.time() - self.time) + + def set_game_over_false(self): + self.game_over_1 = False + self.game_over_2 = False + + def determine_who_won(self) -> int: + if self.score_1 > self.score_2: + if not self.game_over_1 and self.game_over_2: + return 1 + else: + return 2 + + elif self.score_1 < self.score_2: + if self.game_over_1 and not self.game_over_2: + return 2 + else: + return 1 + elif self.score_1 == self.score_2: + if not self.game_over_1 and self.game_over_2: + return 1 + elif self.game_over_1 and not self.game_over_2: + return 2 + else: + return 3 + + def get_score_of_players(self) -> list: + return [str(self.score_1), str(self.score_2)] + + def get_status_about_snake_and_fruit(self): + return self.another_player_gets_longer_status + + def create_new_threads(self) -> list: + threading_list = [] + t1 = threading.Thread(target=lambda: self.process_players_input_win()) + threading_list.extend([t1, ]) + return threading_list diff --git a/snake_project/snake_game.py b/snake_project/snake_game.py new file mode 100644 index 0000000..8ce07ac --- /dev/null +++ b/snake_project/snake_game.py @@ -0,0 +1,43 @@ +from extra.tools.git_repo_opener import open_git_repo_in_browser +from extra.game_environment.menu_files.menu import Menu +from extra.game_environment.score_files.score import Score + +from gamemodes.classic_mode import ClassicModeGameManager +from gamemodes.survival_mode import SurvivalModeGameManager +from gamemodes.battle_mode import BattleModeGameManager + + +game_menu = Menu() +game_menu.show_ascii_art_and_loading() +game_menu.show_welcome_message_to_player() +player_name = game_menu.set_name_of_player() +player_score = Score(player_name) + +# Main loop +while True: + game_menu.ask_player_about_choice_in_menu() + menu_selection = game_menu.set_choice_in_menu() + + if menu_selection == 1: + classic_mode = ClassicModeGameManager(player_score, game_menu) + classic_mode.run() + + elif menu_selection == 2: + survival_mode = SurvivalModeGameManager(player_score, game_menu) + survival_mode.run() + + elif menu_selection == 3: + battle_mode = BattleModeGameManager(player_score, game_menu) + battle_mode.run() + + elif menu_selection == 4: + player_score.show_score() + + elif menu_selection == 5: + open_git_repo_in_browser() + + elif menu_selection == 6: + game_menu.show_controls_for_snakes() + + else: + exit() diff --git a/snake_project/tests/__init__.py b/snake_project/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/snake_project/tests/test_game_records.py b/snake_project/tests/test_game_records.py new file mode 100644 index 0000000..85fd1fa --- /dev/null +++ b/snake_project/tests/test_game_records.py @@ -0,0 +1,29 @@ +import os +from colorama import Fore, deinit, init + + +records_dir = "snake_project/extra/game_environment/score_files/records" + +scores = records_dir + "/scores.txt" +players = records_dir + "/players.txt" +date_and_time = records_dir + "/date_n_time.txt" + +init(autoreset=True) # Initialization of colorama. +print("\n" + "~" * 42 + Fore.CYAN + "[NOTE]" + Fore.RESET + "~" * 42) +print("Before commit you need to make sure that there are 3 files in the " + + "'records' folder and\nthey are all empty:") + +print(date_and_time) +print(players) +print(scores) +deinit() # Uninstallation of colorama. +print("\n" + "~" * 90) + + +def test_game_records_for_commit(): + global records_dir, scores, players, date_and_time + + for file in [scores, players, date_and_time]: + assert os.stat(file).st_size == 0 + + assert len(os.listdir(records_dir)) == 3 diff --git a/snake_project/tests/test_gamemodes.py b/snake_project/tests/test_gamemodes.py new file mode 100644 index 0000000..466b216 --- /dev/null +++ b/snake_project/tests/test_gamemodes.py @@ -0,0 +1,451 @@ +from extra.game_environment.menu_files.menu import Menu +from extra.game_environment.score_files.score import Score + +from gamemodes.classic_mode import ClassicModeGameManager +from gamemodes.survival_mode import SurvivalModeGameManager +from gamemodes.battle_mode import BattleModeGameManager + + +class TestClassicGamemodeClass: + + def setup(self): + """Initialization of the game mode, player and installation of default + settings. + """ + menu_inst = Menu() + score_inst = Score('TestName') + self.gamemode = ClassicModeGameManager(score_inst, menu_inst) + self.gamemode.settings_storage['width'] = 40 + self.gamemode.settings_storage['height'] = 20 + + walls = "can crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.settings_storage['speed'] = 0.08 + self.gamemode.settings_storage['length'] = 3 + self.gamemode.set_default_settings() + self.gamemode.initialize_new_player() + + def test_initialize_new_player_method_classic_mode(self): + """Testing the content of snake_segments' lists and the value + of adding points. + """ + self.setup() + assert len(self.gamemode.snake_segments_coord_x) != 0 + assert len(self.gamemode.snake_segments_coord_y) != 0 + assert self.gamemode.adding_points in [20, 30, 40] + + def test_snake_and_walls_logic_classic_mode(self): + # Testing the ability to pass through one wall and exit the other. + # Note: see self.gamemode.settings_storage['walls'] in setup method of + # this class. + self.gamemode.head_x_coord = 41 + self.gamemode.process_hook_logic() + assert self.gamemode.head_x_coord == 1 + assert not self.gamemode.game_over + + self.gamemode.head_x_coord = 0 + self.gamemode.process_hook_logic() + assert self.gamemode.head_x_coord == self.gamemode.width - 1 + assert not self.gamemode.game_over + + self.gamemode.head_y_coord = 22 + self.gamemode.process_hook_logic() + assert self.gamemode.head_y_coord == 0 + assert not self.gamemode.game_over + + self.gamemode.head_y_coord = -1 + self.gamemode.process_hook_logic() + assert self.gamemode.head_y_coord == self.gamemode.height + assert not self.gamemode.game_over + + # Testing logic when the ability to pass through the wall is disabled. + walls = "can't crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.set_default_settings() + self.gamemode.initialize_new_player() + + self.gamemode.head_x_coord = 40 + self.gamemode.process_hook_logic() + assert self.gamemode.game_over + self.gamemode.game_over = False + + self.gamemode.head_x_coord = 0 + self.gamemode.process_hook_logic() + assert self.gamemode.game_over + self.gamemode.game_over = False + + self.gamemode.head_y_coord = 21 + self.gamemode.process_hook_logic() + assert self.gamemode.game_over + self.gamemode.game_over = False + + self.gamemode.head_y_coord = -1 + self.gamemode.process_hook_logic() + assert self.gamemode.game_over + self.gamemode.game_over = False + + def test_snake_eats_fruit_logic_classic_mode(self): + # Testing the logic of increasing the number of snake's segments when it + # eats fruit. + self.gamemode.head_x_coord = 20 + self.gamemode.head_y_coord = 11 + self.gamemode.x_coord_of_fruit = 20 + self.gamemode.y_coord_of_fruit = 10 + self.gamemode.process_hook_logic() + assert self.gamemode.num_of_snake_segments == 4 + + def test_snake_eats_itself_logic_classic_mode(self): + self.gamemode.head_x_coord = 20 + self.gamemode.head_y_coord = 13 + self.gamemode.process_hook_logic() + assert self.gamemode.game_over + + +class TestSurvivalGamemodeClass: + + def setup(self): + """Initialization of the game mode, players and installation of default + settings. + """ + menu_inst = Menu() + score_inst = Score('TestName') + self.gamemode = SurvivalModeGameManager(score_inst, menu_inst) + self.gamemode.settings_storage['width'] = 40 + self.gamemode.settings_storage['height'] = 20 + + walls = "can crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.settings_storage['speed'] = 0.08 + self.gamemode.settings_storage['length'] = 3 + self.gamemode.set_default_settings() + self.gamemode.initialize_new_players() + + def test_initialize_new_players_method_survival_mode(self): + """Testing the content of snake_segments_coord_x and + snake_segments_coord_y for snake 1 and snake 2 and the value of adding + points. + """ + self.setup() + assert len(self.gamemode.snake_segments_coord_x_1) != 0 + assert len(self.gamemode.snake_segments_coord_y_1) != 0 + assert len(self.gamemode.snake_segments_coord_x_2) != 0 + assert len(self.gamemode.snake_segments_coord_y_2) != 0 + assert self.gamemode.adding_points in [20, 30, 40] + + def test_snakes_and_walls_logic_survival_mode(self): + # Testing the ability to pass through one wall and exit the other for + # snake 1. + # Note: see self.gamemode.settings_storage['walls'] in setup method of + # this class. + self.gamemode.head_x_coord_1 = 41 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_x_coord_1 == 1 + assert not self.gamemode.game_over_1 + + self.gamemode.head_x_coord_1 = 0 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_x_coord_1 == self.gamemode.width - 1 + assert not self.gamemode.game_over_1 + + self.gamemode.head_y_coord_1 = 22 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_y_coord_1 == 0 + assert not self.gamemode.game_over_1 + + self.gamemode.head_y_coord_1 = -1 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_y_coord_1 == self.gamemode.height + assert not self.gamemode.game_over_1 + + # Testing the ability to pass through one wall and exit the other for + # snake 2. + self.gamemode.head_x_coord_2 = 41 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_x_coord_2 == 1 + assert not self.gamemode.game_over_2 + + self.gamemode.head_x_coord_2 = 0 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_x_coord_2 == self.gamemode.width - 1 + assert not self.gamemode.game_over_2 + + self.gamemode.head_y_coord_2 = 22 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_y_coord_2 == 0 + assert not self.gamemode.game_over_2 + + self.gamemode.head_y_coord_2 = -1 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_y_coord_2 == self.gamemode.height + assert not self.gamemode.game_over_2 + + # Testing logic when the ability to pass through the wall is disabled. + walls = "can't crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.set_default_settings() + self.gamemode.initialize_new_players() + + # Snake 1. + self.gamemode.head_x_coord_1 = 40 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_x_coord_1 = 0 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_y_coord_1 = 21 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_y_coord_1 = -1 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + # Snake 2. + self.gamemode.head_x_coord_2 = 40 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_x_coord_2 = 0 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_y_coord_2 = 21 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_y_coord_2 = -1 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + def test_snakes_eat_fruit_logic_survival_mode(self): + # Testing the logic of increasing the number of snakes' segments when + # they eat fruit. + + # Test for snake 1. + self.gamemode.head_x_coord_1 = 20 + self.gamemode.head_y_coord_1 = 11 + self.gamemode.x_coord_of_fruit = 20 + self.gamemode.y_coord_of_fruit = 10 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.num_of_snake_segments_1 == 4 + + # Test for snake 2. + self.gamemode.head_x_coord_2 = 20 + self.gamemode.head_y_coord_2 = 11 + self.gamemode.x_coord_of_fruit = 20 + self.gamemode.y_coord_of_fruit = 10 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.num_of_snake_segments_2 == 4 + + def test_snakes_eat_themselves_logic_survival_mode(self): + # Snake 1. + self.gamemode.head_x_coord_1 = 15 + self.gamemode.head_y_coord_1 = 13 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + # Snake 2. + self.gamemode.head_x_coord_2 = 25 + self.gamemode.head_y_coord_2 = 13 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + def test_common_logic_of_2_snakes_survival_mode(self): + # If two heads of snakes have the same coordinates, they lose. + self.gamemode.head_x_coord_1 = 20 + self.gamemode.head_y_coord_1 = 20 + self.gamemode.head_x_coord_2 = 20 + self.gamemode.head_y_coord_2 = 20 + + self.gamemode.process_common_logic_of_2_snakes() + assert self.gamemode.game_over_1 + assert self.gamemode.game_over_2 + self.gamemode.game_over_1 = False + self.gamemode.game_over_2 = False + + # If the coordinates of the first snake match the coordinates of the + # elements of the tail of the 2nd snake, then the first snake loses and + # vice versa. + + # Initial coords for the 2-nd snake's segments. + self.gamemode.head_x_coord_1 = 25 + self.gamemode.head_y_coord_1 = 13 + self.gamemode.process_common_logic_of_2_snakes() + assert self.gamemode.game_over_1 + + # Initial coords for the 1-st snake's segments. + self.gamemode.head_x_coord_2 = 15 + self.gamemode.head_y_coord_2 = 13 + self.gamemode.process_common_logic_of_2_snakes() + assert self.gamemode.game_over_2 + + +class TestBattleGamemodeClass: + + def setup(self): + """Initialization of the game mode, players and installation of default + settings. + """ + menu_inst = Menu() + score_inst = Score('TestName') + self.gamemode = BattleModeGameManager(score_inst, menu_inst) + + walls = "can crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.settings_storage['speed'] = 0.08 + self.gamemode.settings_storage['game_time'] = 1000 + self.gamemode.settings_storage['length'] = 3 + self.gamemode.set_default_settings() + self.gamemode.initialize_new_players() + + def test_initialize_new_players_method_battle_mode(self): + """Testing the content of snake_segments_coord_x and + snake_segments_coord_y for snake 1 and snake 2 and the value of adding + points. + """ + self.setup() + assert len(self.gamemode.snake_segments_coord_x_1) != 0 + assert len(self.gamemode.snake_segments_coord_y_1) != 0 + assert len(self.gamemode.snake_segments_coord_x_2) != 0 + assert len(self.gamemode.snake_segments_coord_y_2) != 0 + assert self.gamemode.adding_points in [20, 30, 40] + + def test_snakes_and_walls_logic_battle_mode(self): + # Testing the ability to pass through one wall and exit the other for + # snake 1. + # Note: see self.gamemode.settings_storage['walls'] in setup method of + # this class. + self.gamemode.head_x_coord_1 = 20 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_x_coord_1 == 1 + assert not self.gamemode.game_over_1 + + self.gamemode.head_x_coord_1 = 0 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_x_coord_1 == 19 + assert not self.gamemode.game_over_1 + + self.gamemode.head_y_coord_1 = 22 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_y_coord_1 == 0 + assert not self.gamemode.game_over_1 + + self.gamemode.head_y_coord_1 = -1 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.head_y_coord_1 == 20 + assert not self.gamemode.game_over_1 + + # Testing the ability to pass through one wall and exit the other for + # snake 2. + self.gamemode.head_x_coord_2 = 60 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_x_coord_2 == 41 + assert not self.gamemode.game_over_2 + + self.gamemode.head_x_coord_2 = 40 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_x_coord_2 == 59 + assert not self.gamemode.game_over_2 + + self.gamemode.head_y_coord_2 = 22 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_y_coord_2 == 0 + assert not self.gamemode.game_over_2 + + self.gamemode.head_y_coord_2 = -1 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.head_y_coord_2 == 20 + assert not self.gamemode.game_over_2 + + # Testing logic when the ability to pass through the wall is disabled. + walls = "can't crawl through the walls" + self.gamemode.settings_storage['walls'] = walls + self.gamemode.set_default_settings() + self.gamemode.initialize_new_players() + + # Snake 1. + self.gamemode.head_x_coord_1 = 20 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_x_coord_1 = 0 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_y_coord_1 = 21 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + self.gamemode.head_y_coord_1 = -1 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + self.gamemode.game_over_1 = False + + # Snake 2. + self.gamemode.head_x_coord_2 = 60 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_x_coord_2 = 40 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_y_coord_2 = 21 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + self.gamemode.head_y_coord_2 = -1 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2 + self.gamemode.game_over_2 = False + + def test_snakes_eat_fruit_logic_battle_mode(self): + # Testing the logic of increasing the number of segments of the 2-nd + # snake when the first one eats fruit and vice versa. + + # Snake 1. + self.gamemode.head_x_coord_1 = 10 + self.gamemode.head_y_coord_1 = 11 + self.gamemode.x_coord_of_fruit_1 = 10 + self.gamemode.y_coord_of_fruit_1 = 10 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.num_of_snake_segments_2 == 4 + + # Snake 2. + self.gamemode.head_x_coord_2 = 55 + self.gamemode.head_y_coord_2 = 11 + self.gamemode.x_coord_of_fruit_2 = 55 + self.gamemode.y_coord_of_fruit_2 = 10 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.num_of_snake_segments_1 == 4 + + def test_snakes_eat_themselves_logic_battle_mode(self): + # Snake 1. + self.gamemode.head_x_coord_1 = 10 + self.gamemode.head_y_coord_1 = 13 + self.gamemode.process_hook_logic_for_player_1() + assert self.gamemode.game_over_1 + + # Snake 2. + self.gamemode.head_x_coord_2 = 50 + self.gamemode.head_y_coord_2 = 13 + self.gamemode.process_hook_logic_for_player_2() + assert self.gamemode.game_over_2