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