-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
1,514 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
lint: | ||
poetry run isort src tests | ||
poetry run flake8 src tests | ||
poetry run mypy src | ||
poetry run mypy tests | ||
|
||
test: | ||
poetry run pytest | ||
|
||
build: | ||
python src/hangman/main.py | ||
install: | ||
pip install poetry | ||
poetry install |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
This is a simple game hangman | ||
|
||
to install dependencies got to repository "Industrial_developed_hangman" by `cd .\Industrial_developed_hangman\` and run `make install` | ||
|
||
to start it use `make build` command | ||
|
||
also makefile have lint command to lint source code |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[tool.poetry] | ||
name = "Hangman" | ||
version = "0.2.0" | ||
description = "" | ||
authors = ["DiodDan <[email protected]>"] | ||
readme = "README.md" | ||
packages = [{include = "hangman", from = "src"}] | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.9" | ||
requests = "2.31.0" | ||
colorama = "0.4.6" | ||
beautifulsoup4 = "4.12" | ||
|
||
|
||
[tool.poetry.group.dev.dependencies] | ||
mypy = "1.5.1" | ||
wemake-python-styleguide = "0.18.0" | ||
isort = "5.12.0" | ||
pytest = "7.4.2" | ||
pytest-cov = "4.1.0" | ||
pytest-timeout = "2.2.0" | ||
pytest-randomly = "3.15.0" | ||
requests-mock = "1.11.0" | ||
pytest-freezer = "0.4.8" | ||
types-requests = " 2.31.0.2" | ||
|
||
[build-system] | ||
requires = ["poetry-core", "colorama", "bs4", "requests"] | ||
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.isort] | ||
line_length = 80 | ||
multi_line_output = 3 | ||
include_trailing_comma = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[pytest] | ||
markers = | ||
internet_required: marks tests that requires internet connection (deselect with '-m "not internet_required"') | ||
serial | ||
timeout = 20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
[flake8] | ||
max-line-length = 120 | ||
docstring_style = sphinx | ||
max-arguments = 6 | ||
exps-for-one-empty-line = 0 | ||
ignore = | ||
D100, | ||
D104, | ||
|
||
per-file-ignores = | ||
tests/*: | ||
# Missing docstring in public class | ||
D101, | ||
# Missing docstring in public method | ||
D102, | ||
# Missing docstring in public function | ||
D103, | ||
# Missing docstring in magic method | ||
D105, | ||
# Missing docstring in __init__ | ||
D107, | ||
# Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. | ||
S101, | ||
# Found magic number | ||
WPS432, | ||
# Found wrong keyword: pass | ||
WPS420, | ||
# Found incorrect node inside `class` body | ||
WPS604, | ||
# Found outer scope names shadowing: message_update | ||
WPS442, | ||
# Found comparison with float or complex number | ||
WPS459, | ||
# split between test action and assert | ||
WPS473, | ||
# Found compare with falsy constant | ||
WPS520, | ||
# Found string literal over-use | ||
WPS226 | ||
# Found overused expression | ||
WPS204 | ||
# Found too many module members | ||
WPS202 | ||
|
||
[mypy] | ||
ignore_missing_imports = True | ||
check_untyped_defs = True | ||
disallow_untyped_calls = True |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import random | ||
import time | ||
from enum import Enum | ||
from pathlib import Path | ||
from typing import Callable, List | ||
|
||
import requests | ||
from bs4 import BeautifulSoup | ||
from colorama import Fore, Style | ||
|
||
DEBUG = False | ||
success_code = 200 | ||
request_timeout = 1000 | ||
data_path = Path(__file__).parent.parent.parent / 'Data' | ||
year = 4800566455 | ||
|
||
|
||
class Source(Enum): | ||
"""Enum that represents switch between local and web word parsing.""" | ||
|
||
FROM_FILE = 0 # noqa: WPS115 | ||
FROM_INTERNET = 1 # noqa: WPS115 | ||
|
||
|
||
def print_wrong(text: str, print_function: Callable[[str], None]) -> None: | ||
""" | ||
Print styled text(red). | ||
:parameter text: text to print. | ||
:parameter print_function: Function that will be used to print in game. | ||
""" | ||
text_to_print = Style.RESET_ALL + Fore.RED + text | ||
print_function(text_to_print) | ||
|
||
|
||
def print_right(text: str, print_function: Callable[[str], None]) -> None: | ||
""" | ||
Print styled text(red). | ||
:parameter text: text to print. | ||
:parameter print_function: Function that will be used to print in game. | ||
""" | ||
print_function(Style.RESET_ALL + Fore.GREEN + text) | ||
|
||
|
||
def parse_word_from_local(choice_function: Callable[[List[str]], str] = random.choice) -> str: | ||
# noqa: DAR201 | ||
""" | ||
Parse word from local file. | ||
:parameter choice_function: Function that will be used to choice a word from file. | ||
:returns str: string that contains the word. | ||
:raises FileNotFoundError: file to read words not found. | ||
""" | ||
try: | ||
with open(data_path / 'local_words.txt', encoding='utf8') as words_file: | ||
return choice_function(words_file.read().split('\n')) | ||
except FileNotFoundError: | ||
raise FileNotFoundError('File local_words.txt was not found') | ||
|
||
|
||
def parse_word_from_site(url: str = 'https://randomword.com') -> str: | ||
# noqa: DAR201 | ||
""" | ||
Parse word from website. | ||
:param url: url that word will be parsed from. | ||
:return Optional[str]: string that contains the word. | ||
:raises ConnectionError: no connection to the internet. | ||
:raises RuntimeError: something go wrong with getting the word from site. | ||
""" | ||
try: | ||
page: requests.Response = requests.get(url, timeout=request_timeout) | ||
except ConnectionError: | ||
raise ConnectionError('There is no connection to the internet') | ||
if page.status_code == success_code: | ||
soup = BeautifulSoup(page.text, 'html.parser') | ||
return soup.find('div', id='random_word').text | ||
raise RuntimeError('Something go wrong with getting the word from site') | ||
|
||
|
||
class MainProcess(object): | ||
"""Manages game process.""" | ||
|
||
def __init__(self, source: Enum, pr_func: Callable, in_func: Callable, ch_func: Callable) -> None: | ||
""" | ||
Init MainProcess object. | ||
:parameter in_func: Function that will be used to get input in game. | ||
:parameter source: Represents source to get word. | ||
:parameter pr_func: Function that will be used to print in game. | ||
:parameter ch_func: Function that will be used to choice word. | ||
""" | ||
self._source = source | ||
self._answer_word = '' | ||
self._word_string_to_show = '' | ||
self._guess_attempts_coefficient = 2 | ||
self._print_function = pr_func | ||
self._input_function = in_func | ||
self._choice_function = ch_func | ||
|
||
def get_word(self) -> str: | ||
# noqa: DAR201 | ||
""" | ||
Parse word(wrapper for local and web parse). | ||
:returns str: string that contains the word. | ||
:raises AttributeError: Not existing enum | ||
""" | ||
if self._source == Source.FROM_INTERNET: | ||
return parse_word_from_site() | ||
elif self._source == Source.FROM_FILE: | ||
return parse_word_from_local(self._choice_function) | ||
raise AttributeError('Non existing enum') | ||
|
||
def user_lose(self) -> None: | ||
"""Print text for end of game and exits.""" | ||
print_wrong(f"YOU LOST(the word was '{self._answer_word}')", self._print_function) # noqa:WPS305 | ||
|
||
def user_win(self) -> None: | ||
"""Print text for end of game and exits.""" | ||
print_wrong(f'{self._word_string_to_show} YOU WON', self._print_function) # noqa:WPS305 | ||
|
||
def game_process(self, user_character: str) -> bool: | ||
# noqa: DAR201 | ||
""" | ||
Process user input. | ||
:parameter user_character: User character. | ||
:returns bool: state of game. | ||
""" | ||
if user_character in self._answer_word: | ||
word_list_to_show = list(self._word_string_to_show) | ||
for index, character in enumerate(self._answer_word): | ||
if character == user_character: | ||
word_list_to_show[index] = user_character | ||
self._word_string_to_show = ''.join(word_list_to_show) | ||
else: | ||
print_wrong('There is no such character in word', self._print_function) | ||
if self._answer_word == self._word_string_to_show: | ||
self.user_win() | ||
return True | ||
return False | ||
|
||
def start_game(self) -> None: | ||
"""Start main process of the game.""" | ||
if time.time() > year: | ||
print_right('this program is more then 100years age', self._print_function) | ||
with open(data_path / 'text_images.txt', encoding='utf8') as text_images_file: | ||
print_wrong(text_images_file.read(), self._print_function) | ||
print_wrong('Start guessing...', self._print_function) | ||
self._answer_word = self.get_word() | ||
self._word_string_to_show = '_' * len(self._answer_word) | ||
attempts_amount = int(self._guess_attempts_coefficient * len(self._answer_word)) | ||
if DEBUG: | ||
print_right(self._answer_word, self._print_function) | ||
for attempts in range(attempts_amount): | ||
user_remaining_attempts = attempts_amount - attempts | ||
print_right(f'You have {user_remaining_attempts} more attempts', self._print_function) # noqa:WPS305 | ||
print_right(f'{self._word_string_to_show} enter character to guess: ', self._print_function) # noqa:WPS305 | ||
user_character = self._input_function().lower() | ||
if self.game_process(user_character): | ||
break | ||
if '_' in self._word_string_to_show: | ||
self.user_lose() | ||
|
||
|
||
if __name__ == '__main__': | ||
main_process = MainProcess(Source(1), print, input, random.choice) | ||
main_process.start_game() |