From 0e9134e345930890d9110d0cbd05d39c106b12d2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 May 2021 21:45:53 -0700 Subject: [PATCH 01/77] Update last exercise --- freegames/snake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freegames/snake.py b/freegames/snake.py index 3b8c7b43..7c657894 100644 --- a/freegames/snake.py +++ b/freegames/snake.py @@ -5,7 +5,7 @@ 1. How do you make the snake faster or slower? 2. How can you make the snake go around the edges? 3. How would you move the food? -4. Change the snake to respond to arrow keys. +4. Change the snake to respond to mouse clicks. """ From 3e86c283bdc1f36bbbda29303d60eb39422d93b7 Mon Sep 17 00:00:00 2001 From: David Eynon Date: Tue, 1 Jun 2021 06:38:57 +0200 Subject: [PATCH 02/77] #55 - added initial mad libs game and initial test. (#65) Initial mad libs game and with testing. --- docs/index.rst | 1 + docs/madlibs.rst | 6 ++++++ freegames/madlibs.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/test_madlibs.py | 13 +++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 docs/madlibs.rst create mode 100644 freegames/madlibs.py create mode 100644 tests/test_madlibs.py diff --git a/docs/index.rst b/docs/index.rst index 41a30183..0af26523 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,3 +27,4 @@ memory pacman fidget + madlibs diff --git a/docs/madlibs.rst b/docs/madlibs.rst new file mode 100644 index 00000000..1f0c31f8 --- /dev/null +++ b/docs/madlibs.rst @@ -0,0 +1,6 @@ +Mad Libs +===== + +Create a funny story from a not-so funny story + +.. literalinclude:: ../freegames/madlibs.py \ No newline at end of file diff --git a/freegames/madlibs.py b/freegames/madlibs.py new file mode 100644 index 00000000..2066abe3 --- /dev/null +++ b/freegames/madlibs.py @@ -0,0 +1,41 @@ +""" Mad Libs: Funny Story Creation Game. + +Exercises: + +1. Can you create functionality to replace the given story? +2. Can you load the story and template from a file? +3. Can you add additional parts of speech? +4. Can you add flexibility in a loaded story template? +5. Can you adjust for grammar? (past tense, etc.) +6. Can you fix the bug that occurs if there is a period after the story? +7. At what point would you need to encapsulate into a function? +8. Can you use a function for this? +9. What problems can occur to the expected output if the program were to + crash mid-run (ex: due to power failure, unexpected computer freeze etc.) +10. How could you re-engineer this to allow for more robust retention of + the original story and/or the input collected? +""" + +story_template = "The [adj] [adj] [n] [v] over the [adj] [n]" +# The quick brown fox jumps over the lazy dog + +new_story = "" + +word_type_key = { + "[adj]": "adjective", + "[n]": "noun", + "[v]": "verb", + "[adv]": "adverb" +} + +"Replace word types with user provided words" +for char_block in story_template.split(' '): + if char_block in word_type_key: + new_word = input("Please enter a {}: ".format( + word_type_key[char_block])) + new_story += " {} ".format(new_word) + else: + new_story += " {} ".format(char_block) + +print("Your new story:") +print(new_story) diff --git a/tests/test_madlibs.py b/tests/test_madlibs.py new file mode 100644 index 00000000..56a52508 --- /dev/null +++ b/tests/test_madlibs.py @@ -0,0 +1,13 @@ +import random +import runpy +import unittest.mock as mock + + +def test_madlibs(): + random.seed(0) + mock_input = mock.Mock() + mock_input.side_effect = ["quick", "brown","lazy","brown","dog","car","jumps"] + mocks = {'print': lambda *args: None, 'input': mock_input} + + with mock.patch.multiple('builtins', **mocks): + runpy.run_module('freegames.madlibs') \ No newline at end of file From 1d82dd98bbbc9b56c5c6a72b1b89190a9d71e268 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 May 2021 21:59:30 -0700 Subject: [PATCH 03/77] Update madlibs game --- freegames/madlibs.py | 58 ++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/freegames/madlibs.py b/freegames/madlibs.py index 2066abe3..68ff46f1 100644 --- a/freegames/madlibs.py +++ b/freegames/madlibs.py @@ -1,41 +1,35 @@ -""" Mad Libs: Funny Story Creation Game. +"""Mad Libs: Funny Story Creation Game Exercises: -1. Can you create functionality to replace the given story? -2. Can you load the story and template from a file? -3. Can you add additional parts of speech? -4. Can you add flexibility in a loaded story template? -5. Can you adjust for grammar? (past tense, etc.) -6. Can you fix the bug that occurs if there is a period after the story? -7. At what point would you need to encapsulate into a function? -8. Can you use a function for this? -9. What problems can occur to the expected output if the program were to - crash mid-run (ex: due to power failure, unexpected computer freeze etc.) -10. How could you re-engineer this to allow for more robust retention of - the original story and/or the input collected? -""" - -story_template = "The [adj] [adj] [n] [v] over the [adj] [n]" -# The quick brown fox jumps over the lazy dog +1. How to replace the story? +2. How load the story and from a file? +3. How to add additional parts of speech? -new_story = "" +""" -word_type_key = { - "[adj]": "adjective", - "[n]": "noun", - "[v]": "verb", - "[adv]": "adverb" +# The quick brown fox jumps over the lazy dog. +template = 'The |1| |2| |3| |4| over the |5| |6|.' +parts = { + '1': 'adjective', + '2': 'adjective', + '3': 'noun', + '4': 'verb', + '5': 'adjective', + '6': 'noun', } -"Replace word types with user provided words" -for char_block in story_template.split(' '): - if char_block in word_type_key: - new_word = input("Please enter a {}: ".format( - word_type_key[char_block])) - new_story += " {} ".format(new_word) +chunks = [] + +for chunk in template.split('|'): + if chunk in parts: + description = parts[chunk] + prompt = 'Enter [{}]: '.format(description) + word = input(prompt) + chunks.append(word) else: - new_story += " {} ".format(char_block) + chunks.append(chunk) -print("Your new story:") -print(new_story) +print('=' * 80) +story = ''.join(chunks) +print(story) From f0319fc05d3efa0386accac2af747fbc2348c06c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 17:13:32 -0700 Subject: [PATCH 04/77] Add GitHub workflow action for integration --- .github/workflows/integration.yml | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/integration.yml diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000..1e9295c9 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,50 @@ +name: integration + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + + checks: + runs-on: ubuntu-latest + strategy: + max-parallel: 6 + matrix: + check: [bluecheck, doc8, docs, isortcheck, flake8, rstcheck] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Setup tox + run: | + pip install --upgrade pip + pip install tox + - name: Run checks with tox + run: | + tox -e ${{ matrix.check }} + + tests: + needs: checks + runs-on: ${{ matrix.os }} + strategy: + max-parallel: 4 + matrix: + os: [ubuntu-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup tox + run: | + pip install --upgrade pip + pip install tox + - name: Test with tox + run: tox -e py From e497e613d836cc0d4df0a953fd127b0ed8305ee9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 17:19:32 -0700 Subject: [PATCH 05/77] Update readme --- README.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 133bde98..3efddd88 100644 --- a/README.rst +++ b/README.rst @@ -72,16 +72,13 @@ Features - Used in hundreds of hours of classroom instruction - Fully Documented - 100% Test Coverage -- Developed on Python 3.7 -- Tested on CPython 2.7, 3.4, 3.5, 3.6, and 3.7 -- Tested on Windows, Mac OS X, Raspbian (Raspberry Pi), and Linux -- Tested using Travis CI and AppVeyor CI +- Developed on Python 3.9 +- Tested on CPython 3.6, 3.7, 3.8, 3.9 +- Tested on Linux, Mac OS X, and Windows +- Tested using GitHub Actions -.. image:: https://api.travis-ci.org/grantjenks/free-python-games.svg?branch=master - :target: http://www.grantjenks.com/docs/freegames/ - -.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/free-python-games?branch=master&svg=true - :target: http://www.grantjenks.com/docs/freegames/ +.. image:: https://github.com/grantjenks/free-python-games/workflows/integration/badge.svg + :target: http://www.grantjenks.com/docs/freegames/ Quickstart From c51e19255b45e5f00598216076130794f29d0053 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 17:43:57 -0700 Subject: [PATCH 06/77] Delete travis/appveyor, update requirements and tox --- .travis.yml | 15 ---------- appveyor.yml | 23 ---------------- requirements.txt | 5 +++- tox.ini | 71 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 40 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ca04f963..00000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -sudo: false -language: python -install: pip install tox -script: tox -matrix: - include: - - python: 3.4 - env: TOXENV=py34 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - env: TOXENV=py36 - - python: 3.7 - dist: xenial - env: TOXENV=py37 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index b8f2abfd..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -environment: - - matrix: - - - PYTHON: "C:\\Python34" - - PYTHON: "C:\\Python35" - - PYTHON: "C:\\Python36" - - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python34-x64" - - PYTHON: "C:\\Python35-x64" - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python37-x64" - -install: - - - "%PYTHON%\\python.exe -m pip install pytest" - -build: off - -test_script: - - - set PYTHONPATH=C:\projects\free-python-games;C:\projects\free-python-games\tests - - "%PYTHON%\\python.exe -m pytest -v tests" diff --git a/requirements.txt b/requirements.txt index eccc7a5b..d845935a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ +-e . +blue coverage doc8 -gj +flake8 +isort pylint pytest pytest-cov diff --git a/tox.ini b/tox.ini index 57a6a7c6..2ac9f335 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] -envlist=py34,py35,py36,py37 +envlist=bluecheck,doc8,docs,isortcheck,flake8,rstcheck,py36,py37,py38,py39 +skip_missing_interpreters=True [testenv] deps= @@ -9,3 +10,71 @@ deps= commands=python -m pytest -v --cov freegames --cov-report term-missing tests setenv= PYTHONPATH={toxinidir}:{toxinidir}/tests + +[testenv:blue] +commands=blue {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=blue + +[testenv:bluecheck] +commands=blue --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=blue + +[testenv:doc8] +deps=doc8 +commands=doc8 docs --ignore-path docs/_build + +[testenv:docs] +allowlist_externals=make +changedir=docs +commands=make html +deps= + sphinx + +[testenv:flake8] +commands=flake8 {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=flake8 + +[testenv:isort] +commands=isort {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=isort + +[testenv:isortcheck] +commands=isort --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=isort + +[testenv:rstcheck] +commands=rstcheck {toxinidir}/README.rst +deps=rstcheck + +[testenv:uploaddocs] +allowlist_externals=rsync +changedir=docs +commands= + rsync -azP --stats --delete _build/html/ \ + grantjenks.com:/srv/www/www.grantjenks.com/public/docs/freegames/ + +[isort] +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +use_parentheses = True +ensure_newline_before_comments = True +line_length = 79 + +[pytest] +addopts= + -n auto + --cov-branch + --cov-fail-under=100 + --cov-report=term-missing + --cov=freegames + --doctest-glob="*.rst" + --doctest-modules + --import-mode append +testpaths=docs freegames tests README.rst + +[doc8] +# ignore=D000 + +[flake8] +max-line-length=120 From a5169cb128995b0146ac678af14d1d534731c6c9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 17:52:05 -0700 Subject: [PATCH 07/77] First pass with blue --- freegames/__main__.py | 2 ++ freegames/ant.py | 3 +++ freegames/bounce.py | 4 ++++ freegames/cannon.py | 5 +++++ freegames/connect.py | 3 +++ freegames/crypto.py | 5 +++++ freegames/fidget.py | 4 ++++ freegames/flappy.py | 5 +++++ freegames/guess.py | 10 +++++----- freegames/life.py | 5 ++++- freegames/maze.py | 3 +++ freegames/memory.py | 6 ++++++ freegames/pacman.py | 6 ++++++ freegames/paint.py | 8 ++++++++ freegames/pong.py | 6 ++++++ freegames/simonsays.py | 6 ++++++ freegames/snake.py | 4 ++++ freegames/tictactoe.py | 7 +++++++ freegames/tiles.py | 5 +++++ freegames/tron.py | 3 +++ freegames/utils.py | 3 +++ setup.py | 2 ++ tests/mockturtle.py | 8 ++++++++ tests/test_bagels.py | 1 + tests/test_cannon.py | 4 +--- tests/test_crypto.py | 4 ++++ tests/test_flappy.py | 8 ++------ tests/test_madlibs.py | 12 ++++++++++-- tests/test_main.py | 3 +++ tests/test_tron.py | 11 +++-------- tests/test_utils.py | 2 ++ 31 files changed, 133 insertions(+), 25 deletions(-) diff --git a/freegames/__main__.py b/freegames/__main__.py index e92a46ce..b083f70a 100644 --- a/freegames/__main__.py +++ b/freegames/__main__.py @@ -8,6 +8,7 @@ directory = os.path.dirname(os.path.realpath(__file__)) contents = os.listdir(directory) + def game_file(name): "Return True if filename represents a game." return ( @@ -16,6 +17,7 @@ def game_file(name): and name != 'utils.py' ) + games = sorted(name[:-3] for name in contents if game_file(name)) parser = argparse.ArgumentParser( diff --git a/freegames/ant.py b/freegames/ant.py index ae388d0d..2d03bf7c 100644 --- a/freegames/ant.py +++ b/freegames/ant.py @@ -16,10 +16,12 @@ ant = vector(0, 0) aim = vector(2, 0) + def wrap(value): "Wrap value around -200 and 200." return value # TODO + def draw(): "Move ant and draw screen." ant.move(aim) @@ -36,6 +38,7 @@ def draw(): if running: ontimer(draw, 100) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/bounce.py b/freegames/bounce.py index be92e587..7c9fcabb 100644 --- a/freegames/bounce.py +++ b/freegames/bounce.py @@ -14,13 +14,16 @@ from turtle import * from freegames import vector + def value(): "Randomly generate value between (-5, -3) or (3, 5)." return (3 + random() * 2) * choice([1, -1]) + ball = vector(0, 0) aim = vector(value(), value()) + def draw(): "Move ball and draw game." ball.move(aim) @@ -40,6 +43,7 @@ def draw(): ontimer(draw, 50) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/cannon.py b/freegames/cannon.py index ed21b771..e90ab986 100644 --- a/freegames/cannon.py +++ b/freegames/cannon.py @@ -17,6 +17,7 @@ speed = vector(0, 0) targets = [] + def tap(x, y): "Respond to screen tap." if not inside(ball): @@ -25,10 +26,12 @@ def tap(x, y): speed.x = (x + 200) / 25 speed.y = (y + 200) / 25 + def inside(xy): "Return True if xy within screen." return -200 < xy.x < 200 and -200 < xy.y < 200 + def draw(): "Draw ball and targets." clear() @@ -43,6 +46,7 @@ def draw(): update() + def move(): "Move ball and targets." if randrange(40) == 0: @@ -72,6 +76,7 @@ def move(): ontimer(move, 50) + setup(420, 420, 370, 0) hideturtle() up() diff --git a/freegames/connect.py b/freegames/connect.py index 9da2bcd3..5fcf8e8d 100644 --- a/freegames/connect.py +++ b/freegames/connect.py @@ -16,6 +16,7 @@ turns = {'red': 'yellow', 'yellow': 'red'} state = {'player': 'yellow', 'rows': [0] * 8} + def grid(): "Draw Connect Four grid." bgcolor('light blue') @@ -31,6 +32,7 @@ def grid(): update() + def tap(x, y): "Draw red or yellow circle in tapped row." player = state['player'] @@ -50,6 +52,7 @@ def tap(x, y): rows[row] = count + 1 state['player'] = turns[player] + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/crypto.py b/freegames/crypto.py index 0d7311e8..f89e52ac 100644 --- a/freegames/crypto.py +++ b/freegames/crypto.py @@ -12,6 +12,7 @@ """ + def encrypt(message, key): "Encrypt message with key." result = '' @@ -48,14 +49,17 @@ def encrypt(message, key): return result + def decrypt(message, key): "Decrypt message with key." return encrypt(message, -key) + def decode(message): "Decode message without key." pass # TODO + def get_key(): "Get key from user." try: @@ -66,6 +70,7 @@ def get_key(): print('Invalid key. Using key: 0.') return 0 + print('Do you wish to encrypt, decrypt, or decode a message?') choice = input() diff --git a/freegames/fidget.py b/freegames/fidget.py index 6f649d53..e425acd5 100644 --- a/freegames/fidget.py +++ b/freegames/fidget.py @@ -13,6 +13,7 @@ state = {'turn': 0} + def spinner(): "Draw fidget spinner." clear() @@ -32,6 +33,7 @@ def spinner(): right(120) update() + def animate(): "Animate fidget spinner." if state['turn'] > 0: @@ -40,10 +42,12 @@ def animate(): spinner() ontimer(animate, 20) + def flick(): "Flick fidget spinner." state['turn'] += 10 + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/flappy.py b/freegames/flappy.py index cd62c643..1bfd341d 100644 --- a/freegames/flappy.py +++ b/freegames/flappy.py @@ -16,15 +16,18 @@ bird = vector(0, 0) balls = [] + def tap(x, y): "Move bird up in response to screen tap." up = vector(0, 30) bird.move(up) + def inside(point): "Return True if point on screen." return -200 < point.x < 200 and -200 < point.y < 200 + def draw(alive): "Draw screen objects." clear() @@ -42,6 +45,7 @@ def draw(alive): update() + def move(): "Update object positions." bird.y -= 5 @@ -69,6 +73,7 @@ def move(): draw(True) ontimer(move, 50) + setup(420, 420, 370, 0) hideturtle() up() diff --git a/freegames/guess.py b/freegames/guess.py index 2edc8149..d7e5738c 100644 --- a/freegames/guess.py +++ b/freegames/guess.py @@ -16,17 +16,17 @@ value = randint(start, end) print(value) -print("I'm thinking of a number between", start, "and", end) +print("I'm thinking of a number between", start, 'and', end) guess = None while guess != value: - text = input("Guess the number: ") + text = input('Guess the number: ') guess = int(text) if guess < value: - print("Higher.") + print('Higher.') elif guess > value: - print("Lower.") + print('Lower.') -print("Congratulations! You guessed the right answer:", value) +print('Congratulations! You guessed the right answer:', value) diff --git a/freegames/life.py b/freegames/life.py index cd4d2968..912595cc 100644 --- a/freegames/life.py +++ b/freegames/life.py @@ -18,6 +18,7 @@ cells = {} + def initialize(): "Randomly initialize the cells." for x in range(-200, 200, 10): @@ -28,6 +29,7 @@ def initialize(): for y in range(-50, 50, 10): cells[x, y] = choice([True, False]) + def step(): "Compute one step in the Game of Life." neighbors = {} @@ -37,7 +39,7 @@ def step(): count = -cells[x, y] for h in [-10, 0, 10]: for v in [-10, 0, 10]: - count += cells[x+h, y+v] + count += cells[x + h, y + v] neighbors[x, y] = count for cell, count in neighbors.items(): @@ -47,6 +49,7 @@ def step(): elif count == 3: cells[cell] = True + def draw(): "Draw all the squares." step() diff --git a/freegames/maze.py b/freegames/maze.py index 9fe41bf6..24a23bf0 100644 --- a/freegames/maze.py +++ b/freegames/maze.py @@ -12,6 +12,7 @@ from random import random from freegames import line + def draw(): "Draw maze." color('black') @@ -26,6 +27,7 @@ def draw(): update() + def tap(x, y): "Draw line and dot for screen tap." if abs(x) > 198 or abs(y) > 198: @@ -38,6 +40,7 @@ def tap(x, y): goto(x, y) dot(4) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/memory.py b/freegames/memory.py index 7ff61aa3..f5a0ee5c 100644 --- a/freegames/memory.py +++ b/freegames/memory.py @@ -19,6 +19,7 @@ state = {'mark': None} hide = [True] * 64 + def square(x, y): "Draw white square with black outline at (x, y)." up() @@ -31,14 +32,17 @@ def square(x, y): left(90) end_fill() + def index(x, y): "Convert (x, y) coordinates to tiles index." return int((x + 200) // 50 + ((y + 200) // 50) * 8) + def xy(count): "Convert tiles count to (x, y) coordinates." return (count % 8) * 50 - 200, (count // 8) * 50 - 200 + def tap(x, y): "Update mark and hidden tiles based on tap." spot = index(x, y) @@ -51,6 +55,7 @@ def tap(x, y): hide[mark] = False state['mark'] = None + def draw(): "Draw image and tiles." clear() @@ -75,6 +80,7 @@ def draw(): update() ontimer(draw, 100) + shuffle(tiles) setup(420, 420, 370, 0) addshape(car) diff --git a/freegames/pacman.py b/freegames/pacman.py index f249a27d..c2fb3ab1 100644 --- a/freegames/pacman.py +++ b/freegames/pacman.py @@ -61,6 +61,7 @@ def square(x, y): path.end_fill() + def offset(point): "Return offset of point in tiles." x = (floor(point.x, 20) + 200) / 20 @@ -68,6 +69,7 @@ def offset(point): index = int(x + y * 20) return index + def valid(point): "Return True if point is valid in tiles." index = offset(point) @@ -82,6 +84,7 @@ def valid(point): return point.x % 20 == 0 or point.y % 20 == 0 + def world(): "Draw world using path." bgcolor('black') @@ -100,6 +103,7 @@ def world(): path.goto(x + 10, y + 10) path.dot(2, 'white') + def move(): "Move pacman and all ghosts." writer.undo() @@ -149,12 +153,14 @@ def move(): ontimer(move, 100) + def change(x, y): "Change pacman aim if valid." if valid(pacman + vector(x, y)): aim.x = x aim.y = y + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/paint.py b/freegames/paint.py index b5fb2f2c..4235abcc 100644 --- a/freegames/paint.py +++ b/freegames/paint.py @@ -13,6 +13,7 @@ from turtle import * from freegames import vector + def line(start, end): "Draw line from start to end." up() @@ -20,6 +21,7 @@ def line(start, end): down() goto(end.x, end.y) + def square(start, end): "Draw square from start to end." up() @@ -33,18 +35,22 @@ def square(start, end): end_fill() + def circle(start, end): "Draw circle from start to end." pass # TODO + def rectangle(start, end): "Draw rectangle from start to end." pass # TODO + def triangle(start, end): "Draw triangle from start to end." pass # TODO + def tap(x, y): "Store starting point or draw shape." start = state['start'] @@ -57,10 +63,12 @@ def tap(x, y): shape(start, end) state['start'] = None + def store(key, value): "Store value in state at key." state[key] = value + state = {'start': None, 'shape': line} setup(420, 420, 370, 0) onscreenclick(tap) diff --git a/freegames/pong.py b/freegames/pong.py index 0bdd9685..4e6ec456 100644 --- a/freegames/pong.py +++ b/freegames/pong.py @@ -16,18 +16,22 @@ from turtle import * from freegames import vector + def value(): "Randomly generate value between (-5, -3) or (3, 5)." return (3 + random() * 2) * choice([1, -1]) + ball = vector(0, 0) aim = vector(value(), value()) state = {1: 0, 2: 0} + def move(player, change): "Move player position by change." state[player] += change + def rectangle(x, y, width, height): "Draw rectangle at (x, y) with given width and height." up() @@ -41,6 +45,7 @@ def rectangle(x, y, width, height): left(90) end_fill() + def draw(): "Draw game and move pong ball." clear() @@ -79,6 +84,7 @@ def draw(): ontimer(draw, 50) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/simonsays.py b/freegames/simonsays.py index e922a4ce..312e74a1 100644 --- a/freegames/simonsays.py +++ b/freegames/simonsays.py @@ -21,6 +21,7 @@ vector(-200, -200): ('yellow', 'khaki'), } + def grid(): "Draw grid of tiles." square(0, 0, 200, 'dark red') @@ -29,6 +30,7 @@ def grid(): square(-200, -200, 200, 'khaki') update() + def flash(tile): "Flash tile in grid." glow, dark = tiles[tile] @@ -39,6 +41,7 @@ def flash(tile): update() sleep(0.5) + def grow(): "Grow pattern and flash tiles." tile = choice(list(tiles)) @@ -50,6 +53,7 @@ def grow(): print('Pattern length:', len(pattern)) guesses.clear() + def tap(x, y): "Respond to screen tap." onscreenclick(None) @@ -69,11 +73,13 @@ def tap(x, y): onscreenclick(tap) + def start(x, y): "Start game." grow() onscreenclick(tap) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/snake.py b/freegames/snake.py index 7c657894..9372c38e 100644 --- a/freegames/snake.py +++ b/freegames/snake.py @@ -17,15 +17,18 @@ snake = [vector(10, 0)] aim = vector(0, -10) + def change(x, y): "Change snake direction." aim.x = x aim.y = y + def inside(head): "Return True if head inside boundaries." return -200 < head.x < 190 and -200 < head.y < 190 + def move(): "Move snake forward one segment." head = snake[-1].copy() @@ -54,6 +57,7 @@ def move(): update() ontimer(move, 100) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/tictactoe.py b/freegames/tictactoe.py index 8bb9dc2a..8857d59b 100644 --- a/freegames/tictactoe.py +++ b/freegames/tictactoe.py @@ -12,6 +12,7 @@ from turtle import * from freegames import line + def grid(): "Draw tic-tac-toe grid." line(-67, 200, -67, -200) @@ -19,11 +20,13 @@ def grid(): line(-200, -67, 200, -67) line(-200, 67, 200, 67) + def drawx(x, y): "Draw X player." line(x, y, x + 133, y + 133) line(x, y + 133, x + 133, y) + def drawo(x, y): "Draw O player." up() @@ -31,13 +34,16 @@ def drawo(x, y): down() circle(62) + def floor(value): "Round value down to grid with square size 133." return ((value + 200) // 133) * 133 - 200 + state = {'player': 0} players = [drawx, drawo] + def tap(x, y): "Draw X or O in tapped square." x = floor(x) @@ -48,6 +54,7 @@ def tap(x, y): update() state['player'] = not player + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/tiles.py b/freegames/tiles.py index f7671a82..05083d3d 100644 --- a/freegames/tiles.py +++ b/freegames/tiles.py @@ -21,6 +21,7 @@ vector(0, -100), ] + def load(): "Load tiles and scramble." count = 1 @@ -43,6 +44,7 @@ def load(): tiles[mark] = number mark = spot + def square(mark, number): "Draw white square with black outline and number." up() @@ -63,6 +65,7 @@ def square(mark, number): write(number, font=('Arial', 60, 'normal')) + def tap(x, y): "Swap tile and empty square." x = floor(x, 100) @@ -79,12 +82,14 @@ def tap(x, y): tiles[mark] = None square(mark, None) + def draw(): "Draw all tiles." for mark in tiles: square(mark, tiles[mark]) update() + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/tron.py b/freegames/tron.py index cefcc57c..3f1b69e6 100644 --- a/freegames/tron.py +++ b/freegames/tron.py @@ -20,10 +20,12 @@ p2aim = vector(-4, 0) p2body = set() + def inside(head): "Return True if head inside screen." return -200 < head.x < 200 and -200 < head.y < 200 + def draw(): "Advance players and draw game." p1xy.move(p1aim) @@ -48,6 +50,7 @@ def draw(): update() ontimer(draw, 50) + setup(420, 420, 370, 0) hideturtle() tracer(False) diff --git a/freegames/utils.py b/freegames/utils.py index 4665a7d4..103d9899 100644 --- a/freegames/utils.py +++ b/freegames/utils.py @@ -48,6 +48,7 @@ def path(filename): def line(a, b, x, y): "Draw line from `(a, b)` to `(x, y)`." import turtle + turtle.up() turtle.goto(a, b) turtle.down() @@ -61,6 +62,7 @@ def square(x, y, size, name): """ import turtle + turtle.up() turtle.goto(x, y) turtle.down() @@ -88,6 +90,7 @@ class vector(collections.Sequence): vector(-2.0, 1.0) """ + # pylint: disable=invalid-name PRECISION = 6 diff --git a/setup.py b/setup.py index addb36b4..645b4d06 100644 --- a/setup.py +++ b/setup.py @@ -12,8 +12,10 @@ def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True + def run_tests(self): import tox + errno = tox.cmdline(self.test_args) sys.exit(errno) diff --git a/tests/mockturtle.py b/tests/mockturtle.py index 5e7d0572..9bb2b019 100644 --- a/tests/mockturtle.py +++ b/tests/mockturtle.py @@ -5,6 +5,7 @@ state = {} events = [] + class Turtle: def __init__(self, visible=True): pass @@ -78,6 +79,7 @@ def update(self): def undo(self): pass + _turtle = Turtle() goto = _turtle.goto up = _turtle.up @@ -103,22 +105,28 @@ def undo(self): update = _turtle.update undo = _turtle.undo + def setup(width, height, x, y): pass + def listen(): pass + def onkey(function, key): state['key ' + key] = function + def ontimer(function, delay): state['timer'] = function state['delay'] = delay + def onscreenclick(function): state['click'] = function + def done(): for event in events: name = event[0] diff --git a/tests/test_bagels.py b/tests/test_bagels.py index 5b24b8b6..9c631592 100644 --- a/tests/test_bagels.py +++ b/tests/test_bagels.py @@ -12,6 +12,7 @@ def test_bagels_pass(): with mock.patch.multiple('builtins', **mocks): runpy.run_module('freegames.bagels') + def test_bagels_fail(): random.seed(0) mock_input = mock.Mock() diff --git a/tests/test_cannon.py b/tests/test_cannon.py index cdcdc615..082c4235 100644 --- a/tests/test_cannon.py +++ b/tests/test_cannon.py @@ -10,8 +10,6 @@ def test_cannon(): random.seed(0) mockturtle.events[:] = ( - [('timer',)] * 300 - + [('click', 0, 0)] - + [('timer', True)] * 3000 + [('timer',)] * 300 + [('click', 0, 0)] + [('timer', True)] * 3000 ) runpy.run_module('freegames.cannon') diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 16b35ac2..2f3a88f7 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -12,6 +12,7 @@ def test_crypto_encrypt(): with mock.patch.multiple('builtins', **mocks): runpy.run_module('freegames.crypto') + def test_crypto_encrypt_bad_key(): random.seed(0) mock_input = mock.Mock() @@ -21,6 +22,7 @@ def test_crypto_encrypt_bad_key(): with mock.patch.multiple('builtins', **mocks): runpy.run_module('freegames.crypto') + def test_crypto_bad_command(): random.seed(0) mock_input = mock.Mock() @@ -30,6 +32,7 @@ def test_crypto_bad_command(): with mock.patch.multiple('builtins', **mocks): runpy.run_module('freegames.crypto') + def test_crypto_decrypt(): random.seed(0) mock_input = mock.Mock() @@ -39,6 +42,7 @@ def test_crypto_decrypt(): with mock.patch.multiple('builtins', **mocks): runpy.run_module('freegames.crypto') + def test_crypto_decode(): random.seed(0) mock_input = mock.Mock() diff --git a/tests/test_flappy.py b/tests/test_flappy.py index 608da5cf..5e6b0173 100644 --- a/tests/test_flappy.py +++ b/tests/test_flappy.py @@ -9,15 +9,11 @@ def test_flappy_outside(): random.seed(0) - mockturtle.events[:] = ( - [('timer', True)] * 300 - ) + mockturtle.events[:] = [('timer', True)] * 300 runpy.run_module('freegames.flappy') def test_flappy_collision(): random.seed(0) - mockturtle.events[:] = ( - ([('timer', True)] * 6 + [('click', 0, 0)]) * 100 - ) + mockturtle.events[:] = ([('timer', True)] * 6 + [('click', 0, 0)]) * 100 runpy.run_module('freegames.flappy') diff --git a/tests/test_madlibs.py b/tests/test_madlibs.py index 56a52508..7e7c4708 100644 --- a/tests/test_madlibs.py +++ b/tests/test_madlibs.py @@ -6,8 +6,16 @@ def test_madlibs(): random.seed(0) mock_input = mock.Mock() - mock_input.side_effect = ["quick", "brown","lazy","brown","dog","car","jumps"] + mock_input.side_effect = [ + 'quick', + 'brown', + 'lazy', + 'brown', + 'dog', + 'car', + 'jumps', + ] mocks = {'print': lambda *args: None, 'input': mock_input} with mock.patch.multiple('builtins', **mocks): - runpy.run_module('freegames.madlibs') \ No newline at end of file + runpy.run_module('freegames.madlibs') diff --git a/tests/test_main.py b/tests/test_main.py index d82eb6d3..f85436cd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -11,6 +11,7 @@ def test_main_list(): with mock.patch('sys.argv', ['__main__.py', 'list']): runpy.run_module('freegames.__main__') + def test_main_copy(): random.seed(0) mock_open = mock.Mock() @@ -20,6 +21,7 @@ def test_main_copy(): with mock.patch('builtins.open', mock_open): runpy.run_module('freegames.__main__') + def test_main_copy_error(): cwd = os.getcwd() path = os.path.join(cwd, 'guess.py') @@ -33,6 +35,7 @@ def test_main_copy_error(): finally: os.remove('guess.py') + def test_main_show(): random.seed(0) diff --git a/tests/test_tron.py b/tests/test_tron.py index 1e9d7362..12753ace 100644 --- a/tests/test_tron.py +++ b/tests/test_tron.py @@ -9,16 +9,11 @@ def test_tron_1(): random.seed(0) - mockturtle.events[:] = ( - [('key a',), ('key a',)] - + [('timer', True)] * 600 - ) + mockturtle.events[:] = [('key a',), ('key a',)] + [('timer', True)] * 600 runpy.run_module('freegames.tron') + def test_tron_2(): random.seed(0) - mockturtle.events[:] = ( - [('key j',), ('key j',)] - + [('timer', True)] * 600 - ) + mockturtle.events[:] = [('key j',), ('key j',)] + [('timer', True)] * 600 runpy.run_module('freegames.tron') diff --git a/tests/test_utils.py b/tests/test_utils.py index 1b0ef11a..435f4638 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,7 @@ import freegames.utils as utils + def test_change_after_hash(): v = utils.vector(0, 0) hash(v) @@ -20,6 +21,7 @@ def test_change_after_hash(): with raises(ValueError): v.rotate(90) + def test_not_implemented_paths(): v = utils.vector(0, 0) assert not (v == 0) From 23010c9ecff6284a11891030c733c75212f05eb8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 18:16:36 -0700 Subject: [PATCH 08/77] More blue fixes --- freegames/pacman.py | 3 +++ tests/test_fidget.py | 7 +++---- tests/test_pacman.py | 7 +++---- tests/test_pong.py | 15 +++++++-------- tests/test_snake.py | 12 ++++++++---- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/freegames/pacman.py b/freegames/pacman.py index c2fb3ab1..58e849ea 100644 --- a/freegames/pacman.py +++ b/freegames/pacman.py @@ -25,6 +25,7 @@ [vector(100, 160), vector(0, -5)], [vector(100, -160), vector(-5, 0)], ] +# fmt: off tiles = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, @@ -47,6 +48,8 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ] +# fmt: on + def square(x, y): "Draw square using path at (x, y)." diff --git a/tests/test_fidget.py b/tests/test_fidget.py index 5c48655f..62101c0d 100644 --- a/tests/test_fidget.py +++ b/tests/test_fidget.py @@ -9,8 +9,7 @@ def test_fidget(): random.seed(0) - mockturtle.events[:] = ( - [('timer',), ('key space',)] * 30 - + [('timer',)] * 600 - ) + mockturtle.events.clear() + mockturtle.events += [('timer',), ('key space',)] * 30 + mockturtle.events += [('timer',)] * 600 runpy.run_module('freegames.fidget') diff --git a/tests/test_pacman.py b/tests/test_pacman.py index 2990fe1a..b9f8a45a 100644 --- a/tests/test_pacman.py +++ b/tests/test_pacman.py @@ -9,8 +9,7 @@ def test_pacman(): random.seed(0) - mockturtle.events[:] = ( - [('timer', True), ('key Up',)] * 600 - + [('timer', True)] * 3000 - ) + mockturtle.events.clear() + mockturtle.events += [('timer', True), ('key Up',)] * 600 + mockturtle.events += [('timer', True)] * 3000 runpy.run_module('freegames.pacman') diff --git a/tests/test_pong.py b/tests/test_pong.py index e3a0746c..b2809eb3 100644 --- a/tests/test_pong.py +++ b/tests/test_pong.py @@ -9,16 +9,15 @@ def test_pong_1(): random.seed(0) - mockturtle.events[:] = ( - [('timer',), ('key s',)] * 8 - + [('timer', True)] * 600 - ) + mockturtle.events.clear() + mockturtle.events += [('timer',), ('key s',)] * 8 + mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.pong') + def test_pong_2(): random.seed(1) - mockturtle.events[:] = ( - [('timer',), ('key k',)] * 12 - + [('timer', True)] * 600 - ) + mockturtle.events.clear() + mockturtle.events += [('timer',), ('key k',)] * 12 + mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.pong') diff --git a/tests/test_snake.py b/tests/test_snake.py index c6f25a14..d08a954a 100644 --- a/tests/test_snake.py +++ b/tests/test_snake.py @@ -9,8 +9,12 @@ def test_snake(): random.seed(0) - mockturtle.events[:] = ( - [('timer',), ('key Left',), ('timer',), ('key Up',)] - + [('timer', True)] * 300 - ) + mockturtle.events.clear() + mockturtle.events += [ + ('timer',), + ('key Left',), + ('timer',), + ('key Up',), + ] + mockturtle.events += [('timer', True)] * 300 runpy.run_module('freegames.snake') From f585ae42e9cd2abea2f4a68077c08218b1b1fb16 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 18:19:16 -0700 Subject: [PATCH 09/77] Fixes for docs --- docs/madlibs.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/madlibs.rst b/docs/madlibs.rst index 1f0c31f8..359ffbc5 100644 --- a/docs/madlibs.rst +++ b/docs/madlibs.rst @@ -1,6 +1,6 @@ Mad Libs -===== +======== Create a funny story from a not-so funny story -.. literalinclude:: ../freegames/madlibs.py \ No newline at end of file +.. literalinclude:: ../freegames/madlibs.py From 7d2996c9d525d8ae77079439655f5b08645409f1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 14 Jun 2021 18:20:22 -0700 Subject: [PATCH 10/77] isort fixes --- freegames/ant.py | 1 + freegames/bounce.py | 1 + freegames/cannon.py | 1 + freegames/connect.py | 1 + freegames/flappy.py | 1 + freegames/life.py | 1 + freegames/maze.py | 3 ++- freegames/memory.py | 1 + freegames/minesweeper.py | 1 + freegames/pacman.py | 1 + freegames/paint.py | 1 + freegames/pong.py | 1 + freegames/simonsays.py | 1 + freegames/snake.py | 3 ++- freegames/tictactoe.py | 1 + freegames/tiles.py | 1 + freegames/tron.py | 1 + setup.py | 2 +- tests/test_bounce.py | 2 +- 19 files changed, 21 insertions(+), 4 deletions(-) diff --git a/freegames/ant.py b/freegames/ant.py index 2d03bf7c..030a8a16 100644 --- a/freegames/ant.py +++ b/freegames/ant.py @@ -11,6 +11,7 @@ from random import * from turtle import * + from freegames import vector ant = vector(0, 0) diff --git a/freegames/bounce.py b/freegames/bounce.py index 7c9fcabb..64910ca8 100644 --- a/freegames/bounce.py +++ b/freegames/bounce.py @@ -12,6 +12,7 @@ from random import * from turtle import * + from freegames import vector diff --git a/freegames/cannon.py b/freegames/cannon.py index e90ab986..b42e8410 100644 --- a/freegames/cannon.py +++ b/freegames/cannon.py @@ -11,6 +11,7 @@ from random import randrange from turtle import * + from freegames import vector ball = vector(-200, -200) diff --git a/freegames/connect.py b/freegames/connect.py index 5fcf8e8d..a5d103df 100644 --- a/freegames/connect.py +++ b/freegames/connect.py @@ -11,6 +11,7 @@ """ from turtle import * + from freegames import line turns = {'red': 'yellow', 'yellow': 'red'} diff --git a/freegames/flappy.py b/freegames/flappy.py index 1bfd341d..4aec8aaf 100644 --- a/freegames/flappy.py +++ b/freegames/flappy.py @@ -11,6 +11,7 @@ from random import * from turtle import * + from freegames import vector bird = vector(0, 0) diff --git a/freegames/life.py b/freegames/life.py index 912595cc..8c0bb3aa 100644 --- a/freegames/life.py +++ b/freegames/life.py @@ -14,6 +14,7 @@ from random import choice from turtle import * + from freegames import square cells = {} diff --git a/freegames/maze.py b/freegames/maze.py index 24a23bf0..f90b6b9f 100644 --- a/freegames/maze.py +++ b/freegames/maze.py @@ -8,8 +8,9 @@ """ -from turtle import * from random import random +from turtle import * + from freegames import line diff --git a/freegames/memory.py b/freegames/memory.py index f5a0ee5c..80bc62e8 100644 --- a/freegames/memory.py +++ b/freegames/memory.py @@ -12,6 +12,7 @@ from random import * from turtle import * + from freegames import path car = path('car.gif') diff --git a/freegames/minesweeper.py b/freegames/minesweeper.py index 87b772ca..44efa68e 100644 --- a/freegames/minesweeper.py +++ b/freegames/minesweeper.py @@ -10,6 +10,7 @@ from random import randrange, seed from turtle import * + from freegames import floor, square seed(0) diff --git a/freegames/pacman.py b/freegames/pacman.py index 58e849ea..2bc70d2f 100644 --- a/freegames/pacman.py +++ b/freegames/pacman.py @@ -12,6 +12,7 @@ from random import choice from turtle import * + from freegames import floor, vector state = {'score': 0} diff --git a/freegames/paint.py b/freegames/paint.py index 4235abcc..0a7b167e 100644 --- a/freegames/paint.py +++ b/freegames/paint.py @@ -11,6 +11,7 @@ """ from turtle import * + from freegames import vector diff --git a/freegames/pong.py b/freegames/pong.py index 4e6ec456..faf0efd0 100644 --- a/freegames/pong.py +++ b/freegames/pong.py @@ -14,6 +14,7 @@ from random import choice, random from turtle import * + from freegames import vector diff --git a/freegames/simonsays.py b/freegames/simonsays.py index 312e74a1..c1aa4733 100644 --- a/freegames/simonsays.py +++ b/freegames/simonsays.py @@ -10,6 +10,7 @@ from random import choice from time import sleep from turtle import * + from freegames import floor, square, vector pattern = [] diff --git a/freegames/snake.py b/freegames/snake.py index 9372c38e..8bf5a52f 100644 --- a/freegames/snake.py +++ b/freegames/snake.py @@ -9,8 +9,9 @@ """ -from turtle import * from random import randrange +from turtle import * + from freegames import square, vector food = vector(0, 0) diff --git a/freegames/tictactoe.py b/freegames/tictactoe.py index 8857d59b..b7a4a2d2 100644 --- a/freegames/tictactoe.py +++ b/freegames/tictactoe.py @@ -10,6 +10,7 @@ """ from turtle import * + from freegames import line diff --git a/freegames/tiles.py b/freegames/tiles.py index 05083d3d..11aef296 100644 --- a/freegames/tiles.py +++ b/freegames/tiles.py @@ -11,6 +11,7 @@ from random import * from turtle import * + from freegames import floor, vector tiles = {} diff --git a/freegames/tron.py b/freegames/tron.py index 3f1b69e6..5b4cbb58 100644 --- a/freegames/tron.py +++ b/freegames/tron.py @@ -10,6 +10,7 @@ """ from turtle import * + from freegames import square, vector p1xy = vector(-100, 0) diff --git a/setup.py b/setup.py index 645b4d06..c4f12aaa 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ import io -import setuptools as st import sys +import setuptools as st from setuptools.command.test import test as TestCommand import freegames diff --git a/tests/test_bounce.py b/tests/test_bounce.py index d397163c..5cad045a 100644 --- a/tests/test_bounce.py +++ b/tests/test_bounce.py @@ -1,7 +1,7 @@ import random import runpy -import turtle import sys +import turtle import mockturtle From e8fbdbc021f053e81b583c80ad890512dae91813 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:25:50 -0400 Subject: [PATCH 11/77] Add tests for minesweeper --- tests/test_minesweeper.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/test_minesweeper.py diff --git a/tests/test_minesweeper.py b/tests/test_minesweeper.py new file mode 100644 index 00000000..2c4e5e53 --- /dev/null +++ b/tests/test_minesweeper.py @@ -0,0 +1,23 @@ +import random +import runpy +import sys +import unittest.mock as mock + +import mockturtle + +sys.modules['turtle'] = sys.modules['mockturtle'] + + +def test_minesweeper(): + random.seed(0) + mockturtle.events[:] = ( + ('click', 175, -175), + ('click', -75, -75), + ('click', 125, 125), + ) + + try: + with mock.patch('time.sleep', lambda delay: None): + runpy.run_module('freegames.minesweeper') + except SystemExit: + pass From 89dfec8361cc15d74aab7a3c31dcd1d56aa6503c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:26:01 -0400 Subject: [PATCH 12/77] Simplify ant game --- freegames/ant.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freegames/ant.py b/freegames/ant.py index 030a8a16..d83c7168 100644 --- a/freegames/ant.py +++ b/freegames/ant.py @@ -36,14 +36,12 @@ def draw(): goto(ant.x, ant.y) dot(4) - if running: - ontimer(draw, 100) + ontimer(draw, 100) setup(420, 420, 370, 0) hideturtle() tracer(False) up() -running = True draw() done() From ab9ef3ff049b4eef570c8db21d950417c20f9391 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:26:24 -0400 Subject: [PATCH 13/77] Simplify crypto branching --- freegames/crypto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freegames/crypto.py b/freegames/crypto.py index f89e52ac..92e0805b 100644 --- a/freegames/crypto.py +++ b/freegames/crypto.py @@ -30,7 +30,8 @@ def encrypt(message, key): if letter.isupper(): base = ord('A') - elif letter.islower(): + else: + assert letter.islower() base = ord('a') # The encryption equation: From 83dc3d209ed4fd1895f853cf82e3145f96bf829c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:26:36 -0400 Subject: [PATCH 14/77] Collections abc deprecation warning --- freegames/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freegames/utils.py b/freegames/utils.py index 103d9899..edf96cbe 100644 --- a/freegames/utils.py +++ b/freegames/utils.py @@ -3,7 +3,7 @@ """ # pylint: disable=no-member -import collections +import collections.abc import math import os @@ -76,7 +76,7 @@ def square(x, y, size, name): turtle.end_fill() -class vector(collections.Sequence): +class vector(collections.abc.Sequence): """Two-dimensional vector. Vectors can be modified in-place. From b4e4a5a0ffecf3cd2d90ad48232eca9e4a81e8ff Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:26:50 -0400 Subject: [PATCH 15/77] Test colors --- tests/test_paint.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_paint.py b/tests/test_paint.py index 4da7ff7d..492d648f 100644 --- a/tests/test_paint.py +++ b/tests/test_paint.py @@ -10,18 +10,23 @@ def test_paint(): random.seed(0) mockturtle.events[:] = [ + ('key K',), ('key l',), ('click', 0, 0), ('click', 10, 10), + ('key W',), ('key s',), ('click', 20, 20), ('click', 30, 30), + ('key G',), ('key c',), ('click', 30, 30), ('click', 40, 40), + ('key B',), ('key r',), ('click', 30, 30), ('click', 40, 40), + ('key R',), ('key t',), ('click', 30, 30), ('click', 40, 40), From b24afe478cc0f4937cdedd038d875dddf168b2f8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:26:57 -0400 Subject: [PATCH 16/77] Test back-and-forth --- tests/test_pong.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_pong.py b/tests/test_pong.py index b2809eb3..c3500585 100644 --- a/tests/test_pong.py +++ b/tests/test_pong.py @@ -10,7 +10,8 @@ def test_pong_1(): random.seed(0) mockturtle.events.clear() - mockturtle.events += [('timer',), ('key s',)] * 8 + mockturtle.events += [('timer',), ('key s',)] * 12 + mockturtle.events += [('timer',), ('key w',)] * 4 mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.pong') @@ -18,6 +19,7 @@ def test_pong_1(): def test_pong_2(): random.seed(1) mockturtle.events.clear() - mockturtle.events += [('timer',), ('key k',)] * 12 + mockturtle.events += [('timer',), ('key k',)] * 18 + mockturtle.events += [('timer',), ('key i',)] * 6 mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.pong') From a5805a81a6e2c88807b1006e623276139c07ae7d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:27:08 -0400 Subject: [PATCH 17/77] Add test for going in a circle --- tests/test_snake.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_snake.py b/tests/test_snake.py index d08a954a..2b12276e 100644 --- a/tests/test_snake.py +++ b/tests/test_snake.py @@ -15,6 +15,14 @@ def test_snake(): ('key Left',), ('timer',), ('key Up',), + ('timer',), + ('key Right',), + ('timer',), + ('key Down',), + ('timer',), + ('key Left',), + ('timer',), + ('key Up',), ] mockturtle.events += [('timer', True)] * 300 runpy.run_module('freegames.snake') From f84515e73becf9dc1dfcd97b51312cfbf2197a25 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:27:26 -0400 Subject: [PATCH 18/77] Go back-and-forth --- tests/test_tron.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_tron.py b/tests/test_tron.py index 12753ace..0d871d16 100644 --- a/tests/test_tron.py +++ b/tests/test_tron.py @@ -9,11 +9,15 @@ def test_tron_1(): random.seed(0) - mockturtle.events[:] = [('key a',), ('key a',)] + [('timer', True)] * 600 + mockturtle.events.clear() + mockturtle.events += [('key a',), ('key d',), ('key a',)] + mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.tron') def test_tron_2(): random.seed(0) - mockturtle.events[:] = [('key j',), ('key j',)] + [('timer', True)] * 600 + mockturtle.events.clear() + mockturtle.events += [('key j',), ('key l',), ('key j',)] + mockturtle.events += [('timer', True)] * 600 runpy.run_module('freegames.tron') From cbe0e8a605f1a1012c8aff86986630f1e416bb02 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:27:58 -0400 Subject: [PATCH 19/77] Remove flake8 and simplify switches --- tox.ini | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 2ac9f335..76758969 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,isortcheck,flake8,rstcheck,py36,py37,py38,py39 +envlist=bluecheck,doc8,docs,isortcheck,rstcheck,py36,py37,py38,py39 skip_missing_interpreters=True [testenv] @@ -7,7 +7,7 @@ deps= coverage pytest pytest-cov -commands=python -m pytest -v --cov freegames --cov-report term-missing tests +commands=pytest tests setenv= PYTHONPATH={toxinidir}:{toxinidir}/tests @@ -30,10 +30,6 @@ commands=make html deps= sphinx -[testenv:flake8] -commands=flake8 {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests -deps=flake8 - [testenv:isort] commands=isort {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests deps=isort @@ -43,7 +39,7 @@ commands=isort --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/te deps=isort [testenv:rstcheck] -commands=rstcheck {toxinidir}/README.rst +commands=rstcheck --report warning {toxinidir}/README.rst deps=rstcheck [testenv:uploaddocs] @@ -63,8 +59,6 @@ line_length = 79 [pytest] addopts= - -n auto - --cov-branch --cov-fail-under=100 --cov-report=term-missing --cov=freegames From 56183bc0d60e268ed791cb2e2cb98c100b6498c9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:34:50 -0400 Subject: [PATCH 20/77] Remove flake8 and test on Windows and MacOS --- .github/workflows/integration.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 1e9295c9..ab2c0aa2 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,9 +13,9 @@ jobs: checks: runs-on: ubuntu-latest strategy: - max-parallel: 6 + max-parallel: 5 matrix: - check: [bluecheck, doc8, docs, isortcheck, flake8, rstcheck] + check: [bluecheck, doc8, docs, isortcheck, rstcheck] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 @@ -35,7 +35,7 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macos-latest] python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 From 615eb562627448953f709b3b13f36e523d44fdf1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:40:33 -0400 Subject: [PATCH 21/77] Update project urls and classifiers --- setup.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index c4f12aaa..ed4ee8c6 100644 --- a/setup.py +++ b/setup.py @@ -29,15 +29,22 @@ def run_tests(self): version=freegames.__version__, description='Free Games', long_description=readme, + long_description_content_type='text/x-rst', author='Grant Jenks', author_email='contact@grantjenks.com', url='http://www.grantjenks.com/docs/freegames/', + license='Apache 2.0', packages=['freegames'], include_package_data=True, tests_require=['tox'], cmdclass={'test': Tox}, - license='Apache 2.0', install_requires=[], + project_urls={ + 'Documentation': 'http://www.grantjenks.com/docs/freegames/', + 'Funding': 'http://gum.co/freegames', + 'Source': 'https://github.com/grantjenks/free-python-games', + 'Tracker': 'https://github.com/grantjenks/free-python-games/issues', + }, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Education', @@ -46,10 +53,10 @@ def run_tests(self): 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Games/Entertainment', 'Topic :: Games/Entertainment :: Arcade', From 65804834ed01b05dc78312bcf57db2a01d19648f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:50:05 -0400 Subject: [PATCH 22/77] Windows %$#@! --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ab2c0aa2..88d91c48 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,7 +35,7 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest] python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 From 9fd4af2b3af669da18cbea457efc5de21279764a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:54:51 -0400 Subject: [PATCH 23/77] Update copyright to 2021 --- README.rst | 2 +- freegames/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3efddd88..cef360bf 100644 --- a/README.rst +++ b/README.rst @@ -334,7 +334,7 @@ References Free Python Games License ------------------------- -Copyright 2017-2020 Grant Jenks +Copyright 2017-2021 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/freegames/__init__.py b/freegames/__init__.py index 8cc4db82..76449978 100644 --- a/freegames/__init__.py +++ b/freegames/__init__.py @@ -67,4 +67,4 @@ __build__ = 0x020302 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = '2017-2020, Grant Jenks' +__copyright__ = '2017-2021, Grant Jenks' From db010f8e015e99584d254d5dd34ec294f2b5db58 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:57:16 -0400 Subject: [PATCH 24/77] Add replit file for support --- .replit | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .replit diff --git a/.replit b/.replit new file mode 100644 index 00000000..fc2fa361 --- /dev/null +++ b/.replit @@ -0,0 +1,2 @@ +language = "python3" +run = "" From 3072487e1387d51f31deb4a2ec9f8d48b5b3f766 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 14:59:44 -0400 Subject: [PATCH 25/77] Remove replit, don't want to grant GitHub access --- .replit | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .replit diff --git a/.replit b/.replit deleted file mode 100644 index fc2fa361..00000000 --- a/.replit +++ /dev/null @@ -1,2 +0,0 @@ -language = "python3" -run = "" From 8c67c5499908aed12e057ab44811400b1fd7a8dd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 15 Jun 2021 15:09:21 -0400 Subject: [PATCH 26/77] Align isort and blue line lengths --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 76758969..91a15c5b 100644 --- a/tox.ini +++ b/tox.ini @@ -55,7 +55,7 @@ include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True ensure_newline_before_comments = True -line_length = 79 +line_length = 80 [pytest] addopts= From b1f940466698a64e1fd94e708e38e44636c1428c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 29 Nov 2021 22:17:06 -0800 Subject: [PATCH 27/77] Update to support Python 3.10 (#75) * Update to support Python 3.10 * Update copyright * Add flake8 testing * Remove unused import * Update copyright for 2022 * I blue it! --- .github/workflows/integration.yml | 10 +++++----- LICENSE | 2 +- README.rst | 6 +++--- freegames/__init__.py | 1 - freegames/__main__.py | 5 ++--- freegames/ant.py | 5 ++--- freegames/bagels.py | 1 - freegames/bounce.py | 5 ++--- freegames/cannon.py | 9 ++++----- freegames/connect.py | 5 ++--- freegames/crypto.py | 9 ++++----- freegames/fidget.py | 7 +++---- freegames/flappy.py | 9 ++++----- freegames/guess.py | 1 - freegames/life.py | 7 +++---- freegames/madlibs.py | 1 - freegames/maze.py | 5 ++--- freegames/memory.py | 11 +++++------ freegames/minesweeper.py | 11 +++++------ freegames/pacman.py | 13 ++++++------- freegames/paint.py | 15 +++++++-------- freegames/pong.py | 9 ++++----- freegames/simonsays.py | 11 +++++------ freegames/snake.py | 7 +++---- freegames/tictactoe.py | 11 +++++------ freegames/tiles.py | 9 ++++----- freegames/tron.py | 5 ++--- freegames/utils.py | 5 ++--- setup.py | 1 + tests/mockturtle.py | 1 - tests/test_bounce.py | 1 - tox.ini | 7 ++++++- 32 files changed, 92 insertions(+), 113 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 88d91c48..331eba3d 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,14 +13,14 @@ jobs: checks: runs-on: ubuntu-latest strategy: - max-parallel: 5 + max-parallel: 6 matrix: - check: [bluecheck, doc8, docs, isortcheck, rstcheck] + check: [bluecheck, doc8, docs, flake8, isortcheck, rstcheck] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: '3.10' - name: Setup tox run: | pip install --upgrade pip @@ -33,10 +33,10 @@ jobs: needs: checks runs-on: ${{ matrix.os }} strategy: - max-parallel: 4 + max-parallel: 5 matrix: os: [ubuntu-latest] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 diff --git a/LICENSE b/LICENSE index 7874bf39..1db7e981 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017-2019 Grant Jenks +Copyright 2017-2022 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/README.rst b/README.rst index cef360bf..32d87e5c 100644 --- a/README.rst +++ b/README.rst @@ -72,8 +72,8 @@ Features - Used in hundreds of hours of classroom instruction - Fully Documented - 100% Test Coverage -- Developed on Python 3.9 -- Tested on CPython 3.6, 3.7, 3.8, 3.9 +- Developed on Python 3.10 +- Tested on CPython 3.6, 3.7, 3.8, 3.9, 3.10 - Tested on Linux, Mac OS X, and Windows - Tested using GitHub Actions @@ -334,7 +334,7 @@ References Free Python Games License ------------------------- -Copyright 2017-2021 Grant Jenks +Copyright 2017-2022 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/freegames/__init__.py b/freegames/__init__.py index 76449978..1d73f372 100644 --- a/freegames/__init__.py +++ b/freegames/__init__.py @@ -55,7 +55,6 @@ code. To launch the editor and make changes to the "snake" game run:: $ python3 -m idlelib.idle snake.py - """ from .utils import floor, line, path, square, vector diff --git a/freegames/__main__.py b/freegames/__main__.py index b083f70a..3212a184 100644 --- a/freegames/__main__.py +++ b/freegames/__main__.py @@ -1,5 +1,4 @@ """Free Games CLI - """ import argparse @@ -10,7 +9,7 @@ def game_file(name): - "Return True if filename represents a game." + """Return True if filename represents a game.""" return ( name.endswith('.py') and not name.startswith('__') @@ -23,7 +22,7 @@ def game_file(name): parser = argparse.ArgumentParser( prog='freegames', description='Free Python Games', - epilog='Copyright 2017 Grant Jenks', + epilog='Copyright 2022 Grant Jenks', ) subparsers = parser.add_subparsers(dest='command', help='sub-command help') diff --git a/freegames/ant.py b/freegames/ant.py index d83c7168..45b32fcd 100644 --- a/freegames/ant.py +++ b/freegames/ant.py @@ -6,7 +6,6 @@ 2. Make the ant leave a trail. 3. Change the ant color based on position. Hint: colormode(255); color(0, 100, 200) - """ from random import * @@ -19,12 +18,12 @@ def wrap(value): - "Wrap value around -200 and 200." + """Wrap value around -200 and 200.""" return value # TODO def draw(): - "Move ant and draw screen." + """Move ant and draw screen.""" ant.move(aim) ant.x = wrap(ant.x) ant.y = wrap(ant.y) diff --git a/freegames/bagels.py b/freegames/bagels.py index 87484eec..4616b2f5 100644 --- a/freegames/bagels.py +++ b/freegames/bagels.py @@ -7,7 +7,6 @@ 3. What's the maximum number of digits we could support? Adapted from code in https://inventwithpython.com/chapter11.html - """ from random import sample, shuffle diff --git a/freegames/bounce.py b/freegames/bounce.py index 64910ca8..ddc14ee8 100644 --- a/freegames/bounce.py +++ b/freegames/bounce.py @@ -7,7 +7,6 @@ 3. Make the ball leave a trail. 4. Change the ball color based on position. Hint: colormode(255); color(0, 100, 200) - """ from random import * @@ -17,7 +16,7 @@ def value(): - "Randomly generate value between (-5, -3) or (3, 5)." + """Randomly generate value between (-5, -3) or (3, 5).""" return (3 + random() * 2) * choice([1, -1]) @@ -26,7 +25,7 @@ def value(): def draw(): - "Move ball and draw game." + """Move ball and draw game.""" ball.move(aim) x = ball.x diff --git a/freegames/cannon.py b/freegames/cannon.py index b42e8410..c68da9b1 100644 --- a/freegames/cannon.py +++ b/freegames/cannon.py @@ -6,7 +6,6 @@ 2. Vary the effect of gravity. 3. Apply gravity to the targets. 4. Change the speed of the ball. - """ from random import randrange @@ -20,7 +19,7 @@ def tap(x, y): - "Respond to screen tap." + """Respond to screen tap.""" if not inside(ball): ball.x = -199 ball.y = -199 @@ -29,12 +28,12 @@ def tap(x, y): def inside(xy): - "Return True if xy within screen." + """Return True if xy within screen.""" return -200 < xy.x < 200 and -200 < xy.y < 200 def draw(): - "Draw ball and targets." + """Draw ball and targets.""" clear() for target in targets: @@ -49,7 +48,7 @@ def draw(): def move(): - "Move ball and targets." + """Move ball and targets.""" if randrange(40) == 0: y = randrange(-150, 150) target = vector(200, y) diff --git a/freegames/connect.py b/freegames/connect.py index a5d103df..5c39e166 100644 --- a/freegames/connect.py +++ b/freegames/connect.py @@ -7,7 +7,6 @@ 3. Add logic to detect a full row. 4. Create a random computer player. 5. How would you detect a winner? - """ from turtle import * @@ -19,7 +18,7 @@ def grid(): - "Draw Connect Four grid." + """Draw Connect Four grid.""" bgcolor('light blue') for x in range(-150, 200, 50): @@ -35,7 +34,7 @@ def grid(): def tap(x, y): - "Draw red or yellow circle in tapped row." + """Draw red or yellow circle in tapped row.""" player = state['player'] rows = state['rows'] diff --git a/freegames/crypto.py b/freegames/crypto.py index 92e0805b..a59affda 100644 --- a/freegames/crypto.py +++ b/freegames/crypto.py @@ -9,12 +9,11 @@ 5. Make the encryption harder to decode. Adapted from code in https://inventwithpython.com/chapter14.html - """ def encrypt(message, key): - "Encrypt message with key." + """Encrypt message with key.""" result = '' # Iterate letters in message and encrypt each individually. @@ -52,17 +51,17 @@ def encrypt(message, key): def decrypt(message, key): - "Decrypt message with key." + """Decrypt message with key.""" return encrypt(message, -key) def decode(message): - "Decode message without key." + """Decode message without key.""" pass # TODO def get_key(): - "Get key from user." + """Get key from user.""" try: text = input('Enter a key (1 - 25): ') key = int(text) diff --git a/freegames/fidget.py b/freegames/fidget.py index e425acd5..84d50d50 100644 --- a/freegames/fidget.py +++ b/freegames/fidget.py @@ -6,7 +6,6 @@ 2. Respond to mouse clicks. 3. Change its acceleration. 4. Make it go forwards and backwards. - """ from turtle import * @@ -15,7 +14,7 @@ def spinner(): - "Draw fidget spinner." + """Draw fidget spinner.""" clear() angle = state['turn'] / 10 right(angle) @@ -35,7 +34,7 @@ def spinner(): def animate(): - "Animate fidget spinner." + """Animate fidget spinner.""" if state['turn'] > 0: state['turn'] -= 1 @@ -44,7 +43,7 @@ def animate(): def flick(): - "Flick fidget spinner." + """Flick fidget spinner.""" state['turn'] += 10 diff --git a/freegames/flappy.py b/freegames/flappy.py index 4aec8aaf..1894c2d5 100644 --- a/freegames/flappy.py +++ b/freegames/flappy.py @@ -6,7 +6,6 @@ 2. Vary the speed. 3. Vary the size of the balls. 4. Allow the bird to move forward and back. - """ from random import * @@ -19,18 +18,18 @@ def tap(x, y): - "Move bird up in response to screen tap." + """Move bird up in response to screen tap.""" up = vector(0, 30) bird.move(up) def inside(point): - "Return True if point on screen." + """Return True if point on screen.""" return -200 < point.x < 200 and -200 < point.y < 200 def draw(alive): - "Draw screen objects." + """Draw screen objects.""" clear() goto(bird.x, bird.y) @@ -48,7 +47,7 @@ def draw(alive): def move(): - "Update object positions." + """Update object positions.""" bird.y -= 5 for ball in balls: diff --git a/freegames/guess.py b/freegames/guess.py index d7e5738c..3cdd60b8 100644 --- a/freegames/guess.py +++ b/freegames/guess.py @@ -6,7 +6,6 @@ 2. Can you still guess the number? 3. Print the number of guesses made. 4. Limit the number of guesses to the minimum required. - """ from random import randint diff --git a/freegames/life.py b/freegames/life.py index 8c0bb3aa..fcee86af 100644 --- a/freegames/life.py +++ b/freegames/life.py @@ -9,7 +9,6 @@ 2. How can you make the simulation faster? Or bigger? 3. How would you modify the initial state? 4. Try changing the rules of life :) - """ from random import choice @@ -21,7 +20,7 @@ def initialize(): - "Randomly initialize the cells." + """Randomly initialize the cells.""" for x in range(-200, 200, 10): for y in range(-200, 200, 10): cells[x, y] = False @@ -32,7 +31,7 @@ def initialize(): def step(): - "Compute one step in the Game of Life." + """Compute one step in the Game of Life.""" neighbors = {} for x in range(-190, 190, 10): @@ -52,7 +51,7 @@ def step(): def draw(): - "Draw all the squares." + """Draw all the squares.""" step() clear() for (x, y), alive in cells.items(): diff --git a/freegames/madlibs.py b/freegames/madlibs.py index 68ff46f1..0328d7fc 100644 --- a/freegames/madlibs.py +++ b/freegames/madlibs.py @@ -5,7 +5,6 @@ 1. How to replace the story? 2. How load the story and from a file? 3. How to add additional parts of speech? - """ # The quick brown fox jumps over the lazy dog. diff --git a/freegames/maze.py b/freegames/maze.py index f90b6b9f..ca40786a 100644 --- a/freegames/maze.py +++ b/freegames/maze.py @@ -5,7 +5,6 @@ 1. Keep score by counting taps. 2. Make the maze harder. 3. Generate the same maze twice. - """ from random import random @@ -15,7 +14,7 @@ def draw(): - "Draw maze." + """Draw maze.""" color('black') width(5) @@ -30,7 +29,7 @@ def draw(): def tap(x, y): - "Draw line and dot for screen tap." + """Draw line and dot for screen tap.""" if abs(x) > 198 or abs(y) > 198: up() else: diff --git a/freegames/memory.py b/freegames/memory.py index 80bc62e8..c0d2ad3d 100644 --- a/freegames/memory.py +++ b/freegames/memory.py @@ -7,7 +7,6 @@ 3. Detect when all tiles are revealed. 4. Center single-digit tile. 5. Use letters instead of tiles. - """ from random import * @@ -22,7 +21,7 @@ def square(x, y): - "Draw white square with black outline at (x, y)." + """Draw white square with black outline at (x, y).""" up() goto(x, y) down() @@ -35,17 +34,17 @@ def square(x, y): def index(x, y): - "Convert (x, y) coordinates to tiles index." + """Convert (x, y) coordinates to tiles index.""" return int((x + 200) // 50 + ((y + 200) // 50) * 8) def xy(count): - "Convert tiles count to (x, y) coordinates." + """Convert tiles count to (x, y) coordinates.""" return (count % 8) * 50 - 200, (count // 8) * 50 - 200 def tap(x, y): - "Update mark and hidden tiles based on tap." + """Update mark and hidden tiles based on tap.""" spot = index(x, y) mark = state['mark'] @@ -58,7 +57,7 @@ def tap(x, y): def draw(): - "Draw image and tiles." + """Draw image and tiles.""" clear() goto(0, 0) shape(car) diff --git a/freegames/minesweeper.py b/freegames/minesweeper.py index 44efa68e..4278af03 100644 --- a/freegames/minesweeper.py +++ b/freegames/minesweeper.py @@ -5,7 +5,6 @@ 1. What does the `seed(0)` function call do? 2. Change the number of bombs on the grid. 3. Change the size of the grid. - """ from random import randrange, seed @@ -20,7 +19,7 @@ def initialize(): - "Initialize `bombs`, `counts`, and `shown` grids." + """Initialize `bombs`, `counts`, and `shown` grids.""" for x in range(-250, 250, 50): for y in range(-250, 250, 50): bombs[x, y] = False @@ -42,21 +41,21 @@ def initialize(): def stamp(x, y, text): - "Display `text` at coordinates `x` and `y`." + """Display `text` at coordinates `x` and `y`.""" square(x, y, 50, 'white') color('black') write(text, font=('Arial', 50, 'normal')) def draw(): - "Draw the initial board grid." + """Draw the initial board grid.""" for x in range(-200, 200, 50): for y in range(-200, 200, 50): stamp(x, y, '?') def end(): - "Draw the bombs as X's on the grid." + """Draw the bombs as X's on the grid.""" for x in range(-200, 200, 50): for y in range(-200, 200, 50): if bombs[x, y]: @@ -64,7 +63,7 @@ def end(): def tap(x, y): - "Respond to screen click at `x` and `y` coordinates." + """Respond to screen click at `x` and `y` coordinates.""" x = floor(x, 50) y = floor(y, 50) diff --git a/freegames/pacman.py b/freegames/pacman.py index 2bc70d2f..644b35a4 100644 --- a/freegames/pacman.py +++ b/freegames/pacman.py @@ -7,7 +7,6 @@ 3. Change where pacman starts. 4. Make the ghosts faster/slower. 5. Make the ghosts smarter. - """ from random import choice @@ -53,7 +52,7 @@ def square(x, y): - "Draw square using path at (x, y)." + """Draw square using path at (x, y).""" path.up() path.goto(x, y) path.down() @@ -67,7 +66,7 @@ def square(x, y): def offset(point): - "Return offset of point in tiles." + """Return offset of point in tiles.""" x = (floor(point.x, 20) + 200) / 20 y = (180 - floor(point.y, 20)) / 20 index = int(x + y * 20) @@ -75,7 +74,7 @@ def offset(point): def valid(point): - "Return True if point is valid in tiles." + """Return True if point is valid in tiles.""" index = offset(point) if tiles[index] == 0: @@ -90,7 +89,7 @@ def valid(point): def world(): - "Draw world using path." + """Draw world using path.""" bgcolor('black') path.color('blue') @@ -109,7 +108,7 @@ def world(): def move(): - "Move pacman and all ghosts." + """Move pacman and all ghosts.""" writer.undo() writer.write(state['score']) @@ -159,7 +158,7 @@ def move(): def change(x, y): - "Change pacman aim if valid." + """Change pacman aim if valid.""" if valid(pacman + vector(x, y)): aim.x = x aim.y = y diff --git a/freegames/paint.py b/freegames/paint.py index 0a7b167e..adc5dec5 100644 --- a/freegames/paint.py +++ b/freegames/paint.py @@ -7,7 +7,6 @@ 3. Complete rectangle. 4. Complete triangle. 5. Add width parameter. - """ from turtle import * @@ -16,7 +15,7 @@ def line(start, end): - "Draw line from start to end." + """Draw line from start to end.""" up() goto(start.x, start.y) down() @@ -24,7 +23,7 @@ def line(start, end): def square(start, end): - "Draw square from start to end." + """Draw square from start to end.""" up() goto(start.x, start.y) down() @@ -38,22 +37,22 @@ def square(start, end): def circle(start, end): - "Draw circle from start to end." + """Draw circle from start to end.""" pass # TODO def rectangle(start, end): - "Draw rectangle from start to end." + """Draw rectangle from start to end.""" pass # TODO def triangle(start, end): - "Draw triangle from start to end." + """Draw triangle from start to end.""" pass # TODO def tap(x, y): - "Store starting point or draw shape." + """Store starting point or draw shape.""" start = state['start'] if start is None: @@ -66,7 +65,7 @@ def tap(x, y): def store(key, value): - "Store value in state at key." + """Store value in state at key.""" state[key] = value diff --git a/freegames/pong.py b/freegames/pong.py index faf0efd0..cbabedc9 100644 --- a/freegames/pong.py +++ b/freegames/pong.py @@ -9,7 +9,6 @@ 5. Change how the ball bounces off walls. 6. How would you add a computer player? 6. Add a second ball. - """ from random import choice, random @@ -19,7 +18,7 @@ def value(): - "Randomly generate value between (-5, -3) or (3, 5)." + """Randomly generate value between (-5, -3) or (3, 5).""" return (3 + random() * 2) * choice([1, -1]) @@ -29,12 +28,12 @@ def value(): def move(player, change): - "Move player position by change." + """Move player position by change.""" state[player] += change def rectangle(x, y, width, height): - "Draw rectangle at (x, y) with given width and height." + """Draw rectangle at (x, y) with given width and height.""" up() goto(x, y) down() @@ -48,7 +47,7 @@ def rectangle(x, y, width, height): def draw(): - "Draw game and move pong ball." + """Draw game and move pong ball.""" clear() rectangle(-200, state[1], 10, 50) rectangle(190, state[2], 10, 50) diff --git a/freegames/simonsays.py b/freegames/simonsays.py index c1aa4733..d583afc8 100644 --- a/freegames/simonsays.py +++ b/freegames/simonsays.py @@ -4,7 +4,6 @@ 1. Speed up tile flash rate. 2. Add more tiles. - """ from random import choice @@ -24,7 +23,7 @@ def grid(): - "Draw grid of tiles." + """Draw grid of tiles.""" square(0, 0, 200, 'dark red') square(0, -200, 200, 'dark blue') square(-200, 0, 200, 'dark green') @@ -33,7 +32,7 @@ def grid(): def flash(tile): - "Flash tile in grid." + """Flash tile in grid.""" glow, dark = tiles[tile] square(tile.x, tile.y, 200, glow) update() @@ -44,7 +43,7 @@ def flash(tile): def grow(): - "Grow pattern and flash tiles." + """Grow pattern and flash tiles.""" tile = choice(list(tiles)) pattern.append(tile) @@ -56,7 +55,7 @@ def grow(): def tap(x, y): - "Respond to screen tap." + """Respond to screen tap.""" onscreenclick(None) x = floor(x, 200) y = floor(y, 200) @@ -76,7 +75,7 @@ def tap(x, y): def start(x, y): - "Start game." + """Start game.""" grow() onscreenclick(tap) diff --git a/freegames/snake.py b/freegames/snake.py index 8bf5a52f..f1f2599f 100644 --- a/freegames/snake.py +++ b/freegames/snake.py @@ -6,7 +6,6 @@ 2. How can you make the snake go around the edges? 3. How would you move the food? 4. Change the snake to respond to mouse clicks. - """ from random import randrange @@ -20,18 +19,18 @@ def change(x, y): - "Change snake direction." + """Change snake direction.""" aim.x = x aim.y = y def inside(head): - "Return True if head inside boundaries." + """Return True if head inside boundaries.""" return -200 < head.x < 190 and -200 < head.y < 190 def move(): - "Move snake forward one segment." + """Move snake forward one segment.""" head = snake[-1].copy() head.move(aim) diff --git a/freegames/tictactoe.py b/freegames/tictactoe.py index b7a4a2d2..23a3142e 100644 --- a/freegames/tictactoe.py +++ b/freegames/tictactoe.py @@ -6,7 +6,6 @@ 2. What happens when someone taps a taken spot? 3. How would you detect when someone has won? 4. How could you create a computer player? - """ from turtle import * @@ -15,7 +14,7 @@ def grid(): - "Draw tic-tac-toe grid." + """Draw tic-tac-toe grid.""" line(-67, 200, -67, -200) line(67, 200, 67, -200) line(-200, -67, 200, -67) @@ -23,13 +22,13 @@ def grid(): def drawx(x, y): - "Draw X player." + """Draw X player.""" line(x, y, x + 133, y + 133) line(x, y + 133, x + 133, y) def drawo(x, y): - "Draw O player." + """Draw O player.""" up() goto(x + 67, y + 5) down() @@ -37,7 +36,7 @@ def drawo(x, y): def floor(value): - "Round value down to grid with square size 133." + """Round value down to grid with square size 133.""" return ((value + 200) // 133) * 133 - 200 @@ -46,7 +45,7 @@ def floor(value): def tap(x, y): - "Draw X or O in tapped square." + """Draw X or O in tapped square.""" x = floor(x) y = floor(y) player = state['player'] diff --git a/freegames/tiles.py b/freegames/tiles.py index 11aef296..94efda28 100644 --- a/freegames/tiles.py +++ b/freegames/tiles.py @@ -6,7 +6,6 @@ 2. Permit diagonal squares as neighbors. 3. Respond to arrow keys instead of mouse clicks. 4. Make the grid bigger. - """ from random import * @@ -24,7 +23,7 @@ def load(): - "Load tiles and scramble." + """Load tiles and scramble.""" count = 1 for y in range(-200, 200, 100): @@ -47,7 +46,7 @@ def load(): def square(mark, number): - "Draw white square with black outline and number." + """Draw white square with black outline and number.""" up() goto(mark.x, mark.y) down() @@ -68,7 +67,7 @@ def square(mark, number): def tap(x, y): - "Swap tile and empty square." + """Swap tile and empty square.""" x = floor(x, 100) y = floor(y, 100) mark = vector(x, y) @@ -85,7 +84,7 @@ def tap(x, y): def draw(): - "Draw all tiles." + """Draw all tiles.""" for mark in tiles: square(mark, tiles[mark]) update() diff --git a/freegames/tron.py b/freegames/tron.py index 5b4cbb58..6cdf0b1a 100644 --- a/freegames/tron.py +++ b/freegames/tron.py @@ -6,7 +6,6 @@ 2. Stop a tron player from running into itself. 3. Allow the tron player to go around the edge of the screen. 4. How would you create a computer player? - """ from turtle import * @@ -23,12 +22,12 @@ def inside(head): - "Return True if head inside screen." + """Return True if head inside screen.""" return -200 < head.x < 200 and -200 < head.y < 200 def draw(): - "Advance players and draw game." + """Advance players and draw game.""" p1xy.move(p1aim) p1head = p1xy.copy() diff --git a/freegames/utils.py b/freegames/utils.py index edf96cbe..30f63292 100644 --- a/freegames/utils.py +++ b/freegames/utils.py @@ -1,5 +1,4 @@ """Utilities - """ # pylint: disable=no-member @@ -38,7 +37,7 @@ def floor(value, size, offset=200): def path(filename): - "Return full path to `filename` in freegames module." + """Return full path to `filename` in freegames module.""" filepath = os.path.realpath(__file__) dirpath = os.path.dirname(filepath) fullpath = os.path.join(dirpath, filename) @@ -46,7 +45,7 @@ def path(filename): def line(a, b, x, y): - "Draw line from `(a, b)` to `(x, y)`." + """Draw line from `(a, b)` to `(x, y)`.""" import turtle turtle.up() diff --git a/setup.py b/setup.py index ed4ee8c6..8d4e9e3f 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ def run_tests(self): 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Games/Entertainment', 'Topic :: Games/Entertainment :: Arcade', diff --git a/tests/mockturtle.py b/tests/mockturtle.py index 9bb2b019..26e5d52b 100644 --- a/tests/mockturtle.py +++ b/tests/mockturtle.py @@ -1,5 +1,4 @@ """Mock turtle module. - """ state = {} diff --git a/tests/test_bounce.py b/tests/test_bounce.py index 5cad045a..eef2bc20 100644 --- a/tests/test_bounce.py +++ b/tests/test_bounce.py @@ -1,7 +1,6 @@ import random import runpy import sys -import turtle import mockturtle diff --git a/tox.ini b/tox.ini index 91a15c5b..5d835318 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,isortcheck,rstcheck,py36,py37,py38,py39 +envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py36,py37,py38,py39,py310 skip_missing_interpreters=True [testenv] @@ -30,6 +30,10 @@ commands=make html deps= sphinx +[testenv:flake8] +commands=flake8 {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +deps=flake8 + [testenv:isort] commands=isort {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests deps=isort @@ -71,4 +75,5 @@ testpaths=docs freegames tests README.rst # ignore=D000 [flake8] +ignore=E722,F403,F405,W503 max-line-length=120 From 3f27c6555e2c72a9b1c668c4dd8506d7b8378d09 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 29 Nov 2021 22:22:37 -0800 Subject: [PATCH 28/77] Bump version to 2.4.0 --- freegames/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freegames/__init__.py b/freegames/__init__.py index 1d73f372..93a04f93 100644 --- a/freegames/__init__.py +++ b/freegames/__init__.py @@ -62,8 +62,8 @@ __all__ = ['floor', 'line', 'path', 'square', 'vector'] __title__ = 'freegames' -__version__ = '2.3.2' -__build__ = 0x020302 +__version__ = '2.4.0' +__build__ = 0x020400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = '2017-2021, Grant Jenks' +__copyright__ = '2017-2022, Grant Jenks' From b593e16c48dc0e2250c45dd0ce2893d0937f8b97 Mon Sep 17 00:00:00 2001 From: Nitin Kumar <40369168+nitinkumar30@users.noreply.github.com> Date: Sun, 24 Apr 2022 23:42:56 +0530 Subject: [PATCH 29/77] Not printing value beforehand (#84) Printing the number before the user can actually guess the number. Doesn't justify the game, so need to comment the line. --- freegames/guess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freegames/guess.py b/freegames/guess.py index 3cdd60b8..d77fd842 100644 --- a/freegames/guess.py +++ b/freegames/guess.py @@ -14,7 +14,7 @@ end = 100 value = randint(start, end) -print(value) +# print(value) print("I'm thinking of a number between", start, 'and', end) guess = None From f8374506a16909cd9b3ad941bdd2d6aabe70a34d Mon Sep 17 00:00:00 2001 From: gaurav17467 <84239101+gaurav17467@users.noreply.github.com> Date: Tue, 27 Dec 2022 09:14:00 +0530 Subject: [PATCH 30/77] Create rock_paper_scissors.py (#70) --- freegames/rock_paper_scissors.py | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 freegames/rock_paper_scissors.py diff --git a/freegames/rock_paper_scissors.py b/freegames/rock_paper_scissors.py new file mode 100644 index 00000000..a7e50a8e --- /dev/null +++ b/freegames/rock_paper_scissors.py @@ -0,0 +1,74 @@ +import random +import os + +WIN_LIST_RPS = [('ROCK', 'SCISSORS'), + ('SCISSORS', 'PAPER'), + ('PAPER', 'ROCK')] + +SELECTIONS_LIST_RPS = ['ROCK', 'PAPER', 'SCISSORS'] + +stats_count = {'player':0, 'computer':0, 'ties':0} + +def clrscr(): + if os.name == "posix": + os.system('clear') + elif os.name in ("nt", "dos", "ce"): + os.system('CLS') + + +def lets_play(): + """ A function that sets which game is going to be played based on the option selected """ + clrscr() + + intro = (" Welcome to Rock + Paper + Scissors game!") + option_help = ("\n This game is simple and goes as following:" + "\n *You can choose between: Rock, Paper or Scissors." + "\n\n You must take in consideration that:" + "\n *Rock crushers Scissors." + "\n *Scissors cut Paper." + "\n *Paper covers Rock." + "\n\n Notes: *The first one that scores 5 points WINS the game!") + selections_list = SELECTIONS_LIST_RPS + win_list = WIN_LIST_RPS + print(intro) + print(option_help) + + while True: + player_selection = input("Your Selection {}: ".format(selections_list)) + player_selection = player_selection.upper() + + if player_selection in selections_list: + clrscr() + computer_selection = random.choice(selections_list) + + print("Player Selection: {}".format(player_selection)) + print("Computer Selection: {}".format(computer_selection)) + + match = player_selection, computer_selection + + if player_selection == computer_selection: + stats_count['ties'] += 1 + print("\nResult: Both are {}! So that's a Tie!".format(player_selection)) + elif match in win_list: + stats_count['player'] += 1 + print("\nResult: The power of {} beats {}! You won!".format(player_selection, computer_selection)) + else: + stats_count['computer'] += 1 + print("\nResult: The power of {} is stronger than {}! You lost!".format(computer_selection, player_selection)) + print("------- STATS -------" + "\n YOU | COMPUTER | DRAW" + "\n {player} {computer} {ties}" + "\n-----------------------".format(**stats_count)) + if stats_count['player'] == 5: + print("The game is DONE! You won! Congratulations!") + exit() + elif stats_count['computer'] == 5: + print("The game is DONE! You lost against a perfectly designed AI! You're now in the wall of shame!") + exit() + elif player_selection == 'HELP': + clrscr() + print(option_help) + else: + print("Invalid selection. Try again...") + +lets_play() From 1a3940eaa41061508ab781c2670a73df3d1d63cf Mon Sep 17 00:00:00 2001 From: Bamboudorer <51965632+Bamboudorer@users.noreply.github.com> Date: Tue, 27 Dec 2022 07:47:43 +0400 Subject: [PATCH 31/77] Feature horizontal illusion line (#82) * Feature horizontal illusion line * Update for test flake8 and isortcheck * Code reformat with tox -e blue --- freegames/illusion.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 freegames/illusion.py diff --git a/freegames/illusion.py b/freegames/illusion.py new file mode 100644 index 00000000..1e283a2e --- /dev/null +++ b/freegames/illusion.py @@ -0,0 +1,54 @@ +"""Illusion, just create a small illusion. + +Exercises : + +1. Change the number of line. +2. Change the size of the line or the square +""" + +from turtle import * + +from freegames import line, square + +square_size = 25 + + +def draw_one_line_square(start_x, start_y): + for i in range(0, 10): + place_x = start_x + (i * square_size * 2) + square(place_x, start_y, square_size, 'black') + + +def draw_all_lines(): + base_x = -200 + base_y = 150 + for i in range(0, 11): + line( + base_x, + base_y - i * square_size, + base_x + 16 * square_size, + base_y - i * square_size, + ) + + +def draw_all_square(): + draw_one_line_square(-200, 150) + draw_one_line_square(-190, 125) + draw_one_line_square(-180, 100) + draw_one_line_square(-190, 75) + draw_one_line_square(-200, 50) + draw_one_line_square(-190, 25) + draw_one_line_square(-180, 0) + draw_one_line_square(-190, -25) + draw_one_line_square(-200, -50) + draw_one_line_square(-190, -75) + draw_one_line_square(-180, -100) + + +setup(420, 400, 30, 0) +hideturtle() +tracer(False) +listen() +draw_all_lines() +draw_all_square() +done() From 23914d20d93e267ee977927a1b7c3f6e034151c5 Mon Sep 17 00:00:00 2001 From: BaekMinHyeon <44044119+BaekMinHyeon@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:53:08 +0900 Subject: [PATCH 32/77] hangman game code (#91) * hangman game code * Add docs about hangman --- docs/hangman.rst | 6 +++ freegames/hangman.py | 112 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 docs/hangman.rst create mode 100644 freegames/hangman.py diff --git a/docs/hangman.rst b/docs/hangman.rst new file mode 100644 index 00000000..b017049d --- /dev/null +++ b/docs/hangman.rst @@ -0,0 +1,6 @@ +Hangman +==== + +Hangman, a letter guessing game. + +.. literalinclude:: ../freegames/hangman.py \ No newline at end of file diff --git a/freegames/hangman.py b/freegames/hangman.py new file mode 100644 index 00000000..9839d79c --- /dev/null +++ b/freegames/hangman.py @@ -0,0 +1,112 @@ +"""Hangman, a letter guessing game + +Exercises +1. Change Hangman's picture (position, length, etc) +2. Make an additional dictionary, not fruits +3. Convert lowercase letters to uppercase letters +""" + +from turtle import * +from random import choice +import functools + +def screen_write(letter): + """write text on the screen""" + write(letter, move=True, font=("Courier", 14, "normal")) + +def move_write(x, y, letter): + """After move, write text on the screen""" + goto(x, y) + screen_write(letter) + + +def draw(x, y, angle, length, straight): + """draw a hangman game""" + goto(x, y) + pendown() + left(angle) + if straight: + forward(length) + else: + circle(length, None, 100) + penup() + +def draw_Hangman(step): + if step == 1: + # draw head + draw(-45, 160, 270, 15, False) + elif step == 2: + # draw torso + draw(-45, 130, 90, 40, True) + elif step == 3: + # draw left arm + draw(-45, 120, 235, 40, True) + elif step == 4: + # draw right arm + draw(-45, 120, 250, 40, True) + elif step == 5: + # draw left leg + draw(-45, 90, 205, 40, True) + elif step == 6: + # draw right leg + draw(-45, 90, 60, 40, True) + +def guess(letter): + global correct + global use + global wrong + global finish + if not finish: + if letter not in use: + goto(-100,-35) + for i in word: + if i == letter: + screen_write(letter.lower() + " ") + correct += letter + else: + screen_write("_ ") + use += letter + if letter not in word: + wrong += 1 + draw_Hangman(wrong) + move_write(-200, -100, "Guesses: ") + for i in use: + screen_write(i.lower() + " ") + if wrong == 6: + goto(-100,-35) + for i in word: + if i in correct: + screen_write("_ ") + else: + screen_write(i.lower() + " ") + move_write(-130, -60, "Sorry, you lose!") + finish = True + if len(correct) == len(word): + move_write(-130, -60, "Congratulations, you win!") + finish = True + +hideturtle() +speed(0) + +fruitword = ['apple', 'banana', 'blueberry', 'cherry', 'coconut', 'durian', 'grape', 'kiwi', 'lemon', + 'lime', 'mango', 'melon', 'orange', 'peach', 'pear', 'pineapple', 'strawberry','watermelon'] + +"""draw gallows""" +penup() +draw(50, 0, 90, 175, True) +draw(50, 175, 90, 95, True) +draw(-45, 175, 90, 15, True) + +goto(-100,-35) +word = choice(fruitword) +correct = [] +use = [] +wrong = 0 +finish = False +for i in word: + screen_write("_ ") +for ascii in range(ord('a'), ord('z')+1): + letter = chr(ascii) + onkey(functools.partial(guess, letter), letter) +listen() +done() \ No newline at end of file From ce73415be2e5d469934a26f22cb127796eb5c5ea Mon Sep 17 00:00:00 2001 From: hanwooseok1 <44044200+hanwooseok1@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:54:20 +0900 Subject: [PATCH 33/77] typing game code (#92) * typing game * add docs for typing.py. --- docs/typing.rst | 6 +++ freegames/typing.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 docs/typing.rst create mode 100644 freegames/typing.py diff --git a/docs/typing.rst b/docs/typing.rst new file mode 100644 index 00000000..e7c5eb26 --- /dev/null +++ b/docs/typing.rst @@ -0,0 +1,6 @@ +Typing +===== + +Typing, simple typing game. + +.. literalinclude:: ../freegames/typing.py diff --git a/freegames/typing.py b/freegames/typing.py new file mode 100644 index 00000000..b7496b6b --- /dev/null +++ b/freegames/typing.py @@ -0,0 +1,97 @@ +""" Typing, typing game to remove the same letters. + +Exercises + +1. Change the speed of letters. +2. Change letters to simple words. +3. Add bonus words. + +""" +from random import * +from turtle import * +from string import ascii_lowercase +from freegames import vector + +targets = [] +letters = [] +score = 0 + +def inside(xy): + """Return True if xy within screen.""" + return -200 < xy.x < 200 and -200 < xy.y < 200 + +def draw(): + """Draw letters.""" + clear() + count = 0 + for target in targets: + goto(target.x, target.y) + write(letters[count], align='center', font=('Consolas', 20, 'normal')) + count += 1 + update() + +def move(): + """Move letters.""" + if randrange(10) == 0: + x = randrange(-150, 150) + target = vector(x, 200) + targets.append(target) + while True: + letter = choice(ascii_lowercase) + if letter not in letters: + letters.append(letter) + break + for target in targets: + target.y -= 5 + draw() + for target in targets: + if not inside(target): + return + ontimer(move, 100) + +def press(c): + """Press key.""" + global score + if c in letters: + score += 1 + k = letters.index(c) + del targets[k] + del letters[k] + print('score:', score) + else: + score -= 1 + print('score:', score) + +setup(420, 420, 370, 0) +hideturtle() +up() +tracer(False) +listen() +onkey(lambda: press('a'), 'a') +onkey(lambda: press('b'), 'b') +onkey(lambda: press('c'), 'c') +onkey(lambda: press('d'), 'd') +onkey(lambda: press('e'), 'e') +onkey(lambda: press('f'), 'f') +onkey(lambda: press('g'), 'g') +onkey(lambda: press('h'), 'h') +onkey(lambda: press('i'), 'i') +onkey(lambda: press('j'), 'j') +onkey(lambda: press('k'), 'k') +onkey(lambda: press('l'), 'l') +onkey(lambda: press('m'), 'm') +onkey(lambda: press('n'), 'n') +onkey(lambda: press('o'), 'o') +onkey(lambda: press('p'), 'p') +onkey(lambda: press('q'), 'q') +onkey(lambda: press('r'), 'r') +onkey(lambda: press('s'), 's') +onkey(lambda: press('t'), 't') +onkey(lambda: press('u'), 'u') +onkey(lambda: press('v'), 'v') +onkey(lambda: press('w'), 'w') +onkey(lambda: press('x'), 'x') +onkey(lambda: press('y'), 'y') +onkey(lambda: press('z'), 'z') +move() +done() \ No newline at end of file From b13ca795a0abd1b4df93e3a9951fb7ed1c63f5c0 Mon Sep 17 00:00:00 2001 From: SongJungHyun1004 <44044105+SongJungHyun1004@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:55:59 +0900 Subject: [PATCH 34/77] Add Avoid game (#93) * add avoid.py game * Condensing four lists to a single list & modifying some codes * add rst file to docs --- docs/avoid.rst | 6 +++ freegames/avoid.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 docs/avoid.rst create mode 100644 freegames/avoid.py diff --git a/docs/avoid.rst b/docs/avoid.rst new file mode 100644 index 00000000..de259a1d --- /dev/null +++ b/docs/avoid.rst @@ -0,0 +1,6 @@ +Avoid +====== + +Avoid, classic arcade game. + +.. literalinclude:: ../freegames/avoid.py diff --git a/freegames/avoid.py b/freegames/avoid.py new file mode 100644 index 00000000..ab1bc9aa --- /dev/null +++ b/freegames/avoid.py @@ -0,0 +1,99 @@ +"""Avoid, classic arcade game. + +Exercises + +1. Check the time you've lived. +2. Vary the person's speed & bomb's speed. +3. Vary the size of the bombs. +4. Vary the person's color & bomb's color. +""" +from random import * +from turtle import * +import time +from freegames import vector + +person = vector(0, 0) +bombs = [] +speeds = [] +aim = vector(0, 0) +start = time.time() + +def change(x, y): + """Change person direction.""" + aim.x = x + aim.y = y + +def inside(point): + """Return True if point on screen.""" + return -200 < point.x < 200 and -200 < point.y < 200 + +def draw(alive): + """Draw screen objects.""" + clear() + goto(person.x, person.y) + if alive: + dot(10, 'blue') + else: + dot(10, 'red') + for bomb in bombs: + goto(bomb.x, bomb.y) + dot(20, 'black') + + present = time.time() + print('\rtime : %.3f' %float(present-start), end='sec') + update() + +def move(): + """Update object positions.""" + person.move(aim) + + for bomb, speed in zip(bombs, speeds): + bomb.move(speed) + + if randrange(10) == 0: + x = randrange(-199, 199) + y = randrange(-199, 199) + dir = randrange(4) + s = randrange(3, 11) + if dir == 0: + bomb = vector(x, 199) + speed = vector(0, -s) + elif dir == 1: + bomb = vector(x, -199) + speed = vector(0, s) + elif dir == 2: + bomb = vector(199, y) + speed = vector(-s, 0) + else: + bomb = vector(-199, y) + speed = vector(s, 0) + bombs.append(bomb) + speeds.append(speed) + + while len(bombs) > 0 and not inside(bombs[0]): + bombs.pop(0) + speeds.pop(0) + + if not inside(person): + draw(False) + return + + for bomb in bombs: + if abs(bomb - person) < 15: + draw(False) + return + + draw(True) + ontimer(move, 50) + +setup(420, 420, 370, 0) +hideturtle() +up() +tracer(False) +listen() +onkey(lambda: change(5, 0), 'Right') +onkey(lambda: change(-5, 0), 'Left') +onkey(lambda: change(0, 5), 'Up') +onkey(lambda: change(0, -5), 'Down') +move() +done() \ No newline at end of file From 27803c0eee055b964cfb4f90ca896a96bf109066 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 5 Nov 2022 21:17:30 -0700 Subject: [PATCH 35/77] Add link to youtube video --- docs/give-gift-python.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/give-gift-python.rst b/docs/give-gift-python.rst index 3b1512fc..69a2246f 100644 --- a/docs/give-gift-python.rst +++ b/docs/give-gift-python.rst @@ -1,7 +1,10 @@ Give the Gift of Python ======================= -*Talk given at SF Python Holiday Party on December 5, 2018.* +* `Give the Gift of Python Video`_ +* Talk given at SF Python Holiday Party on December 5, 2018. + +.. _`Give the Gift of Python Video`: https://www.youtube.com/watch?v=BVI78g4V4so Who am I? --------- From 06a07b85e0208b1adefee30f91368844f868c02e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 26 Dec 2022 23:59:17 -0800 Subject: [PATCH 36/77] Update rock, paper, scissors to use different strategies --- freegames/rock_paper_scissors.py | 74 ------------------------- freegames/rps.py | 92 ++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 74 deletions(-) delete mode 100644 freegames/rock_paper_scissors.py create mode 100644 freegames/rps.py diff --git a/freegames/rock_paper_scissors.py b/freegames/rock_paper_scissors.py deleted file mode 100644 index a7e50a8e..00000000 --- a/freegames/rock_paper_scissors.py +++ /dev/null @@ -1,74 +0,0 @@ -import random -import os - -WIN_LIST_RPS = [('ROCK', 'SCISSORS'), - ('SCISSORS', 'PAPER'), - ('PAPER', 'ROCK')] - -SELECTIONS_LIST_RPS = ['ROCK', 'PAPER', 'SCISSORS'] - -stats_count = {'player':0, 'computer':0, 'ties':0} - -def clrscr(): - if os.name == "posix": - os.system('clear') - elif os.name in ("nt", "dos", "ce"): - os.system('CLS') - - -def lets_play(): - """ A function that sets which game is going to be played based on the option selected """ - clrscr() - - intro = (" Welcome to Rock + Paper + Scissors game!") - option_help = ("\n This game is simple and goes as following:" - "\n *You can choose between: Rock, Paper or Scissors." - "\n\n You must take in consideration that:" - "\n *Rock crushers Scissors." - "\n *Scissors cut Paper." - "\n *Paper covers Rock." - "\n\n Notes: *The first one that scores 5 points WINS the game!") - selections_list = SELECTIONS_LIST_RPS - win_list = WIN_LIST_RPS - print(intro) - print(option_help) - - while True: - player_selection = input("Your Selection {}: ".format(selections_list)) - player_selection = player_selection.upper() - - if player_selection in selections_list: - clrscr() - computer_selection = random.choice(selections_list) - - print("Player Selection: {}".format(player_selection)) - print("Computer Selection: {}".format(computer_selection)) - - match = player_selection, computer_selection - - if player_selection == computer_selection: - stats_count['ties'] += 1 - print("\nResult: Both are {}! So that's a Tie!".format(player_selection)) - elif match in win_list: - stats_count['player'] += 1 - print("\nResult: The power of {} beats {}! You won!".format(player_selection, computer_selection)) - else: - stats_count['computer'] += 1 - print("\nResult: The power of {} is stronger than {}! You lost!".format(computer_selection, player_selection)) - print("------- STATS -------" - "\n YOU | COMPUTER | DRAW" - "\n {player} {computer} {ties}" - "\n-----------------------".format(**stats_count)) - if stats_count['player'] == 5: - print("The game is DONE! You won! Congratulations!") - exit() - elif stats_count['computer'] == 5: - print("The game is DONE! You lost against a perfectly designed AI! You're now in the wall of shame!") - exit() - elif player_selection == 'HELP': - clrscr() - print(option_help) - else: - print("Invalid selection. Try again...") - -lets_play() diff --git a/freegames/rps.py b/freegames/rps.py new file mode 100644 index 00000000..bb1dc650 --- /dev/null +++ b/freegames/rps.py @@ -0,0 +1,92 @@ +"""Rock, Paper, Scissors + +The game is played in a series as follows: + +1. Player chooses "r" for rock, "p" for paper, or "s" for scissors. +2. The computer will also choose rock, paper, or scissors. +3. The result will either be a win, loss, or tie: + a. Rock crushes scissors. + b. Scissors cuts paper. + c. Paper covers rock. + +First to score 5 points wins the series! + +Exercises: + +1. Create a strategy to rotate through 'r', 'p', and 's' in a cycle. +2. Create a strategy to do the same as the last computer choice. +3. Create a new strategy of your own. +""" + +import random + +beats = {'r': 's', 'p': 'r', 's': 'p'} +loses = {value: key for key, value in beats.items()} +state = {'player': 0, 'computer': 0, 'ties': 0} +guesses = [] +default = random.choice('rps') + + +def always_same(): + return default + + +def random_strategy(): + return random.choice('rps') + + +def beat_last(): + if not guesses: + return default + last, _ = guesses[-1] + return loses[last] + + +strategies = [always_same, random_strategy, beat_last] +strategy = random.choice(strategies) + + +def get_option(): + while True: + choice = input('Enter "r" for rock, "p" for paper, "s" for scissors: ') + if choice not in beats: + print('Invalid choice.') + continue + return choice + + +print(__doc__) + +while True: + player = get_option() + computer = strategy() + guess = (player, computer) + guesses.append(guess) + + print('Player chooses:', player) + print('Computer chooses:', computer) + + if player == computer: + state['ties'] += 1 + print('Tie!') + elif beats[player] == computer: + state['player'] += 1 + print('Player wins!') + else: + state['computer'] += 1 + print('Computer wins!') + + print() + print('Player points:', state['player']) + print('Computer points:', state['computer']) + print() + + if state['player'] == 5: + print('PLAYER WINS!') + break + + if state['computer'] == 5: + print('COMPUTER WINS!') + break + +print('Computer strategy:', strategy.__name__) From e882097c2de0d22e35ab300afceddc1714fa1adc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 00:10:43 -0800 Subject: [PATCH 37/77] Simplify illusion --- freegames/illusion.py | 62 +++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/freegames/illusion.py b/freegames/illusion.py index 1e283a2e..1054015c 100644 --- a/freegames/illusion.py +++ b/freegames/illusion.py @@ -1,54 +1,42 @@ -"""Illusion, just create a small illusion. +"""Illusion -Exercises : +Exercises: -1. Change the number of line. -2. Change the size of the line or the square +1. Change the size of the squares. +2. Change the number of rows and lines. """ from turtle import * - +from itertools import cycle from freegames import line, square -square_size = 25 +size = 25 -def draw_one_line_square(start_x, start_y): +def draw_row(x, y): for i in range(0, 10): - place_x = start_x + (i * square_size * 2) - square(place_x, start_y, square_size, 'black') - - -def draw_all_lines(): - base_x = -200 - base_y = 150 - for i in range(0, 11): - line( - base_x, - base_y - i * square_size, - base_x + 16 * square_size, - base_y - i * square_size, - ) - - -def draw_all_square(): - draw_one_line_square(-200, 150) - draw_one_line_square(-190, 125) - draw_one_line_square(-180, 100) - draw_one_line_square(-190, 75) - draw_one_line_square(-200, 50) - draw_one_line_square(-190, 25) - draw_one_line_square(-180, 0) - draw_one_line_square(-190, -25) - draw_one_line_square(-200, -50) - draw_one_line_square(-190, -75) - draw_one_line_square(-180, -100) + offset = x + (i * size * 2) + square(offset, y, size, 'black') + + +def draw_rows(): + offsets = [-200, -190, -180, -190] + pairs = zip(cycle(offsets), range(150, -176, -25)) + for offset, y in pairs: + draw_row(offset, y) + + +def draw_lines(): + x = -200 + y = 150 + for i in range(0, 14): + line(x, y - i * size, x + 16 * size, y - i * size) setup(420, 400, 30, 0) hideturtle() tracer(False) listen() -draw_all_lines() -draw_all_square() +draw_rows() +draw_lines() done() From 4ab7f7df25426b1c77b5376a9058aeb9aab5a6cd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 15:11:57 -0800 Subject: [PATCH 38/77] Add vector.set and refactor/simplify game --- freegames/avoid.py | 90 ++++++++++++++++++++++------------------------ freegames/utils.py | 13 +++++++ 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/freegames/avoid.py b/freegames/avoid.py index ab1bc9aa..ec96b5f0 100644 --- a/freegames/avoid.py +++ b/freegames/avoid.py @@ -2,98 +2,92 @@ Exercises -1. Check the time you've lived. -2. Vary the person's speed & bomb's speed. -3. Vary the size of the bombs. -4. Vary the person's color & bomb's color. +1. Display the duration of the game. +2. Vary the size of the bombs. +3. Vary the speed of the bombs. """ + from random import * from turtle import * -import time from freegames import vector -person = vector(0, 0) +north, south = vector(0, 4), vector(0, -4) +east, west = vector(4, 0), vector(-4, 0) +options = north, south, east, west + +player = vector(0, 0) +aim = choice(options).copy() bombs = [] speeds = [] -aim = vector(0, 0) -start = time.time() -def change(x, y): - """Change person direction.""" - aim.x = x - aim.y = y def inside(point): """Return True if point on screen.""" return -200 < point.x < 200 and -200 < point.y < 200 + def draw(alive): """Draw screen objects.""" clear() - goto(person.x, person.y) - if alive: - dot(10, 'blue') - else: - dot(10, 'red') + goto(player.x, player.y) + color = 'blue' if alive else 'red' + dot(10, color) for bomb in bombs: goto(bomb.x, bomb.y) dot(20, 'black') - - present = time.time() - print('\rtime : %.3f' %float(present-start), end='sec') update() + def move(): - """Update object positions.""" - person.move(aim) + """Update player and bomb positions.""" + player.move(aim) for bomb, speed in zip(bombs, speeds): bomb.move(speed) if randrange(10) == 0: - x = randrange(-199, 199) - y = randrange(-199, 199) - dir = randrange(4) - s = randrange(3, 11) - if dir == 0: - bomb = vector(x, 199) - speed = vector(0, -s) - elif dir == 1: - bomb = vector(x, -199) - speed = vector(0, s) - elif dir == 2: - bomb = vector(199, y) - speed = vector(-s, 0) - else: - bomb = vector(-199, y) - speed = vector(s, 0) + speed = choice(options).copy() + offset = randrange(-199, 200) + + if speed == north: + bomb = vector(offset, -199) + if speed == south: + bomb = vector(offset, 199) + if speed == east: + bomb = vector(-199, offset) + if speed == west: + bomb = vector(199, offset) + bombs.append(bomb) speeds.append(speed) - while len(bombs) > 0 and not inside(bombs[0]): - bombs.pop(0) - speeds.pop(0) + for index in reversed(range(len(bombs))): + bomb = bombs[index] + if not inside(bomb): + del bombs[index] + del speeds[index] - if not inside(person): + if not inside(player): draw(False) return for bomb in bombs: - if abs(bomb - person) < 15: + if abs(bomb - player) < 15: draw(False) return draw(True) ontimer(move, 50) + setup(420, 420, 370, 0) hideturtle() up() tracer(False) listen() -onkey(lambda: change(5, 0), 'Right') -onkey(lambda: change(-5, 0), 'Left') -onkey(lambda: change(0, 5), 'Up') -onkey(lambda: change(0, -5), 'Down') +onkey(lambda: aim.set(north), 'Up') +onkey(lambda: aim.set(south), 'Down') +onkey(lambda: aim.set(east), 'Right') +onkey(lambda: aim.set(west), 'Left') move() -done() \ No newline at end of file +done() diff --git a/freegames/utils.py b/freegames/utils.py index 30f63292..ce9a56e0 100644 --- a/freegames/utils.py +++ b/freegames/utils.py @@ -165,6 +165,19 @@ def __hash__(self): self._hash = hash(pair) return self._hash + def set(self, other): + """Set vector x, y to other x, y. + + >>> a = vector(0, 0) + >>> b = vector(1, 2) + >>> a.set(b) + >>> a + vector(1, 2) + + """ + self.x = other.x + self.y = other.y + def __len__(self): """v.__len__() -> len(v) From 873dc2a46a4dcc719b087180c13eab337ea156eb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 16:16:28 -0800 Subject: [PATCH 39/77] Refactor typing and tune speed --- freegames/typing.py | 90 +++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/freegames/typing.py b/freegames/typing.py index b7496b6b..fe85d87f 100644 --- a/freegames/typing.py +++ b/freegames/typing.py @@ -1,14 +1,15 @@ -""" Typing, typing game to remove the same letters. +"""Game to practice typing. Exercises 1. Change the speed of letters. -2. Change letters to simple words. -3. Add bonus words. - +2. Add uppercase letters. +3. Make the game faster as the score gets higher. +4. Make the letters more frequent as the score gets higher. """ -from random import * + from turtle import * +from random import choice, randrange from string import ascii_lowercase from freegames import vector @@ -16,82 +17,65 @@ letters = [] score = 0 -def inside(xy): - """Return True if xy within screen.""" - return -200 < xy.x < 200 and -200 < xy.y < 200 + +def inside(point): + """Return True if point on screen.""" + return -200 < point.x < 200 and -200 < point.y < 200 + def draw(): """Draw letters.""" clear() - count = 0 - for target in targets: + + for target, letter in zip(targets, letters): goto(target.x, target.y) - write(letters[count], align='center', font=('Consolas', 20, 'normal')) - count += 1 + write(letter, align='center', font=('Consolas', 20, 'normal')) + update() + def move(): """Move letters.""" - if randrange(10) == 0: + if randrange(20) == 0: x = randrange(-150, 150) target = vector(x, 200) targets.append(target) - while True: - letter = choice(ascii_lowercase) - if letter not in letters: - letters.append(letter) - break + letter = choice(ascii_lowercase) + letters.append(letter) + for target in targets: - target.y -= 5 + target.y -= 1 + draw() + for target in targets: if not inside(target): return + ontimer(move, 100) -def press(c): + +def press(key): """Press key.""" global score - if c in letters: + + if key in letters: score += 1 - k = letters.index(c) - del targets[k] - del letters[k] - print('score:', score) + pos = letters.index(key) + del targets[pos] + del letters[pos] else: score -= 1 - print('score:', score) + + print('Score:', score) + setup(420, 420, 370, 0) hideturtle() up() tracer(False) listen() -onkey(lambda: press('a'), 'a') -onkey(lambda: press('b'), 'b') -onkey(lambda: press('c'), 'c') -onkey(lambda: press('d'), 'd') -onkey(lambda: press('e'), 'e') -onkey(lambda: press('f'), 'f') -onkey(lambda: press('g'), 'g') -onkey(lambda: press('h'), 'h') -onkey(lambda: press('i'), 'i') -onkey(lambda: press('j'), 'j') -onkey(lambda: press('k'), 'k') -onkey(lambda: press('l'), 'l') -onkey(lambda: press('m'), 'm') -onkey(lambda: press('n'), 'n') -onkey(lambda: press('o'), 'o') -onkey(lambda: press('p'), 'p') -onkey(lambda: press('q'), 'q') -onkey(lambda: press('r'), 'r') -onkey(lambda: press('s'), 's') -onkey(lambda: press('t'), 't') -onkey(lambda: press('u'), 'u') -onkey(lambda: press('v'), 'v') -onkey(lambda: press('w'), 'w') -onkey(lambda: press('x'), 'x') -onkey(lambda: press('y'), 'y') -onkey(lambda: press('z'), 'z') +for letter in ascii_lowercase: + onkey(lambda letter=letter: press(letter), letter) move() -done() \ No newline at end of file +done() From 4dad2845175aaca6124a94e8d7a059202cbe32c0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 16:37:49 -0800 Subject: [PATCH 40/77] Game is problematic --- freegames/hangman.py | 112 ------------------------------------------- 1 file changed, 112 deletions(-) delete mode 100644 freegames/hangman.py diff --git a/freegames/hangman.py b/freegames/hangman.py deleted file mode 100644 index 9839d79c..00000000 --- a/freegames/hangman.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Hangman, a letter guessing game - -Exercises -1. Change Hangman's picture (position, length, etc) -2. Make an additional dictionary, not fruits -3. Convert lowercase letters to uppercase letters -""" - -from turtle import * -from random import choice -import functools - -def screen_write(letter): - """write text on the screen""" - write(letter, move=True, font=("Courier", 14, "normal")) - -def move_write(x, y, letter): - """After move, write text on the screen""" - goto(x, y) - screen_write(letter) - - -def draw(x, y, angle, length, straight): - """draw a hangman game""" - goto(x, y) - pendown() - left(angle) - if straight: - forward(length) - else: - circle(length, None, 100) - penup() - -def draw_Hangman(step): - if step == 1: - # draw head - draw(-45, 160, 270, 15, False) - elif step == 2: - # draw torso - draw(-45, 130, 90, 40, True) - elif step == 3: - # draw left arm - draw(-45, 120, 235, 40, True) - elif step == 4: - # draw right arm - draw(-45, 120, 250, 40, True) - elif step == 5: - # draw left leg - draw(-45, 90, 205, 40, True) - elif step == 6: - # draw right leg - draw(-45, 90, 60, 40, True) - -def guess(letter): - global correct - global use - global wrong - global finish - if not finish: - if letter not in use: - goto(-100,-35) - for i in word: - if i == letter: - screen_write(letter.lower() + " ") - correct += letter - else: - screen_write("_ ") - use += letter - if letter not in word: - wrong += 1 - draw_Hangman(wrong) - move_write(-200, -100, "Guesses: ") - for i in use: - screen_write(i.lower() + " ") - if wrong == 6: - goto(-100,-35) - for i in word: - if i in correct: - screen_write("_ ") - else: - screen_write(i.lower() + " ") - move_write(-130, -60, "Sorry, you lose!") - finish = True - if len(correct) == len(word): - move_write(-130, -60, "Congratulations, you win!") - finish = True - -hideturtle() -speed(0) - -fruitword = ['apple', 'banana', 'blueberry', 'cherry', 'coconut', 'durian', 'grape', 'kiwi', 'lemon', - 'lime', 'mango', 'melon', 'orange', 'peach', 'pear', 'pineapple', 'strawberry','watermelon'] - -"""draw gallows""" -penup() -draw(50, 0, 90, 175, True) -draw(50, 175, 90, 95, True) -draw(-45, 175, 90, 15, True) - -goto(-100,-35) -word = choice(fruitword) -correct = [] -use = [] -wrong = 0 -finish = False -for i in word: - screen_write("_ ") -for ascii in range(ord('a'), ord('z')+1): - letter = chr(ascii) - onkey(functools.partial(guess, letter), letter) -listen() -done() \ No newline at end of file From 673c1fdbde4bdbae753faa0ecc3861e4c5f4f0ef Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 16:41:13 -0800 Subject: [PATCH 41/77] i sort it --- freegames/avoid.py | 1 + freegames/illusion.py | 3 ++- freegames/typing.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freegames/avoid.py b/freegames/avoid.py index ec96b5f0..4c33933a 100644 --- a/freegames/avoid.py +++ b/freegames/avoid.py @@ -9,6 +9,7 @@ from random import * from turtle import * + from freegames import vector north, south = vector(0, 4), vector(0, -4) diff --git a/freegames/illusion.py b/freegames/illusion.py index 1054015c..a102d5ac 100644 --- a/freegames/illusion.py +++ b/freegames/illusion.py @@ -6,8 +6,9 @@ 2. Change the number of rows and lines. """ -from turtle import * from itertools import cycle +from turtle import * + from freegames import line, square size = 25 diff --git a/freegames/typing.py b/freegames/typing.py index fe85d87f..cb40098c 100644 --- a/freegames/typing.py +++ b/freegames/typing.py @@ -8,9 +8,10 @@ 4. Make the letters more frequent as the score gets higher. """ -from turtle import * from random import choice, randrange from string import ascii_lowercase +from turtle import * + from freegames import vector targets = [] From 427f8a149125478b0847b9fd14607ab13203c19e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 16:42:28 -0800 Subject: [PATCH 42/77] doc8 fixes --- docs/hangman.rst | 6 ------ docs/typing.rst | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 docs/hangman.rst diff --git a/docs/hangman.rst b/docs/hangman.rst deleted file mode 100644 index b017049d..00000000 --- a/docs/hangman.rst +++ /dev/null @@ -1,6 +0,0 @@ -Hangman -==== - -Hangman, a letter guessing game. - -.. literalinclude:: ../freegames/hangman.py \ No newline at end of file diff --git a/docs/typing.rst b/docs/typing.rst index e7c5eb26..e7a3597e 100644 --- a/docs/typing.rst +++ b/docs/typing.rst @@ -1,5 +1,5 @@ Typing -===== +====== Typing, simple typing game. From 24d5e962b478d9ba4e5ac1bfd63dee6419c29009 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:01:08 -0800 Subject: [PATCH 43/77] Add tests for typing game --- tests/test_typing.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/test_typing.py diff --git a/tests/test_typing.py b/tests/test_typing.py new file mode 100644 index 00000000..23ba07e6 --- /dev/null +++ b/tests/test_typing.py @@ -0,0 +1,18 @@ +import random +import runpy +import sys + +import mockturtle + +sys.modules['turtle'] = sys.modules['mockturtle'] + + +def test_typing(): + random.seed(0) + mockturtle.events.clear() + mockturtle.events += [('timer',)] * 100 + mockturtle.events += [('key i',)] + mockturtle.events += [('timer',)] * 100 + mockturtle.events += [('key k',)] + mockturtle.events += [('timer', True)] * 1000 + runpy.run_module('freegames.typing') From f3953c712bcf9015368985c4ead04f5fae131dfe Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:01:25 -0800 Subject: [PATCH 44/77] Add tests for optical illusion --- tests/test_illusion.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_illusion.py diff --git a/tests/test_illusion.py b/tests/test_illusion.py new file mode 100644 index 00000000..6a0fad11 --- /dev/null +++ b/tests/test_illusion.py @@ -0,0 +1,14 @@ +import random +import runpy +import sys + +import mockturtle + +sys.modules['turtle'] = sys.modules['mockturtle'] + + +def test_illusion(): + random.seed(0) + mockturtle.events.clear() + mockturtle.events += [('timer', True)] * 10 + runpy.run_module('freegames.illusion') From 8ee9ad94b7f6c600bd2a1f8fab6483c93641dc9d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:01:56 -0800 Subject: [PATCH 45/77] Accept `align` param to turtle.write --- tests/mockturtle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mockturtle.py b/tests/mockturtle.py index 26e5d52b..8a26ac3c 100644 --- a/tests/mockturtle.py +++ b/tests/mockturtle.py @@ -24,7 +24,7 @@ def width(self, size): def color(self, fill, outline=None): pass - def write(self, text, font=None): + def write(self, text, font=None, align=None): pass def begin_fill(self): From b5f1268468526a3d14e96a878916dfa5b3629e1f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:15:02 -0800 Subject: [PATCH 46/77] Move exercises to comments --- freegames/rps.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freegames/rps.py b/freegames/rps.py index bb1dc650..24b32bfe 100644 --- a/freegames/rps.py +++ b/freegames/rps.py @@ -10,13 +10,12 @@ c. Paper covers rock. First to score 5 points wins the series! - -Exercises: - -1. Create a strategy to rotate through 'r', 'p', and 's' in a cycle. -2. Create a strategy to do the same as the last computer choice. -3. Create a new strategy of your own. """ +# Exercises: +# +# 1. Create a strategy to rotate through 'r', 'p', and 's' in a cycle. +# 2. Create a strategy to do the same as the last computer choice. +# 3. Create a new strategy of your own. import random From 6f2def0b2ae84dc69256297c3577d9d600eec227 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:15:17 -0800 Subject: [PATCH 47/77] Add tests for rock, paper, scissors --- tests/test_rps.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_rps.py diff --git a/tests/test_rps.py b/tests/test_rps.py new file mode 100644 index 00000000..8afece14 --- /dev/null +++ b/tests/test_rps.py @@ -0,0 +1,33 @@ +import random +import runpy +import unittest.mock as mock + + +def test_rps_same(): + random.seed(0) + mock_input = mock.Mock() + mock_input.side_effect = list('xrpsrpsrpsrpsrpsrpsrpsrps') + mocks = {'print': lambda *args: None, 'input': mock_input} + + with mock.patch.multiple('builtins', **mocks): + runpy.run_module('freegames.rps') + + +def test_rps_beat_last(): + random.seed(1) + mock_input = mock.Mock() + mock_input.side_effect = list('sprsprsprsprsprsprsprspr') + mocks = {'print': lambda *args: None, 'input': mock_input} + + with mock.patch.multiple('builtins', **mocks): + runpy.run_module('freegames.rps') + + +def test_rps_random(): + random.seed(2) + mock_input = mock.Mock() + mock_input.side_effect = list('rpsrpsrpsrpsrpsrpsrpsrps') + mocks = {'print': lambda *args: None, 'input': mock_input} + + with mock.patch.multiple('builtins', **mocks): + runpy.run_module('freegames.rps') From 0f9a3a21efcca9266858dee2b20ca4b7db4c53c3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:35:33 -0800 Subject: [PATCH 48/77] Add tests for "avoid" game --- tests/test_avoid.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/test_avoid.py diff --git a/tests/test_avoid.py b/tests/test_avoid.py new file mode 100644 index 00000000..d2dd6c54 --- /dev/null +++ b/tests/test_avoid.py @@ -0,0 +1,30 @@ +import random +import runpy +import sys + +import mockturtle + +sys.modules['turtle'] = sys.modules['mockturtle'] + + +def test_avoid_1(): + random.seed(0) + mockturtle.events.clear() + mockturtle.events += [('timer',)] * 10 + mockturtle.events += [('key Up',)] + mockturtle.events += [('timer',)] * 10 + mockturtle.events += [('key Down',)] + mockturtle.events += [('timer', True)] * 1000 + runpy.run_module('freegames.avoid') + + +def test_avoid_2(): + random.seed(0) + mockturtle.events.clear() + mockturtle.events += [ + ('timer', True), + ('key Up',), + ('timer', True), + ('key Down',), + ] * 1000 + runpy.run_module('freegames.avoid') From f3737e1e1bc0f3661e3f7e549ecf6a7b25d0abae Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:39:03 -0800 Subject: [PATCH 49/77] Skip testing for Python 3.6 and 3.7 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5d835318..8c0beb36 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py36,py37,py38,py39,py310 +envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py38,py39,py310 skip_missing_interpreters=True [testenv] From ec577c5a9bc874cc52324f4c518dcbde411c0ff6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 17:39:18 -0800 Subject: [PATCH 50/77] i blue it --- freegames/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freegames/utils.py b/freegames/utils.py index ce9a56e0..e535aeff 100644 --- a/freegames/utils.py +++ b/freegames/utils.py @@ -454,7 +454,7 @@ def __abs__(self): 5.0 """ - return (self.x ** 2 + self.y ** 2) ** 0.5 + return (self.x**2 + self.y**2) ** 0.5 def rotate(self, angle): """Rotate vector counter-clockwise by angle (in-place). From 80af1ecc2944778ca2141b6904471fd86fba44ec Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:31:32 -0800 Subject: [PATCH 51/77] Include Python 3.7 and 3.11 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8c0beb36..a669ad0a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py38,py39,py310 +envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py37,py38,py39,py310,py311 skip_missing_interpreters=True [testenv] From 8abccd0248b37365a89270572e9e926cfcce0ad2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:31:49 -0800 Subject: [PATCH 52/77] Change --report to --report-level for rstcheck --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a669ad0a..276e4d80 100644 --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ commands=isort --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/te deps=isort [testenv:rstcheck] -commands=rstcheck --report warning {toxinidir}/README.rst +commands=rstcheck --report-level warning {toxinidir}/README.rst deps=rstcheck [testenv:uploaddocs] From 874bfd90560817174e484a8b98d224d8cc038e45 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:32:06 -0800 Subject: [PATCH 53/77] Add 3.11 for Python testing --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 331eba3d..0f00038b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -36,7 +36,7 @@ jobs: max-parallel: 5 matrix: os: [ubuntu-latest] - python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] + python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 From 2f901de9d478e5bd3162f28a8038fa30c76638f0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:38:49 -0800 Subject: [PATCH 54/77] Re-arrange requirements and requirements-dev --- requirements-dev.txt | 14 ++++++++++++++ requirements.txt | 13 ------------- 2 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 requirements-dev.txt diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..7f3b5cfa --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,14 @@ +-r requirements.txt +blue +build +coverage +doc8 +flake8 +isort +ipython +pytest +pytest-cov +rstcheck +sphinx +tox +twine diff --git a/requirements.txt b/requirements.txt index d845935a..d6e1198b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1 @@ -e . -blue -coverage -doc8 -flake8 -isort -pylint -pytest -pytest-cov -rstcheck -sphinx -tox -twine -wheel From 0794ff927321e2e99aeddf3657b7094f48c6f077 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:39:06 -0800 Subject: [PATCH 55/77] Add release workflow --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..20753098 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: release + +on: + push: + tags: + - v* + +jobs: + + upload: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Create dist + run: python -m build + + - name: Upload with twine + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload dist/* From b5582e0390824da7e3dc10422aa62bf341c62dd6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:42:01 -0800 Subject: [PATCH 56/77] Bump copyright to 2023 --- LICENSE | 2 +- README.rst | 2 +- freegames/__init__.py | 2 +- freegames/__main__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 1db7e981..38cce0a0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017-2022 Grant Jenks +Copyright 2017-2023 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/README.rst b/README.rst index 32d87e5c..15dba8cf 100644 --- a/README.rst +++ b/README.rst @@ -334,7 +334,7 @@ References Free Python Games License ------------------------- -Copyright 2017-2022 Grant Jenks +Copyright 2017-2023 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/freegames/__init__.py b/freegames/__init__.py index 93a04f93..d5fb8677 100644 --- a/freegames/__init__.py +++ b/freegames/__init__.py @@ -66,4 +66,4 @@ __build__ = 0x020400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = '2017-2022, Grant Jenks' +__copyright__ = '2017-2023, Grant Jenks' diff --git a/freegames/__main__.py b/freegames/__main__.py index 3212a184..fee826a2 100644 --- a/freegames/__main__.py +++ b/freegames/__main__.py @@ -22,7 +22,7 @@ def game_file(name): parser = argparse.ArgumentParser( prog='freegames', description='Free Python Games', - epilog='Copyright 2022 Grant Jenks', + epilog='Copyright 2023 Grant Jenks', ) subparsers = parser.add_subparsers(dest='command', help='sub-command help') From f0a7d95ffab43afb3e3d54c2383262498c638bff Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:42:12 -0800 Subject: [PATCH 57/77] Bump version to 2.5.0 --- freegames/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freegames/__init__.py b/freegames/__init__.py index d5fb8677..48914613 100644 --- a/freegames/__init__.py +++ b/freegames/__init__.py @@ -62,8 +62,7 @@ __all__ = ['floor', 'line', 'path', 'square', 'vector'] __title__ = 'freegames' -__version__ = '2.4.0' -__build__ = 0x020400 +__version__ = '2.5.0' __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = '2017-2023, Grant Jenks' From 51d61c00dcaf8e6822df94a028fd355720c46df6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 19:49:54 -0800 Subject: [PATCH 58/77] Add docs for illusion, minesweeper, and RPS --- docs/illusion.rst | 6 ++++++ docs/minesweeper.rst | 6 ++++++ docs/rps.rst | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 docs/illusion.rst create mode 100644 docs/minesweeper.rst create mode 100644 docs/rps.rst diff --git a/docs/illusion.rst b/docs/illusion.rst new file mode 100644 index 00000000..6b750ab7 --- /dev/null +++ b/docs/illusion.rst @@ -0,0 +1,6 @@ +Illusion +======== + +An optical illusion. + +.. literalinclude:: ../freegames/illusion.py diff --git a/docs/minesweeper.rst b/docs/minesweeper.rst new file mode 100644 index 00000000..e8f26ee7 --- /dev/null +++ b/docs/minesweeper.rst @@ -0,0 +1,6 @@ +Minesweeper +=========== + +Minesweeper, a logic puzzle game to clear the hidden mines. + +.. literalinclude:: ../freegames/minesweeper.py diff --git a/docs/rps.rst b/docs/rps.rst new file mode 100644 index 00000000..6914660a --- /dev/null +++ b/docs/rps.rst @@ -0,0 +1,6 @@ +Rock, Paper, Scissors +===================== + +Rock, Paper, Scissors -- classic hand-to-hand combat game. + +.. literalinclude:: ../freegames/rps.py From a5695853570aea417804927b860961fb18020608 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:04:47 -0800 Subject: [PATCH 59/77] Update links and add illusion/typing screenshots --- README.rst | 94 +++++++++++++++++++++++--------------- docs/_static/illusion.png | Bin 0 -> 5304 bytes docs/_static/typing.png | Bin 0 -> 8466 bytes 3 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 docs/_static/illusion.png create mode 100644 docs/_static/typing.png diff --git a/README.rst b/README.rst index 15dba8cf..47a0b317 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ with a list of exercises to work through with students. Creativity and flexibility is important. There is no right or wrong way to implement a new feature or behavior! You never know which games students will engage with best. -.. _`Free Python Games`: http://www.grantjenks.com/docs/freegames/ +.. _`Free Python Games`: https://grantjenks.com/docs/freegames/ Testimonials @@ -78,7 +78,7 @@ Features - Tested using GitHub Actions .. image:: https://github.com/grantjenks/free-python-games/workflows/integration/badge.svg - :target: http://www.grantjenks.com/docs/freegames/ + :target: https://grantjenks.com/docs/freegames/ Quickstart @@ -134,10 +134,10 @@ Paint shape and click again to mark its end. Different shapes and colors can be selected using the keyboard. -.. image:: http://www.grantjenks.com/docs/freegames/_static/paint.gif +.. image:: https://grantjenks.com/docs/freegames/_static/paint.gif :alt: Paint Free Python Game -.. _`Paint`: http://www.grantjenks.com/docs/freegames/paint.html +.. _`Paint`: https://grantjenks.com/docs/freegames/paint.html Snake ..... @@ -146,10 +146,10 @@ Snake green food. Each time the food is consumed, the snake grows one segment longer. Avoid eating yourself or going out of bounds! -.. image:: http://www.grantjenks.com/docs/freegames/_static/snake.gif +.. image:: https://grantjenks.com/docs/freegames/_static/snake.gif :alt: Snake Free Python Game -.. _`Snake`: http://www.grantjenks.com/docs/freegames/snake.html +.. _`Snake`: https://grantjenks.com/docs/freegames/snake.html Pacman ...... @@ -157,10 +157,10 @@ Pacman `Pacman`_ -- classic arcade game. Use the arrow keys to navigate and eat all the white food. Watch out for red ghosts that roam the maze. -.. image:: http://www.grantjenks.com/docs/freegames/_static/pacman.gif +.. image:: https://grantjenks.com/docs/freegames/_static/pacman.gif :alt: Pacman Free Python Game -.. _`Pacman`: http://www.grantjenks.com/docs/freegames/pacman.html +.. _`Pacman`: https://grantjenks.com/docs/freegames/pacman.html Cannon ...... @@ -169,10 +169,10 @@ Cannon cannonball pops blue balloons in its path. Pop all the balloons before they can cross the screen. -.. image:: http://www.grantjenks.com/docs/freegames/_static/cannon.gif +.. image:: https://grantjenks.com/docs/freegames/_static/cannon.gif :alt: Cannon Free Python Game -.. _`Cannon`: http://www.grantjenks.com/docs/freegames/cannon.html +.. _`Cannon`: https://grantjenks.com/docs/freegames/cannon.html Connect ....... @@ -180,10 +180,10 @@ Connect `Connect`_ -- Connect 4 game. Click a row to drop a disc. The first player to connect four discs vertically, horizontally, or diagonally wins! -.. image:: http://www.grantjenks.com/docs/freegames/_static/connect.gif +.. image:: https://grantjenks.com/docs/freegames/_static/connect.gif :alt: Connect 4 Free Python Game -.. _`Connect`: http://www.grantjenks.com/docs/freegames/connect.html +.. _`Connect`: https://grantjenks.com/docs/freegames/connect.html Flappy ...... @@ -191,10 +191,10 @@ Flappy `Flappy`_ -- Flappy-bird inspired game. Click the screen to flap your wings. Watch out for black ravens as you fly across the screen. -.. image:: http://www.grantjenks.com/docs/freegames/_static/flappy.gif +.. image:: https://grantjenks.com/docs/freegames/_static/flappy.gif :alt: Flappy Bird Free Python Game -.. _`Flappy`: http://www.grantjenks.com/docs/freegames/flappy.html +.. _`Flappy`: https://grantjenks.com/docs/freegames/flappy.html Memory ...... @@ -202,10 +202,10 @@ Memory `Memory`_ -- puzzle game of number pairs. Click a tile to reveal a number. Match two numbers and the tiles will disappear to reveal an image. -.. image:: http://www.grantjenks.com/docs/freegames/_static/memory.gif +.. image:: https://grantjenks.com/docs/freegames/_static/memory.gif :alt: Memory Free Python Game -.. _`Memory`: http://www.grantjenks.com/docs/freegames/memory.html +.. _`Memory`: https://grantjenks.com/docs/freegames/memory.html Pong .... @@ -213,10 +213,10 @@ Pong `Pong`_ -- classic arcade game. Use the keyboard to move your paddle up and down. The first player to miss the ball loses. -.. image:: http://www.grantjenks.com/docs/freegames/_static/pong.gif +.. image:: https://grantjenks.com/docs/freegames/_static/pong.gif :alt: Pong Free Python Game -.. _`Pong`: http://www.grantjenks.com/docs/freegames/pong.html +.. _`Pong`: https://grantjenks.com/docs/freegames/pong.html Simon Says .......... @@ -225,10 +225,10 @@ Simon Says the pattern and then click the tiles in the same order. Each time you get the sequence right the pattern gets one step longer. -.. image:: http://www.grantjenks.com/docs/freegames/_static/simonsays.gif +.. image:: https://grantjenks.com/docs/freegames/_static/simonsays.gif :alt: Simon Says Free Python Game -.. _`Simon Says`: http://www.grantjenks.com/docs/freegames/simonsays.html +.. _`Simon Says`: https://grantjenks.com/docs/freegames/simonsays.html Tic Tac Toe ........... @@ -236,10 +236,10 @@ Tic Tac Toe `Tic Tac Toe`_ -- classic game. Click the screen to place an X or O. Connect three in a row and you win! -.. image:: http://www.grantjenks.com/docs/freegames/_static/tictactoe.gif +.. image:: https://grantjenks.com/docs/freegames/_static/tictactoe.gif :alt: Tic Tac Toe Free Python Game -.. _`Tic Tac Toe`: http://www.grantjenks.com/docs/freegames/tictactoe.html +.. _`Tic Tac Toe`: https://grantjenks.com/docs/freegames/tictactoe.html Tiles ..... @@ -248,10 +248,10 @@ Tiles the empty square to swap positions. Can you make the tiles count one to fifteen from left to right and bottom to top? -.. image:: http://www.grantjenks.com/docs/freegames/_static/tiles.gif +.. image:: https://grantjenks.com/docs/freegames/_static/tiles.gif :alt: Tiles Free Python Game -.. _`Tiles`: http://www.grantjenks.com/docs/freegames/tiles.html +.. _`Tiles`: https://grantjenks.com/docs/freegames/tiles.html Tron .... @@ -259,10 +259,10 @@ Tron `Tron`_ -- classic arcade game. Use the keyboard to change the direction of your Tron player. Avoid touching the line drawn by your opponent. -.. image:: http://www.grantjenks.com/docs/freegames/_static/tron.gif +.. image:: https://grantjenks.com/docs/freegames/_static/tron.gif :alt: Tron Free Python Game -.. _`Tron`: http://www.grantjenks.com/docs/freegames/tron.html +.. _`Tron`: https://grantjenks.com/docs/freegames/tron.html Life .... @@ -270,10 +270,10 @@ Life `Life`_ -- Conway's Game of Life. The classic, zero-player, cellular automation created in 1970 by John Conway. -.. image:: http://www.grantjenks.com/docs/freegames/_static/life.gif +.. image:: https://grantjenks.com/docs/freegames/_static/life.gif :alt: Game of Life Free Python Game -.. _`Life`: http://www.grantjenks.com/docs/freegames/life.html +.. _`Life`: https://grantjenks.com/docs/freegames/life.html Maze .... @@ -282,10 +282,10 @@ Maze of Code with 10 PRINT`_. Tap the screen to trace a path from one side to another. -.. image:: http://www.grantjenks.com/docs/freegames/_static/maze.gif +.. image:: https://grantjenks.com/docs/freegames/_static/maze.gif :alt: Maze Free Python Game -.. _`Maze`: http://www.grantjenks.com/docs/freegames/maze.html +.. _`Maze`: https://grantjenks.com/docs/freegames/maze.html .. _`A Universe in One Line of Code with 10 PRINT`: https://www.makeartwithpython.com/blog/10-print-in-python/ Fidget @@ -294,10 +294,30 @@ Fidget `Fidget`_ -- fidget spinner inspired animation. Click the screen to accelerate the fidget spinner. -.. image:: http://www.grantjenks.com/docs/freegames/_static/fidget.gif +.. image:: https://grantjenks.com/docs/freegames/_static/fidget.gif :alt: Fidget Spinner Free Python Game -.. _`Fidget`: http://www.grantjenks.com/docs/freegames/fidget.html +.. _`Fidget`: https://grantjenks.com/docs/freegames/fidget.html + +Illusion +........ + +`Illusion`_ -- an optical illusion. + +.. image:: https://grantjenks.com/docs/freegames/_static/illusion.png + :alt: Optical Illusion Free Python Game + +.. _`Illusion`: https://grantjenks.com/docs/freegames/illusion.html + +Typing +...... + +`Typing`_ -- practice typing while letters fall down the screen. + +.. image:: https://grantjenks.com/docs/freegames/_static/typing.png + :alt: Typing Free Python Game + +.. _`Typing`: https://grantjenks.com/docs/freegames/typing.html User Guide @@ -311,10 +331,10 @@ curriculum, API, and development. * `Free Python Games API Reference`_ * `Free Python Games Development`_ -.. _`Talk: Give the Gift of Python`: http://www.grantjenks.com/docs/freegames/give-gift-python.html -.. _`Free Python Games Curriculum`: http://www.grantjenks.com/docs/freegames/curriculum.html -.. _`Free Python Games API Reference`: http://www.grantjenks.com/docs/freegames/api.html -.. _`Free Python Games Development`: http://www.grantjenks.com/docs/freegames/development.html +.. _`Talk: Give the Gift of Python`: https://grantjenks.com/docs/freegames/give-gift-python.html +.. _`Free Python Games Curriculum`: https://grantjenks.com/docs/freegames/curriculum.html +.. _`Free Python Games API Reference`: https://grantjenks.com/docs/freegames/api.html +.. _`Free Python Games Development`: https://grantjenks.com/docs/freegames/development.html References @@ -325,7 +345,7 @@ References * `Free Python Games at GitHub`_ * `Free Python Games Issue Tracker`_ -.. _`Free Python Games Documentation`: http://www.grantjenks.com/docs/freegames/ +.. _`Free Python Games Documentation`: https://grantjenks.com/docs/freegames/ .. _`Free Python Games at PyPI`: https://pypi.python.org/pypi/freegames .. _`Free Python Games at GitHub`: https://github.com/grantjenks/free-python-games .. _`Free Python Games Issue Tracker`: https://github.com/grantjenks/free-python-games/issues diff --git a/docs/_static/illusion.png b/docs/_static/illusion.png new file mode 100644 index 0000000000000000000000000000000000000000..6ada11f754c74a82de52b7738b75dadf90bcfa4e GIT binary patch literal 5304 zcmeHKWl&sOwr*TPun?>h(lnmn8laJ&!7Vr;xVwZ#8VJE@T!Xs@g1aQRM+oi^+$BJO z<~29>-sH`DQ#DodYpP~f?X%Wd>s#O2``hQ7suQBDD19535*Gjf+?IJNp$Y(?u>%08 zW9Vqeh_$>m(%{)zii<1Dh>L@io$SplZA<}xry=o)SXyd*q`qqjw6wHY=wi{t9(N=> zqKU=AKp^#ZL@}abI2_c469d_PEuhf5&q*rYmid-qR3u7PoMPO)YxTwYS_YY8uUtZU!vPDxGgWOAfJEMmmD8(d zeO1(3eFnZ!nveM9x&-4>vzBPC1VRq(^=bk>W*$N-nE~skS*hy1BYrQ}GpL(n(T#8b zI^xt=hmsH!6Vq++qr<~^J^V?kO*!fgsIOAD8*dfDOvL!3aAYD+Z?XFcS-5hAUh+W7 z51;&1pTuUpE?)v&ts=0tNwX7zJ*dw4FegqvV1}tLI7EGw(kW6avtJOvD4829uey;# z@5PU`O2AItgbU45iBbRs$vGKke?5fDJ3c1k)MHsEaYW%Gs-#{C5Ode^(=iWY7SZ3i z^>!%Eal-1HKfQFm{PVcDq$07d?REImUiImob;;~bl3tSDv4Eu&@?Mu-*27eP#+u<^ zFcE{9sC0g?X1D!qetpIeY?a9#TrSha1FNeT$a%Y01q+yiPihDYS0W_3_|l-@tQm2( zsQ2!X7>=N2dxWwuy@vrSs-Z=a)$W9I7A?~g+X87FLgI4DFtywUM=RKjY7#IjPFL)E z6d8SC&r%uR9?E#)1T- zVr7f6HftV7#^8C#VJc-xToUX-w9eV*^t9ryuJwgMSM-MBGSxcSK%5{ZXI zcRFOF*}<`v5K!)B%#O{#i-lISqqBS^sH_Dw1Uv((Cf=1N8Hv9l{KohZcPFUCIr;#` z>Y!`!nZH|;zE0h%Wb?HhjZB^p+XlTP6?~P}6Jg-$8*sm8 zvqEk{g^r-CjNz)px?R`H2g3g`;_LweGrg2E)@ht_9DjVIxD|CV4)t62@`_wRf2Vdf zDk;$WzCUxB{~l-Dx7E#6Oy5J@91mEF>31J-!qotKP8oKuoex5xEa&tC`>2^^xORD5 z^Cq<$`&7FP#%^c0+F7!JkJ!-95IH=1V`J1wbL1bUTg%rXIRx`)vn;vh_6$!bMC|Mf^x&&2TKA&YUE&B+bNlgbm>lke+1Tdo^ho1W_o zTS}kGKZ+F3_je}M%CmAMHo)C|-DA)D+DCnDLEKs<@5UF3-(z)92fhy!>mcrc1@hsE zP*lJ_z@6Y4CRB>PNz|@RK+=hQuM@L%1(R5lN|oi+ZM&i_`;NVi6YCCLM6Sm!B>WnW z^9u25ai0#Ez>)^9m$ch;&V)>7Ht^L4r<>=DgJdh2^*cw=264&AHM0t7)tG<@uPIY+w@|4g>q8`^o!d*dxJ~MB&l?y@kDZ zqwDJP^@Qt^r+GemuE3)UvI|vkRJL-cBn71gvVl@rIZ_ciJe%Hd1vqU!D`}^bv1XTW za8+0e$?3Gk(t7=;yEz%%1d$n3KD@7@**Od~KU{Q1b()5p#%#xu$SCyb^exAf#ERtl z=aMBYQicKvhi z)X*Q&Q^_;r7Ub^9UJr(SQ$K9vs;2;QHlE!>=pf{tyfdiD6ekDiF?l6WL^W_8xoe1y z6_?_xL>0=9GOY%WhrKYDHnSf_MBPcLqA6hGqjP(aE~W1=XBbjOEA8}fp4ms%J8V=& zhuJo5h0k&*msLBiR5qfwXJG5r@q*G7T|mncgQTvZfuW?~$jV9|R>8V_w0r~iSI5ol zqLI)KVczM1O3O;t$#xvD;p1A0Z3pYbM@# zen{@kIQzT{V*5POC-kf+KS`hYQEN@R&->MYGqHmYtCG86V=O;(z>E#bn>sogKioNd z4g%;p664r5bdwvZ-QSE?Dhz}S&<)s^Ve3>l=MDE~$46?0>!>#+*EE`(H1XUSPh~q| zoNi92)TpSffiyOIr=2u@+Y&A(d)uZ$q*`JBx-U=?C~QMw)7;1e{W3R{pCZTSGXLaD3 zLeH*aQ~5T|tXkW+``#FA5SDlG-n$Dy)|PwibR;_ZD*v@G)+xg3Xe&@0E>2hTbZ2y` zOhDziHOvY&uoxYbLU{?qzgj(2f378k2#!#sUu}hwC z*;`499ajn${^}*LdECIYXs(!Unjz%Px-b)cz=@_;Id?VOg8wDY`eulA*7&gAntmIUW>sonC^x0fKD_CxhpxlLdD@I%`5x)q zP7B|K@S=Ag+76Z}fdZ8VVsXExRik_?$2QA0k%|t|c$##5_U+Dj?8weh#^>||Aub<{ ztC^ki=!pG@M{M1#7Cua{$!YD&g5eCH{$$&alk$Z&R@d+iD<2lm*;BIv-JCkXIrnDk z&4?}UGvPhhUQi5GPI}q{m~i@4+~J1y-pWYs$hQ~wTDiT#e|WU9m6t2^#~%Ff!C=?= zG$;?4+Xnb1-~!Onrx}ga&EF&Z3Wv+D&=Ge^hQqEf5U*{l(Ls*9fc~6ev)uzmjn>Kvs2CzuY6?pyd z7EwAyE#rvng4x&X$%UY7fedfHo(H;_@OHGz3B)ZXd3cskUJxPj|0C2?OU6t=0q_v% zV*}7pC;=Eq4+S}eQGkE@QYegoo4*wl04e|wWC=k36QhWs*NYay_ff-*?l-pLfi%gWBm4i?4*fj|%^6Egu-38_EJk#|C13l|p$0X8-_ zH#b%{E>?Rdb2bisettG~PBu!qOJ0 z8M22kA15#55B&eh{Ob5yrPi;?#~lBw^0&-CR;oLjI*HrcB3rr$|2nQe+y0*Xvk<~| zv-97S_$l)rQKZhoxDd9#&rBFsr~I4&d5rfgC6t~aE%G6|87S7s3*%3F;|n*OpjO>N z#)y^WpGhL^?c28n1qC4x$m;6q_4V~pBFSd}0E0_L;>k1U)XwaCg$_{?$xr_EfG;Rr z3z$)=d2&y2>^|I=rxX?IXN*+{#12_taM?v=;`HId;%dscaxAFt-#zJKGQq@zxBC*2 zv!l|m8=$X>@acK8TCW`}<{Y(maxqK+zog8-zEdSZSN(lVcgbI&+@Pck+OhG-U)>a; z-T1!|{3QD)uvSZ-b6)u56NwY=D_-~~?Ob&?BgGx(>)8$1CwRzw2IDSyV%x99Ah~YZ z{0lArf|01q?$?znf{SVwm9p#yTecUc?Xsc9dWe5zoyb1_FHA*(_ma-6aJehP~IyNJI;__?@Wy8i(G$}yhD#ef=Re+47qC}aPf#3fCxsv-j literal 0 HcmV?d00001 diff --git a/docs/_static/typing.png b/docs/_static/typing.png new file mode 100644 index 0000000000000000000000000000000000000000..cfb235896e0cd87cba4a777ba309fe453fd0829a GIT binary patch literal 8466 zcmd6L2UJsAw{8FhMG*l3DM||oXN%SCS0#0qUOvm+H{HdMLr|ZEK&+qoNE?)JNS{&MOlpx@<|!FUwQWAB9_@(j6hvED?VjHpzoWx83rw#_wRkr z=Biaca_2b66v{=lqN+eIQ-3;-FF=-Rl2(kXmf9y*?~xW~n1=gZbni;ErrRZE33K7^XWYnS=*qYz9$xV^ zzHe$9C8T_7{=^GhqT7HyQ8w%OSjne;sH!%-nbSecwHAY+kKa|%@6WWHY3Y0Tb>eJ` zXN$;6#$OjIJHol?_-$0KV#1A@U1(%)U5GfQNBBrBY5ir{eh;NUd=pSA%q=dXhNGf} zMZ`bby)hL4`Ki&ci;`2g8)O7_###wuhE zlr2;QmEmEN{^N@`6u$&hS#h2?7E*31A;w+Mc$K{5c+Roi55})-`#;P%-P(9k z-N4B95n;@1K$)9L;b8_9T1BMJXe|zP(ocg@CkM7myQpAH6dN@+PTD)TrNp_%X9bu@s)F!#=p%+DuY$bQym&YATCA)Z=c50lm zE?D^(rR8mK_|XVXM$KS+)MvSr5bSNaREo&U0>-EMg4b_L4p82^ulO1CgyQO(Yg3iNC^-JRG0PVI2EuMduJTV8|E$K`WRUt{9Fe5S?I z>b!pBb66jbS-kxO^Msf?xolhor}rc2dsZ9CQY?NVdb|9Udxp?nh~dj$WNfB|om&6suFQ>mihBfB@+Jte(!z+0Fn(9;4+xfJ4MEIP+3p~>FX5n!W zBVc-L_ymla^8?B!pIl$u?wp;>^rdORr1IP6p>7+j`j`qO(0_5^aML` zLX53m_J4l%ma2&>^lj*sCi}xvK^6d&)x`hUWh8!yi;^n72 zLVa_p2LdXIZmY~fflTR1ZGyI$cf3qejbWx2gkG6K%+^c?P54YYDu-=}ZzAt}yPITJ zIrt3wtZi`EFLB*-mUXsP+KP>XEgol@UM|hyIPZAfk$Iqxpj0JUF+Rvh=p7iTDkM-5 zbjnI98W&VNm)u(12EMy~pz~VuJR@s(>DIj~nROZOwb?RFL+L}ePbHmv!S2`gc&7cH zh-a~zvxj93thU|jsfVvryPxskOU9;-Q#&&=(`mCcBkQBCBVTyJI?s)Y?Z%|%v5mcW zH(w)PuwrMpIv%xfG{^QXT(*^RrUzV#c?SpgC(O`R|vvg0j`kM zlo1ke3>;{3ZCiT2l(tlStTWt>E++o3mM1NY@l~%e=1Ns*LsFmoC!*sE(NFY_>&@n! zSCv<5L$j&n=BdS+O3egCYej=GB24ey?;3qj3NMdJKeIW6`1<`do-r@GnKmx_JW@I& zUQFE5=-tlyNAIm8Hc@j4XW&{`Q|vgZC_y>@FU)gIyK|C`?a|`Q*JG}Kc)D(vUolbf ztYV?m@@{{$6UUTnvClAW`QCV8w6gMBpDPQ0o$Xa+8OySDkv*dXI#f2%~?FKW9=2o z9E+JPx1^+vG_EX73Te1A7fz6$(w&@WCk7VJ4kvkx;WQrii;a2i&k^I0ky}qOskekK zHdMYrzMXuyb!GY8r0POcpYXOR_l0WR8BjKi0Ap#ANc9X)8FV4R|h2q$c$1#3=O!$jz)(OMbD{ z&a%!{3=;V~9KRYgMQgECt?P}Sy))0-sSy^mxx1%ZZsajXk8lA6~*-`ougr%6LvZmEKZ@B+u z>0^UoNnf5*u2af+UWJ;?)Pt>W48(-a`PJ-CS;>l$NW;D1d18F*QtU<1<^}{(AdoO* zvRl}Z&322>xV>KTxlzO`X4)Pp>_4(;vuu`EB|qw0=P(mH8?>dg7`PaQI-i%7$q}fO zwU@XuZL&Dgncw-%g1rF}5VP&qC|XjY)0(injXWx5jBnQjjn09Blc_-zSwk1@8rZ(w zzyGA8Wbep6Ls3W6-qHOBjt)mS-K0USc^x(_uO3x?K8RdlRuVs(v#{{M;O4~R1!qCC z8$R^hYisNEQb&V(KyR2$EG!nA8yZB1EMIzq5E=P$4+KxpWwE@v(5e2}rWZ~49EO(5 z4wCuEVU`p9hGKVM|Ac^Mbgp7S*lFPVBhuO!Zlk3I5(MaDpd)0gprZgq1~eryw%@cG z*#!{Ak9l$sD9jFY?u&8ASz%)4>X?~^1r1iAUPC&Xn+U0d>smh z1O4^8?$*}M9taoDn$8AuVB)yzH4_gIh>4ff$l&_mCBS~4-3?<;V=Ya&yDm;5mR2rz ztVMjBTuF973O;fG>16F`$?45_z)!VmfX#`&}2Ax-1| zqvj#bKQ#?Jtlgn5PQa3$N`Kt-YuUrhUy2H%q&Gjb6F;~4$1JeVO4JIXzkQ}e-4U!o zawEGPOy>sB13^Z*$kKqH3qN}jEg{aFkU|83=!)R5%Qt)m=ZBHeV_udYmyJs1*K3jq ztp*owTiBZBJJieU7-6e)WlD-L9Ox`h7U&k>PVcQTpr&X!=ia**MNMWdliZB z@hZ{OD8u*;%L-t1+PPWPN-ts2lhdQ%Me#_)SzH3#FTK+?5bi7kjC$uDWOd%tR-tM%f!M+S0Q#PQ#CCrk?{|_6dy6eOL|eOV(E>NC`}j*wW#<-i^RTE*2z~ zDPS2X>1bZeWl8J?+;c{G0Rv%SM}_S98MwB17y{wsG){E%MX}+ySWVd!xE_n^wpPRY znLNTnsg<%P@MX39vTbM4xskRBLLwJY0VEnFnZQD^njj!LbOmz5-3*nKknj+5nx=Q| zyY{Euw;&kQ;RFiIf6^4-uH8miJ?4Tz4R&n6SUzZYXsd{ZdngMG>b06M>_~~%C7fVL z$eO=*nkFew0{g)Ruu|89)UiMvXVcn})ijBxjjacHs%gwe?#in8ic9UFE$3A>KPJZ>5*kln*LO`eWH2oCH%wQC}1Zk5Rb z%7PokmkwU5No$ZE?Z(>hxz%P~tR#jc=YPkKUOAAD70-S@9ThNq5>1DD(jenC(<9py z#)dLSFaXbXV+b{YX|G2hfzPwh@lMkv1wL%_NRyZR^D6TphL=0pdFa5V^HEs^jnOvXo)_0Z=3ooO(N z-tM7PT&)3cKfsx! zbT;{Xb>xTp0^v2qBNpm}-F>hmm1mH1o~J3sGg_9p<)mUHb&--(V>`T71= z<%fIgz+=wlC8jl=)2jf>9ND!>2!u0Le7Fh3MBUqtJtIckV$bS0YP7jZLJ4JMZ8qE+ z36|Yi=E5)4ipgLH_0JdsS1FqJEM8EhOgsQMKP8z6m)_#8e>dV0V5bAkG zhLB^r{I)JC;LgZsV0y!7x|Mq);;fl`&1^|~u@k^@bJ>fnL4g?-h0vDK!q-q4U24i- zUVDv(j14O+Q>j9tE2$E|ZG+Rvp$*bgso-hZ@m33Fyx8Oh?ixzV>7Ef(*=&6&G$eMH ziV^4VxeRQZdKq#b&!$eCrb)>vUkXjCJ;}{#YLaOMCpsFTz%Ki-sxW90 zq9GB}C=-D|oWqsE-G|%3SdQIyeCS+j2zISD$h1{dIUbC3V!(IFr3s9ca42O?#7F~& zTV6GCYwQ_6yvV5QR`q)OiEQxz~eJU6>vIbm=8#CQTX}I5aBfO^(3&jw9cYN*?M^F;=)6W{4Z!(3TW%Cihjsvd1?~3FRt{3%YZMX>oa2hu` zmLRl5A^~JW1K14!uKi47lw8G3`D$l1ph>6ID?fv@MP+~i8O+yn!hU8jk2O^fvsKSl zx^uWea6d#R`{`17>QyL&#)Sc=H@=7pu-p>s3WQzt_#PXJzzCVu-<^s=%i0eoO_YSv z&hn)7E2Z`C4e8apQK^*+VmbC}+_rKML9}@GZ^|cnLh!p~7qNv@8Uoe_%s*uBQo=kc z9;bsBE%w*OtLiVJiHb0fdvrMbo6o(XqaUZ*h5p6_6FhxDR1mP$Cz zrhvyqwp3*=PH!iXUM=~S&s_HT-8e-jpSx%}nnQOCLawD8E&^;4g5zhQ)I|492FYvy z>7x7J47D$(6?KbxH#jaS=@<3hGgF1s)f|Fg8##FKZLaQLh(M}&;riXH2sYrJNG@Y zT9OEXN-=vrBhhp5taw_d^#BirM|wYh;{{f^&UJt@l45n4MIh~N!dm-tWO20uJhlW` z=39mMuJEURbAOLOc`MWvu8BTH-hb@VmR#mCG6yCm0YV-L&xL_1&O!fEt;+;Ld)QQ6 z;M)wC1&I_WNX+I)v8n`Qy!or2C+fGZfQ=I4^L%T9MzX=oGW(w+!Tk}T9p(P5OREH+ zIE-P(l|o~h%o2u&Uhk`+PN+huOb2|FhI$h+^wnZ9E?Z*!SdW-lei$^&bjU}^ENi0K zD8dH&7Vqd_t}MVR*Yo)}HabMVL#yT$5Xd){h~T*75k;(tDg>Q-|5tUW0nopz!ze)N zS9M5|L7f{}=ZG`8$cop+-v?ulKLA35Q85*4VhDti;Ghf-Tsi{KxMzZrj6}eo)64}vr(fl~LM@H$X;2wqhSRlBPf$z_>- z){SzaX&r&gElod=cAGV;Fj&Ty3j}26p-leq`2RZQVFi5n^FE8On1JC`(i#3;VSVTz`*Y;{IiwC1 z+yB#g?El5tMf#}wN8$Ko)7Jlm;gD`?LsGv**XHAN3$btnf&>uXB-Vh?mJ^7EPYPL^W)@#LS=FL#-rbGqW`LIs3ZiojAij6^n92KqQnl@X^gz*k z-HlrV-DCCn^6>Kz(i`E9mY>?CH{Eakabj!qH9jS@CqaK4&;qq3nQ36+8`h}sP<4Ew z#A{-%fsS)|7~%;#&9994l}1`rKAyW<%NjURto(l;FRrk`|8#zcp?cxTCxakxl!=2n zVW$AQ-ZU44e^aEoT7A;r>bCRh_5`yk1 z;y&-Xju?XrL4V^#dM3D$9$?@i%rpcF%rn8`i=qXID?nlYoFtFyUZ33uSHE7SG6Gj? zL&R44A^!sR6g+C(G6AUx6V=V@4CoM}m_OJK{CS}UIeY{A{x_ll#BHm;v3DL}UZUe3 zfyjAr2*ZLAw;l5c)a>Lx(!@1ibv#P`5(FIrmE1zovVrjv_EY|(KXl-#+OXm)x9|TK DTPOj6 literal 0 HcmV?d00001 From ef7b1966fa73d8a3146c2f316323bfec9914527d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:08:20 -0800 Subject: [PATCH 60/77] Bump versions on actions --- .github/workflows/integration.yml | 8 ++++---- .github/workflows/release.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0f00038b..1b4ee3f5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -17,8 +17,8 @@ jobs: matrix: check: [bluecheck, doc8, docs, flake8, isortcheck, rstcheck] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: '3.10' - name: Setup tox @@ -38,8 +38,8 @@ jobs: os: [ubuntu-latest] python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Setup tox diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20753098..d902ad84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.11 From fc0f7d347dbe2d5823e40c683f733446d1f39cd6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:32:43 -0800 Subject: [PATCH 61/77] Change setup.py to pyproject.toml --- .gitignore | 2 +- pyproject.toml | 46 +++++++++++++ setup.py | 70 -------------------- {freegames => src/freegames}/__init__.py | 0 {freegames => src/freegames}/__main__.py | 0 {freegames => src/freegames}/ant.py | 0 {freegames => src/freegames}/avoid.py | 0 {freegames => src/freegames}/bagels.py | 0 {freegames => src/freegames}/bounce.py | 0 {freegames => src/freegames}/cannon.py | 0 {freegames => src/freegames}/car.gif | Bin {freegames => src/freegames}/connect.py | 0 {freegames => src/freegames}/crypto.py | 0 {freegames => src/freegames}/fidget.py | 0 {freegames => src/freegames}/flappy.py | 0 {freegames => src/freegames}/guess.py | 0 {freegames => src/freegames}/illusion.py | 0 {freegames => src/freegames}/life.py | 0 {freegames => src/freegames}/madlibs.py | 0 {freegames => src/freegames}/maze.py | 0 {freegames => src/freegames}/memory.py | 0 {freegames => src/freegames}/minesweeper.py | 0 {freegames => src/freegames}/pacman.py | 0 {freegames => src/freegames}/paint.py | 0 {freegames => src/freegames}/pong.py | 0 {freegames => src/freegames}/rps.py | 0 {freegames => src/freegames}/simonsays.py | 0 {freegames => src/freegames}/snake.py | 0 {freegames => src/freegames}/tictactoe.py | 0 {freegames => src/freegames}/tiles.py | 0 {freegames => src/freegames}/tron.py | 0 {freegames => src/freegames}/typing.py | 0 {freegames => src/freegames}/utils.py | 0 33 files changed, 47 insertions(+), 71 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py rename {freegames => src/freegames}/__init__.py (100%) rename {freegames => src/freegames}/__main__.py (100%) rename {freegames => src/freegames}/ant.py (100%) rename {freegames => src/freegames}/avoid.py (100%) rename {freegames => src/freegames}/bagels.py (100%) rename {freegames => src/freegames}/bounce.py (100%) rename {freegames => src/freegames}/cannon.py (100%) rename {freegames => src/freegames}/car.gif (100%) rename {freegames => src/freegames}/connect.py (100%) rename {freegames => src/freegames}/crypto.py (100%) rename {freegames => src/freegames}/fidget.py (100%) rename {freegames => src/freegames}/flappy.py (100%) rename {freegames => src/freegames}/guess.py (100%) rename {freegames => src/freegames}/illusion.py (100%) rename {freegames => src/freegames}/life.py (100%) rename {freegames => src/freegames}/madlibs.py (100%) rename {freegames => src/freegames}/maze.py (100%) rename {freegames => src/freegames}/memory.py (100%) rename {freegames => src/freegames}/minesweeper.py (100%) rename {freegames => src/freegames}/pacman.py (100%) rename {freegames => src/freegames}/paint.py (100%) rename {freegames => src/freegames}/pong.py (100%) rename {freegames => src/freegames}/rps.py (100%) rename {freegames => src/freegames}/simonsays.py (100%) rename {freegames => src/freegames}/snake.py (100%) rename {freegames => src/freegames}/tictactoe.py (100%) rename {freegames => src/freegames}/tiles.py (100%) rename {freegames => src/freegames}/tron.py (100%) rename {freegames => src/freegames}/typing.py (100%) rename {freegames => src/freegames}/utils.py (100%) diff --git a/.gitignore b/.gitignore index 4f3a167a..dc23d9ba 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,6 @@ /build/ /dist/ /docs/_build/ -/freegames.egg-info/ +*.egg-info/ .DS_Store diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..10672a84 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "freegames" +description = "Free Python Games" +authors = [ + {"name" = "Grant Jenks", "email" = "contact@grantjenks.com"}, +] +readme = "README.rst" +requires-python = ">=3.7" +keywords = ["free", "games"] +license = {"text" = "Apache 2.0"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Games/Entertainment", + "Topic :: Games/Entertainment :: Arcade", + "Topic :: Games/Entertainment :: Board Games", + "Topic :: Games/Entertainment :: Puzzle Games", + "Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games", + "Topic :: Games/Entertainment :: Simulation", + "Topic :: Games/Entertainment :: Turn Based Strategy", +] +dynamic = ["version"] + +[project.urls] +Documentation = "https://grantjenks.com/docs/freegames/" +Funding = "https://gum.co/freegames" +Source = "https://github.com/grantjenks/free-python-games" +Tracker = "https://github.com/grantjenks/django-codemirror6/issues" + +[tool.setuptools.dynamic] +version = {attr = "freegames.__version__"} + +[tool.setuptools.package-data] +freegames = ["*.gif"] + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 8d4e9e3f..00000000 --- a/setup.py +++ /dev/null @@ -1,70 +0,0 @@ -import io -import sys - -import setuptools as st -from setuptools.command.test import test as TestCommand - -import freegames - - -class Tox(TestCommand): - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - import tox - - errno = tox.cmdline(self.test_args) - sys.exit(errno) - - -with io.open('README.rst', encoding='UTF-8') as reader: - readme = reader.read() - - -st.setup( - name='freegames', - version=freegames.__version__, - description='Free Games', - long_description=readme, - long_description_content_type='text/x-rst', - author='Grant Jenks', - author_email='contact@grantjenks.com', - url='http://www.grantjenks.com/docs/freegames/', - license='Apache 2.0', - packages=['freegames'], - include_package_data=True, - tests_require=['tox'], - cmdclass={'test': Tox}, - install_requires=[], - project_urls={ - 'Documentation': 'http://www.grantjenks.com/docs/freegames/', - 'Funding': 'http://gum.co/freegames', - 'Source': 'https://github.com/grantjenks/free-python-games', - 'Tracker': 'https://github.com/grantjenks/free-python-games/issues', - }, - classifiers=( - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Education', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: Apache Software License', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Games/Entertainment', - 'Topic :: Games/Entertainment :: Arcade', - 'Topic :: Games/Entertainment :: Board Games', - 'Topic :: Games/Entertainment :: Puzzle Games', - 'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games', - 'Topic :: Games/Entertainment :: Simulation', - 'Topic :: Games/Entertainment :: Turn Based Strategy', - ), -) diff --git a/freegames/__init__.py b/src/freegames/__init__.py similarity index 100% rename from freegames/__init__.py rename to src/freegames/__init__.py diff --git a/freegames/__main__.py b/src/freegames/__main__.py similarity index 100% rename from freegames/__main__.py rename to src/freegames/__main__.py diff --git a/freegames/ant.py b/src/freegames/ant.py similarity index 100% rename from freegames/ant.py rename to src/freegames/ant.py diff --git a/freegames/avoid.py b/src/freegames/avoid.py similarity index 100% rename from freegames/avoid.py rename to src/freegames/avoid.py diff --git a/freegames/bagels.py b/src/freegames/bagels.py similarity index 100% rename from freegames/bagels.py rename to src/freegames/bagels.py diff --git a/freegames/bounce.py b/src/freegames/bounce.py similarity index 100% rename from freegames/bounce.py rename to src/freegames/bounce.py diff --git a/freegames/cannon.py b/src/freegames/cannon.py similarity index 100% rename from freegames/cannon.py rename to src/freegames/cannon.py diff --git a/freegames/car.gif b/src/freegames/car.gif similarity index 100% rename from freegames/car.gif rename to src/freegames/car.gif diff --git a/freegames/connect.py b/src/freegames/connect.py similarity index 100% rename from freegames/connect.py rename to src/freegames/connect.py diff --git a/freegames/crypto.py b/src/freegames/crypto.py similarity index 100% rename from freegames/crypto.py rename to src/freegames/crypto.py diff --git a/freegames/fidget.py b/src/freegames/fidget.py similarity index 100% rename from freegames/fidget.py rename to src/freegames/fidget.py diff --git a/freegames/flappy.py b/src/freegames/flappy.py similarity index 100% rename from freegames/flappy.py rename to src/freegames/flappy.py diff --git a/freegames/guess.py b/src/freegames/guess.py similarity index 100% rename from freegames/guess.py rename to src/freegames/guess.py diff --git a/freegames/illusion.py b/src/freegames/illusion.py similarity index 100% rename from freegames/illusion.py rename to src/freegames/illusion.py diff --git a/freegames/life.py b/src/freegames/life.py similarity index 100% rename from freegames/life.py rename to src/freegames/life.py diff --git a/freegames/madlibs.py b/src/freegames/madlibs.py similarity index 100% rename from freegames/madlibs.py rename to src/freegames/madlibs.py diff --git a/freegames/maze.py b/src/freegames/maze.py similarity index 100% rename from freegames/maze.py rename to src/freegames/maze.py diff --git a/freegames/memory.py b/src/freegames/memory.py similarity index 100% rename from freegames/memory.py rename to src/freegames/memory.py diff --git a/freegames/minesweeper.py b/src/freegames/minesweeper.py similarity index 100% rename from freegames/minesweeper.py rename to src/freegames/minesweeper.py diff --git a/freegames/pacman.py b/src/freegames/pacman.py similarity index 100% rename from freegames/pacman.py rename to src/freegames/pacman.py diff --git a/freegames/paint.py b/src/freegames/paint.py similarity index 100% rename from freegames/paint.py rename to src/freegames/paint.py diff --git a/freegames/pong.py b/src/freegames/pong.py similarity index 100% rename from freegames/pong.py rename to src/freegames/pong.py diff --git a/freegames/rps.py b/src/freegames/rps.py similarity index 100% rename from freegames/rps.py rename to src/freegames/rps.py diff --git a/freegames/simonsays.py b/src/freegames/simonsays.py similarity index 100% rename from freegames/simonsays.py rename to src/freegames/simonsays.py diff --git a/freegames/snake.py b/src/freegames/snake.py similarity index 100% rename from freegames/snake.py rename to src/freegames/snake.py diff --git a/freegames/tictactoe.py b/src/freegames/tictactoe.py similarity index 100% rename from freegames/tictactoe.py rename to src/freegames/tictactoe.py diff --git a/freegames/tiles.py b/src/freegames/tiles.py similarity index 100% rename from freegames/tiles.py rename to src/freegames/tiles.py diff --git a/freegames/tron.py b/src/freegames/tron.py similarity index 100% rename from freegames/tron.py rename to src/freegames/tron.py diff --git a/freegames/typing.py b/src/freegames/typing.py similarity index 100% rename from freegames/typing.py rename to src/freegames/typing.py diff --git a/freegames/utils.py b/src/freegames/utils.py similarity index 100% rename from freegames/utils.py rename to src/freegames/utils.py From 888cf20f4fc633484acf969165e8a990ffe614ed Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:41:06 -0800 Subject: [PATCH 62/77] Update all literal includes --- docs/ant.rst | 2 +- docs/avoid.rst | 2 +- docs/bagels.rst | 2 +- docs/bounce.rst | 2 +- docs/cannon.rst | 2 +- docs/connect.rst | 2 +- docs/crypto.rst | 2 +- docs/fidget.rst | 2 +- docs/flappy.rst | 2 +- docs/guess.rst | 2 +- docs/illusion.rst | 2 +- docs/life.rst | 2 +- docs/madlibs.rst | 2 +- docs/maze.rst | 2 +- docs/memory.rst | 2 +- docs/minesweeper.rst | 2 +- docs/pacman.rst | 2 +- docs/paint.rst | 2 +- docs/pong.rst | 2 +- docs/rps.rst | 2 +- docs/simonsays.rst | 2 +- docs/snake.rst | 2 +- docs/tictactoe.rst | 2 +- docs/tiles.rst | 2 +- docs/tron.rst | 2 +- docs/typing.rst | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/ant.rst b/docs/ant.rst index 2bf4fe12..43beaf91 100644 --- a/docs/ant.rst +++ b/docs/ant.rst @@ -3,4 +3,4 @@ Ant Ant, simple animation demo. -.. literalinclude:: ../freegames/ant.py +.. literalinclude:: ../src/freegames/ant.py diff --git a/docs/avoid.rst b/docs/avoid.rst index de259a1d..274f4538 100644 --- a/docs/avoid.rst +++ b/docs/avoid.rst @@ -3,4 +3,4 @@ Avoid Avoid, classic arcade game. -.. literalinclude:: ../freegames/avoid.py +.. literalinclude:: ../src/freegames/avoid.py diff --git a/docs/bagels.rst b/docs/bagels.rst index 705413c4..154f58fa 100644 --- a/docs/bagels.rst +++ b/docs/bagels.rst @@ -3,4 +3,4 @@ Bagels Bagels, a number puzzle game. -.. literalinclude:: ../freegames/bagels.py +.. literalinclude:: ../src/freegames/bagels.py diff --git a/docs/bounce.rst b/docs/bounce.rst index 252f265d..f557df66 100644 --- a/docs/bounce.rst +++ b/docs/bounce.rst @@ -3,4 +3,4 @@ Bounce Bounce, a simple animation demo. -.. literalinclude:: ../freegames/bounce.py +.. literalinclude:: ../src/freegames/bounce.py diff --git a/docs/cannon.rst b/docs/cannon.rst index 66575d00..77f66a2d 100644 --- a/docs/cannon.rst +++ b/docs/cannon.rst @@ -3,4 +3,4 @@ Cannon Cannon, hitting targets with projectiles. -.. literalinclude:: ../freegames/cannon.py +.. literalinclude:: ../src/freegames/cannon.py diff --git a/docs/connect.rst b/docs/connect.rst index d1684742..3a346277 100644 --- a/docs/connect.rst +++ b/docs/connect.rst @@ -3,4 +3,4 @@ Connect Four Connect Four, two-player connection game. -.. literalinclude:: ../freegames/connect.py +.. literalinclude:: ../src/freegames/connect.py diff --git a/docs/crypto.rst b/docs/crypto.rst index f628f2f4..70cc34c7 100644 --- a/docs/crypto.rst +++ b/docs/crypto.rst @@ -3,4 +3,4 @@ Crypto Crypto: tool for encrypting and decrypting messages. -.. literalinclude:: ../freegames/crypto.py +.. literalinclude:: ../src/freegames/crypto.py diff --git a/docs/fidget.rst b/docs/fidget.rst index 44f8e3ea..1e5a7348 100644 --- a/docs/fidget.rst +++ b/docs/fidget.rst @@ -3,4 +3,4 @@ Fidget Fidget, inspired by fidget spinners. -.. literalinclude:: ../freegames/fidget.py +.. literalinclude:: ../src/freegames/fidget.py diff --git a/docs/flappy.rst b/docs/flappy.rst index 4d46b070..eaead5fc 100644 --- a/docs/flappy.rst +++ b/docs/flappy.rst @@ -3,4 +3,4 @@ Flappy Flappy, game inspired by Flappy Bird. -.. literalinclude:: ../freegames/flappy.py +.. literalinclude:: ../src/freegames/flappy.py diff --git a/docs/guess.rst b/docs/guess.rst index 885eb2ab..f158f8d5 100644 --- a/docs/guess.rst +++ b/docs/guess.rst @@ -3,4 +3,4 @@ Guess Guess a number within a range. -.. literalinclude:: ../freegames/guess.py +.. literalinclude:: ../src/freegames/guess.py diff --git a/docs/illusion.rst b/docs/illusion.rst index 6b750ab7..6bb58eda 100644 --- a/docs/illusion.rst +++ b/docs/illusion.rst @@ -3,4 +3,4 @@ Illusion An optical illusion. -.. literalinclude:: ../freegames/illusion.py +.. literalinclude:: ../src/freegames/illusion.py diff --git a/docs/life.rst b/docs/life.rst index 1db7e63f..c4e7ad7a 100644 --- a/docs/life.rst +++ b/docs/life.rst @@ -3,4 +3,4 @@ Life Game of Life simulation. -.. literalinclude:: ../freegames/life.py +.. literalinclude:: ../src/freegames/life.py diff --git a/docs/madlibs.rst b/docs/madlibs.rst index 359ffbc5..f10ea4a9 100644 --- a/docs/madlibs.rst +++ b/docs/madlibs.rst @@ -3,4 +3,4 @@ Mad Libs Create a funny story from a not-so funny story -.. literalinclude:: ../freegames/madlibs.py +.. literalinclude:: ../src/freegames/madlibs.py diff --git a/docs/maze.rst b/docs/maze.rst index 9d9bfb7e..5c191e2b 100644 --- a/docs/maze.rst +++ b/docs/maze.rst @@ -3,4 +3,4 @@ Maze Maze, move from one side to another. -.. literalinclude:: ../freegames/maze.py +.. literalinclude:: ../src/freegames/maze.py diff --git a/docs/memory.rst b/docs/memory.rst index 31b70ec5..fb9f1fb3 100644 --- a/docs/memory.rst +++ b/docs/memory.rst @@ -3,4 +3,4 @@ Memory Memory, puzzle game of number pairs. -.. literalinclude:: ../freegames/memory.py +.. literalinclude:: ../src/freegames/memory.py diff --git a/docs/minesweeper.rst b/docs/minesweeper.rst index e8f26ee7..ecb9c182 100644 --- a/docs/minesweeper.rst +++ b/docs/minesweeper.rst @@ -3,4 +3,4 @@ Minesweeper Minesweeper, a logic puzzle game to clear the hidden mines. -.. literalinclude:: ../freegames/minesweeper.py +.. literalinclude:: ../src/freegames/minesweeper.py diff --git a/docs/pacman.rst b/docs/pacman.rst index 6200a566..023ee5bc 100644 --- a/docs/pacman.rst +++ b/docs/pacman.rst @@ -3,4 +3,4 @@ Pacman Pacman, classic arcade game. -.. literalinclude:: ../freegames/pacman.py +.. literalinclude:: ../src/freegames/pacman.py diff --git a/docs/paint.rst b/docs/paint.rst index 8a029bc6..11f39265 100644 --- a/docs/paint.rst +++ b/docs/paint.rst @@ -3,4 +3,4 @@ Paint Paint, for drawing shapes. -.. literalinclude:: ../freegames/paint.py +.. literalinclude:: ../src/freegames/paint.py diff --git a/docs/pong.rst b/docs/pong.rst index dfb740c2..03ba6169 100644 --- a/docs/pong.rst +++ b/docs/pong.rst @@ -3,4 +3,4 @@ Pong Pong, classic arcade game. -.. literalinclude:: ../freegames/pong.py +.. literalinclude:: ../src/freegames/pong.py diff --git a/docs/rps.rst b/docs/rps.rst index 6914660a..57180fe2 100644 --- a/docs/rps.rst +++ b/docs/rps.rst @@ -3,4 +3,4 @@ Rock, Paper, Scissors Rock, Paper, Scissors -- classic hand-to-hand combat game. -.. literalinclude:: ../freegames/rps.py +.. literalinclude:: ../src/freegames/rps.py diff --git a/docs/simonsays.rst b/docs/simonsays.rst index 90a43a28..ceeced18 100644 --- a/docs/simonsays.rst +++ b/docs/simonsays.rst @@ -3,4 +3,4 @@ Simon Says A game of watching and recalling patterns. -.. literalinclude:: ../freegames/simonsays.py +.. literalinclude:: ../src/freegames/simonsays.py diff --git a/docs/snake.rst b/docs/snake.rst index 2dab1b38..a2e62a73 100644 --- a/docs/snake.rst +++ b/docs/snake.rst @@ -3,4 +3,4 @@ Snake Snake, classic arcade game. -.. literalinclude:: ../freegames/snake.py +.. literalinclude:: ../src/freegames/snake.py diff --git a/docs/tictactoe.rst b/docs/tictactoe.rst index c12d9ccd..c4f182e4 100644 --- a/docs/tictactoe.rst +++ b/docs/tictactoe.rst @@ -3,4 +3,4 @@ Tic Tac Toe A paper-and-pencil game for two players. -.. literalinclude:: ../freegames/tictactoe.py +.. literalinclude:: ../src/freegames/tictactoe.py diff --git a/docs/tiles.rst b/docs/tiles.rst index d666b319..b474a3c8 100644 --- a/docs/tiles.rst +++ b/docs/tiles.rst @@ -3,4 +3,4 @@ Tiles Tiles, number swapping game. -.. literalinclude:: ../freegames/tiles.py +.. literalinclude:: ../src/freegames/tiles.py diff --git a/docs/tron.rst b/docs/tron.rst index c584e4b1..4f7daef5 100644 --- a/docs/tron.rst +++ b/docs/tron.rst @@ -3,4 +3,4 @@ Tron Tron, classic arcade game. -.. literalinclude:: ../freegames/tron.py +.. literalinclude:: ../src/freegames/tron.py diff --git a/docs/typing.rst b/docs/typing.rst index e7a3597e..86062c5f 100644 --- a/docs/typing.rst +++ b/docs/typing.rst @@ -3,4 +3,4 @@ Typing Typing, simple typing game. -.. literalinclude:: ../freegames/typing.py +.. literalinclude:: ../src/freegames/typing.py From 2a2956a15cf364ed09d34d00e9aee89ac844c255 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:45:03 -0800 Subject: [PATCH 63/77] Add missing docs to index --- docs/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 0af26523..eeef7451 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ guess snake crypto + illusion paint maze flappy @@ -22,9 +23,13 @@ pong life tron + typing tiles connect memory + minesweeper + avoid pacman fidget madlibs + rps From 406ba5ddcc57396e28ca59d76ef6c759a29a0d94 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:45:36 -0800 Subject: [PATCH 64/77] Set language to 'en' --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0c339e44..fc41c1e0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,7 +62,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From d38b373b7e31e3cf5a59d5da535858407ad85690 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 20:51:12 -0800 Subject: [PATCH 65/77] Update src paths --- tox.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 276e4d80..3e28d2a0 100644 --- a/tox.ini +++ b/tox.ini @@ -12,11 +12,11 @@ setenv= PYTHONPATH={toxinidir}:{toxinidir}/tests [testenv:blue] -commands=blue {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +commands=blue {toxinidir}/src/freegames {toxinidir}/tests deps=blue [testenv:bluecheck] -commands=blue --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +commands=blue --check {toxinidir}/src/freegames {toxinidir}/tests deps=blue [testenv:doc8] @@ -31,15 +31,15 @@ deps= sphinx [testenv:flake8] -commands=flake8 {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +commands=flake8 {toxinidir}/src/freegames {toxinidir}/tests deps=flake8 [testenv:isort] -commands=isort {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +commands=isort {toxinidir}/src/freegames {toxinidir}/tests deps=isort [testenv:isortcheck] -commands=isort --check {toxinidir}/setup.py {toxinidir}/freegames {toxinidir}/tests +commands=isort --check {toxinidir}/src/freegames {toxinidir}/tests deps=isort [testenv:rstcheck] @@ -69,7 +69,7 @@ addopts= --doctest-glob="*.rst" --doctest-modules --import-mode append -testpaths=docs freegames tests README.rst +testpaths=docs src/freegames tests README.rst [doc8] # ignore=D000 From 1ea58b9e35759ab0a58520a556c2fa61ac63a201 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 21:02:21 -0800 Subject: [PATCH 66/77] Bump version to 2.5.1 --- src/freegames/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freegames/__init__.py b/src/freegames/__init__.py index 48914613..8f0b20aa 100644 --- a/src/freegames/__init__.py +++ b/src/freegames/__init__.py @@ -62,7 +62,7 @@ __all__ = ['floor', 'line', 'path', 'square', 'vector'] __title__ = 'freegames' -__version__ = '2.5.0' +__version__ = '2.5.1' __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = '2017-2023, Grant Jenks' From 56519cf8dbff6c35992f68a61b7b4cb6c0e03510 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 21:32:46 -0800 Subject: [PATCH 67/77] Update rsync command --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3e28d2a0..257dca07 100644 --- a/tox.ini +++ b/tox.ini @@ -50,8 +50,9 @@ deps=rstcheck allowlist_externals=rsync changedir=docs commands= - rsync -azP --stats --delete _build/html/ \ - grantjenks.com:/srv/www/www.grantjenks.com/public/docs/freegames/ + rsync --rsync-path 'sudo -u herokuish rsync' -azP --stats --delete \ + _build/html/ \ + grantjenks:/srv/www/grantjenks.com/public/docs/freegames/ [isort] multi_line_output = 3 From c9b3d3b34343e5fb35f0008f04b0aef829729d88 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 22:00:43 -0800 Subject: [PATCH 68/77] Use gifs --- README.rst | 4 ++-- docs/_static/illusion.gif | Bin 0 -> 4590 bytes docs/_static/illusion.png | Bin 5304 -> 0 bytes docs/_static/typing.gif | Bin 0 -> 65734 bytes docs/_static/typing.png | Bin 8466 -> 0 bytes 5 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 docs/_static/illusion.gif delete mode 100644 docs/_static/illusion.png create mode 100644 docs/_static/typing.gif delete mode 100644 docs/_static/typing.png diff --git a/README.rst b/README.rst index 47a0b317..f4e6ccea 100644 --- a/README.rst +++ b/README.rst @@ -304,7 +304,7 @@ Illusion `Illusion`_ -- an optical illusion. -.. image:: https://grantjenks.com/docs/freegames/_static/illusion.png +.. image:: https://grantjenks.com/docs/freegames/_static/illusion.gif :alt: Optical Illusion Free Python Game .. _`Illusion`: https://grantjenks.com/docs/freegames/illusion.html @@ -314,7 +314,7 @@ Typing `Typing`_ -- practice typing while letters fall down the screen. -.. image:: https://grantjenks.com/docs/freegames/_static/typing.png +.. image:: https://grantjenks.com/docs/freegames/_static/typing.gif :alt: Typing Free Python Game .. _`Typing`: https://grantjenks.com/docs/freegames/typing.html diff --git a/docs/_static/illusion.gif b/docs/_static/illusion.gif new file mode 100644 index 0000000000000000000000000000000000000000..edc3f4943c69d076d46d82df4b5d024481f3e600 GIT binary patch literal 4590 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 diff --git a/docs/_static/typing.gif b/docs/_static/typing.gif new file mode 100644 index 0000000000000000000000000000000000000000..86a4e389780552621d2b17cc49d4c124e14a5b79 GIT binary patch literal 65734 zcmeFZX*`u}+ctijYql&b^R%!Gm83$Fq*~^=mPjfUk|~u8B}r?s%+o@M%=46F9vYS* zGa)6VGLuwDgC_rzy6*eB@8^1k_rvr1z5n-j`Op_1&K&!3?EAKF`?l?R2D+*{Y?u%e zdJI;72m}HzFR!SmsI;`SoSdAJlG3hSyR@{lOiWD7&CQP*VC*=H_Fp3y^{Q>Ni&ZCV*LCMY-zBi$t|$yS2NhrS;}}$aw-(Vw3{}RP#qly^OcqcsNtx z-JOaD0=5m$OP;^~a>w`Kd)}P@(y=(<8yFNE5*ik+FA7jPJRT8o@d?)xleEN+iEDFQ zyOEWhlbdJe&aFj4X6BdPE-R0S1>C{{?zbxL-n-ATCmcJ5U^KL~cXa9radGg7@!sir z{ABQk0I#TaQ_<6r(Xn7IrT|G~5)8MAa$+&;g5ygcy~Wv@n|a%X&LL2BKIM9))=BI{ z0eKpWQy2lb7}oQ4rI`=H45qqxV{D{bqR8xlN*6}W+G1rNT%uCsSqRRJCp)ZKY#RM` zAdP#5+hgOgD-pZ>@!bOkz*v$JBzG)H_7%W#PVreAR?10W&mlJVI#m`8ZYM?hiWyx9 zN1;XJWn^g-&7o@76xVjad?UXauMa$<2wFoP`?)W##lFXfK~j%6iO1FB*%^C}2^m#& zB|pad%I;Fu0hpL=||{ZFpMHcSumk zkdLD69C7dGY&J0@V}cAp0x`v6ks26^VG}ENv|<23O(R}+3gmVM@||ZyQ@Q(A+ABBK z_P?q~A6msc**?IU(%@~Gu_GjcLQN>1ntAi+%7vBSq_LRc;1Et$3 zI=6T)PKH}@?M+y49_09}XvQl`YiKJBSlrwZThLYJxaIzx!(B>7y6)X!FT&l8BLpBP zKDGDZJUQA{_=;-H_Z1uIz~`BEuR)N^EHTHh3pHSRb*V51Vb3vGlHxtL{qO@_K2UZt z_M=OM)BU*3;Ca<@k{oRfmY9pB?)+|F_D`-fh=tv{9q4xRh)@u|bDAD@OVzW?!gBxvi; zFB1vpelE}C-TL`;Ze?g2_ulIF?0s57o9}NmVjq3|$!QsYge2c-Rm#Ox7xq8%b?JM=M7;{X|P#;!+YML8UKUB-~uaY~u(F@0Ep zw(h+7wA@NFRA<$2eMs0LJtoW8v4=O#xD>GmGVJveSxU#55`PK8^{Cu~C;U@$u-MqEPP5tmmSP0JOr;6^yg^!3zae z{^pPFh1w+GGWB-4mLUWxSc3d=OB{kfSft@lstadhF*+qzX5NtM;OBR1+AQwK)0i~l zd_Vq@^fzCf4+>hHX%wjG)e+Y_R32w2^%Qab1$ts83sJDNN_jQ)uibHfd;yxD2Sh!U3^Pco7zE*kCvSq}IzOyXj(Oaq~2VSv4m+y}1 zl#TX7qdqCoEAz2>CbI|fcH+P)HsD;v20LMF zumM`PiVY~tf58UR4qqYDyuRWKoo~JG!`MLn0cXJkj1A7#zdE68%6V%Q8ywf4zVVqx z_w#2sT)7gkiVdth4n;)A#9liTfACsj(hcu~;Nc@J08DE@dc zL*@OdTBNW8>a!aP4g`-XH263(vlT9yD;{}~v_1Qv>qzp2Q~B?oJ&in-m-^P>!mDQ? z!^8IizUsXluJ`&d`=Yb@)~ow8B)hnAP3dS;Fz=@0T{XAITba^Esm8Tslb!M0HV=}G zrKT@y)36z#AIok@_c5XQxy+X`s#KhfZbliA@H4sT3I zIGgB{oqS>TbzXX)+-!b!yz^|qT>k30Y^n89e$~=T!wq{S_ zu9fgQSM)BfdXEgFs6vef_O9f~x^M4FKF-#kE72ONFS);iZ8*E3Z+-E6BVotfd=pVm zexaG~kc;B2#j%k27*Xk=^U-}Fv!t6PzQt0d!coinJ8Y&}m9B4z4G(YMkZC;IR<&^X zP}{_ax=_P{&o`y6iH_Oly%C{WAA-Nh?~dWMd@;t<&)5Gl2%|}1p;=f!e&UM;RnX%LF za|75VNk@oSGAR&FzjsGSm;?+;0g81Aak<`0l@BP4KsZ4hXhdm$I}Nybfh%Q3>RFd5 z8l~-O{d$IKt`Ibxce?!>Z~TWo2nUo!S`de<0Dy84)CN9rAwWasasmp1;JBBykYm9D z(`Dz##aL0=iCF}&q{x4J$8{P)O&JNJ*0Ri62l6Y+kpE+rq2^=VWMvfu(d#;XWf`~w zNfSo`vgHO4S?;rMHOt)gPk}B8oIZcapAi6eAlhdHw9&31%+)NTLxLNV%YJbwscGrf zYh6iHMnPdwq1b7-CCM+YsH_T%Ty03otM9WKZ1Yz8k;r?Eon77fgwv-HH+mjF>G{=+ zbPSG+W~}xiy>(-=Z{oPJ;a21wm>T`nip>286hD`FGm*@xB?_UQWPfNSK1Ac#1t|G~ zoDdDGsc^@+?e;iH?>oZ|_xg5r`_{7DV>HjQ93y3XCfbrUn$vwlPu1A6*B%(HBva0N z1K|osgF^^syrgr5Q_~J4N!8VyRd6tn^O^OL`45GMJW1Dl#n|`xAvgqh_$Xw0@pF3` z0^|CjYKsB|2(jvV1JhsJF9yQovA1BPsQ_q@-PpibYbrP>t5m&dqB9s&ij8$BPCZP7 z^ZPz!1@g`gdV-IJtn@(|XQ}(6Xu_FpD74N$7HV#5!VEXYa$#BewI3$C z(~MSZZN`?~JpGFfq^Ts`dV8?^4Gl+XDfNc~JK!Tg=7#zx5H^HO!a_I&hQ-G_eL=MJ z!`To{7`Os(gTHOWX{BbG5=K&70pQr|7GDWS`Kr+NU!uDj3IOup*r{F@ z)gjr#92cbR`AZ>Sgs!0XMI~G+#UQli8YE+`o77O95-ER?W&V>6#By$VI|K3c#SoS8 zgN>eMc~&4uM(2Ft`Pf|cP!SHV^M!XR?4;xC zyGA>yo~&ij4%8vsaFqN55fW!3{#KJ~)0jFzQI%)Ei^&6l4KWVfKDzYtEkVGrIb9sd zRA>t#)k%Obl?BC)097^Z>;k5AvjiiFU_qTcF}!cF}w6g#Wb z_<4-{P1EOyGmkADj@e_m$W#_Yw9NI~AhINtU3Su+hZnj1OmAnqlaW&Yeo>52^#*Zk zYY45l+xxQ&$x^1b=&D&vFT`U>qs#jWrs6{M{DH}Mq=aTBkoVO0nq;bKQji-(+Aqn7 z;o!z%WoWmzR7=R+uWw3Qw|twc7(Bc3?QP9;$+vfRKEM0+o`v1|eZE=n-1mjHgla`@ zu?;JXHZA7dcV0R6jG3kVZ7#*xPj_W(!zEu>X@2-Q^dYLRH0eE~f4J}};}P}SNec{@ z5Jfh{>|%48^2Oub+{cGqQ@(|Q;A0#LY%x2nc{}nbD9lYdXE-M+ip65%nWqcfv94EY zYvA;r_a_rFaC-WUJW2Bmt2P8Omrogvh!-=dVX5bkK4 zB{JC7qEfdI;c6a)B9Jw>-mQ!Vq-D^K5PE4>a;76o*@7)lGzfxtwy&^(z3Qdd(;3su zJcD3PjdXxSb*VGI8#|!#Q;luy`6BNyH*9qek$d5IEx}2Pe-P1DY5ncB}tqmJ}d9tbEu3R5Q z{9>H#8g@79;n+;4cI|MG2VWM_fOS97DYjr?WvU7KF? z8I%{=QmZ_xb_g+zrdn$|#8JILdr?yUVUF701`ZjT%nkUYD?ix(pt{|X5N8!<*m;CA zDb#8%Q*i6Y!8`?xgoDH;p|+aW%?Bz4EpBG+=udC4=(E_wHfba}Jg;lM@VMyMcSHUO zz^Nu5Pmv;R;(yn6P<<+cTJ`k+uIB)ng1Ojr+uj`aIP-y)lx<4*sj%&OZ(mkBo)kS) z_t5Iz>o=#qp|^5bxO#tXYu$5gerQ6N!NImsmj~GI2_qRuYmJf3$Q2s6+-7Lgayr(# zHZEu%b%Q$hpgKQXN(VmC^tn4-HsJURs1G8KB(}R^g#H$SAn<=}BCM zt6*%>jpURQBr>EEmCju^j z=!mwy{x-s;ONi`8&z_f>{VIUuq2cM7Xt)9%B#^m>51PIHNaq45xC(NSL1D#}rz0aU zzaV&I=%{L|7UJ}iwlhtodVqckFYN5&4mJ9riHNgWpiu%xxODc6RePCHF2hX8%F&?Q z{u;)cWHnEDmwU?AqXcZc z2(FKtka_@M6Cz0v>uWyj;4hrqNnWh|s35MQd>|#95f^b3nX)NPnP;U} zE5u4w)IbFOuR`EXOp={ckHEuCL_pI3S7fAY!frYN+8Hb{mI5T2A7LVIasYnZ`#WVA z@SNM=*{-ydD!MDedP?SW-rI}=P)wr7Iwx^u%@|@}+n05xWw+oO(dSbBC*<2qX1Gd6 z^VOdbQ!_ge2d2^x#C;u9%(9(FyM2<y|P@IGWc5HB6#io}mr|xUzK#sTTN*L`&gWvwf)ocY4W@6f85?mJBW%G)aNX$AQnEI$LV|zRaZ@QWve1e|$Xi{o_0@ zc2Hy0YP+S*rfKo)kmJ$#!kbQe7d3v@#NsDaG9sirJ#kFwm1?qdos$=PUiEedk) z1-6xXX))_mJtQ~i{qok&&SA&z(zoH(_rawLDCaNilE5_H8%E41c@v$(;0=H{}Ec>Xtb|VnR@>OO_ zOH9~|%>jp_2BB&S>Ie~=)8fW{qnf`;-yO05K8%G6yg47B5S~GB&~FlXHXo?kl_Bu6 zNfA*(hpIzp2w#ClV@f)I$VdmfCm-#?h>iZ%aBhURUag>C=+14Zgu3HjAnZg27#UA{ zL!r`wl-UEtYj7Av@`$XJdiYTkjV{+y;T2e|nr$VVNmTy2 z3>!xpBS|Nosw7;)ftH$A;IbG8H#_^;ca@SbgmpR!6D*r0*A78@hd6?i6#nkhF6}>+ zKJO1VMhJPT34=|%DvQzM#v2BumK)iuDUf>@*l(TR{<l79=sZCADR$$mv#(kVUluN~^CoK?Cgio8aX$E+da1X>}oIa8}k5O&bh%9q1KK~p)PJYtI zDR&2XTy~q4^R***pBaCNpP;RC;6aI*s*L-dti(ItwYHjjOh`{>4Bc&GZWa1uv=h{f z&C#=~?q|t%~}Xc?bBfjfx_Bjim8Lp(ikX#{U!a#0R9JE=zqqu#A~7>qqZv3KjZb)z#KFChjMumHK*;n%(aB zZC~M>?WwVjghN)1$-+A8yKfk~jQyFWs>tul(vOup)=^QQ;q=Hu4UPNQZyrfiG`QPw zD<_9xCE+b5s**LRYF`!qU3f8mHq5Sn^o7x4@kMXw8E?S+Gn7H%llm3gSt5LGP2bULa_hb#``(BZqeYde8YOB+pAO3IO zKkvA8HRi`p`GA&WxId~vdzkP!j%>f#mNVjKSsG8iO7w-E)A+yUi3SKSmaW(Xy{|=& zt(S_dkqBB}7Gx7DUnN9x-LgLOSU8OpO^Wc+50<{P?%kJGmpxBZPcp@KE|H_TkJm`W zv=iFxL@9d`r9%(=6;1Uo4WvoS-Hi|2+vGDiD~h|a*0{TVVWvGw{W(wxN3H`sI!Qxwm$}8M2`c92Z?wGVvhP`U|FnE}W8?RT-pkxSZuHSi=0fWXFNaunA!hHaf4KLX zaayhB%b|*s`dGIVU!~L6=dbKPr1a^r#f=~5u9}YrRXU&AwxlxbRNuXL?9rK&(}TW? zFFpqzI9>R;uoh1VFO;t>d7lf}o&j#4NnY<8}=4%$EF<=5)iy$#FKVl+U@uj9H+ z;THrHS`jc;2*6^%2(1VSm;gD-)$3EluRvCNC)zyQfSInY13*A|*VP|oM}Wq+!q4rzsM!5yFv|bomM904sV*S6nnEDa{86q>}}RiRl`D zCgV|XmiwC~`@Sil<5c=T zIIiG7I4%wDKXF{4c?P&J#xIV``WMHg40Bw^?=VHzIIb1P$OI>3CWS)B4HT<;F$l_> ztQfLz+a9`)G1jGtcAeT5D(}yMMKlH~Rw>5RD)tDc1sf3GHN=qP?y5+%~#?|`>tGOW}fqP+OuIJ*pXy*Y>UA{Ecw9# z?Q;Gg@tV0b`#Z@!n)+?)>9spgLadNp6o?I^NbP#*#<6gz$+6e~e_n|!2|dj<$)yHt zvV*Q!s%6I7SGK0Rym2|FKu(BskK2t)=emm1#t8NWe=4{YndF+kJmQEC2|X8ayHvJT zE-t>(dK-W3Q=P4-*<1T<{I(R40~MJWy&*ZyTMLVi=$y;e;7G{4X~ENxpi(o+5i?eF zsQycwTpL94u#~53t6InnM;Cc@X{9dO3OF#5@H9e6ED_nP7C_y6L|sPeNxNm0Wmez2 zD`6^C%j2MXQZ&Et{V~*AYjoblD4#hkHg}U$ji*eetqf#6Ro)h@tR%y!gw_;gSR0Z` zW4OFo(MO?alxHLFd2Tbh7DTb-3g){_y5L8R#vcsNk4v<=K(kRi0oTYsQC+L$>n~=> z)W4~&zp1Xjsjk1NuKz2kF3#Jfm;p9P1pdomT$+vKzVnj-YIvI5yh65M3l6%QHOy{` z0#jX7y^Wa}$VFznP-ZsZTIk2`cI76xvJPcy+H9xhg>Ok^npp55#G7lH)Sj&@M0#~) ziT{b}%4<~QGh_j|Y!igd7YP6T0pL*WVBhO1RE-13t>JpWevH2P5}w}W zX1a#8ld}JP%Hv(`7N9;V$-CU`rh6uOYcjy_DOBU;QGs#Ek#cNb(x2XWcX7Q8#eClf z1HQ<8+!1X#*vFN4qQW+YtuY7K5~zu?%ht~HvI)(fXNuaKI#ZVXNQcOxg~=*zvQt2c z46p*#&YVXaoj59z!;^ehGHCzGGt)3hVg^pvxS!{`ti7!6f~JL3K>CIzSvltnoYi;U zQ)P?n)+m3xHLl5d)0@Y)j57#c%HzUn+nyvb1Msh{kc{2s(sA>UnSp{UhLlQ5Hq15>{yj3Am_Xw`Vjx4eH_NT;&|~d43A+4V@X4niq;_jUf-rL^sRGLM-*e&!0`-W#B_P z8`b#+pFAH$iSBC8=d%;uH@?bmy*|VJ(`% z{Nn85D!pZQaqr+2@#9AqLBfY80^TI+{L8yeYi?2&qwj&(INxNa8jYf`lr`<>!^BE`zLx#sS!zqI=HR_~|`53pdvT7T{E+dPBF= zsI_m|Q7dv-5}(MQ_;EcT3gH0gk9! zgDxvZM8Wu&Bz`x3E4g$;Js`PVQ_WFS!&+0!?1DBLIIsaECufkYCg%ZncDf#kq(}Y} zMWqKw|LY%GlZ@#BZb%FW!+%lxnlP2~Hw72oJA+^XfOw+qu`3xNB;Kn7uDe#7A`7S`T}VaD~C6|CeCksYKP#u!Fc=y)~~x zVbAl~!-7P1cH-py)Wwe=>`Rs)_1I=VG)JzHWqTSAgYekrw@#o(k}kx}fl)-@0N~a* z+3Jm>s1yC5M+P)B#V-8XXrmGBhWD}g()lTc9bEVeutu&Sd%q)Ty*zOn`H`vBV)7ob z3kH-v{mU-`ClrE@N4#B z-m4mlxRjHA+*A@WHOHLCdsRQ-oqNlO;OvYDEv?qs|64s#RZ|TgpDC=JXlnY0ZX&X) zTAZ!Cob|M6?bHTeP0zfZeKXf&y8mtJ?~4|_a81Ysfht&fE6o6??L?sgI}v?->b(uZ zTNJDYtRwu{#Yap?6B-T0fL#?g+!oZtK)Yb%W!N@^GUMdOt$4_b2Ae_@M_oimSQ?wS zHFl1xS|fo>SNb_sAx->-KMB3{zzR%iZ~3=V`eg!xPbrJXfAS9rrnnl2En5?MzjPN_ zouGLYsXu)GZRM<+5FU-?7N@NWz4hQc0T9vH;0V|R=IQ|OVq_r*5R+gX$}tC2JeeP$ z@?jI0fdhxOcywCDox8aXJUS>KdatE5%z;ZMlhpRG*UJH?Edrpvp=W238#I~h&-EoH zCMU;cj0ypc&;&hrttBB&#G>&+f_T2gpE_=;kOFfwjmU+OGC?p|=~~jF7j}98O^OB= zJ5V2PGi?hpI2y<|qu-yt%|%-_-YTR-h|Z;b=XSF2O@CbriL-sf%Pw||RyPu6p5$!o z*Mbw4=j+w4*ti)J%~$AQ_?T}eflEYxhH3Bo_*S^Q@l)$aC>Uq`Xp8rJ2Lj8{8C;O^ ztreM_A3fFkqH`L-K(mvZ$Bq6Z^wx7%5HqS;wDI>xVg#&$)oZu$5nxJeSLaZ<)4G9p zdT8@(#c65j9kHFRqm^f*Ib?R$jL?VQzQXcrbClAwoJMZ#G`3W5qMV*c?G|JBKF!FHema|9Jf`FJLvcTmRG8rp+kQ{P_ww5l=E=KU4ltE(ELGppgfh82&7HAaViMVCxWS)Goe5;ghBsEESy3ML#| zK+dSVd+#zFTe9f=mR5HzZ3zgF+Ist(ab+Up?Eax=#{kcjSoY_gzP@8qjc{O50D3U> z&%jPka{}HU*9dk`ryIaJBfv9vRNoGH>k20YCp2S2-x4S!>|4sP*QbY*AMS2ak+E%y z9SVIQ#Y4z3vJ>;DL@bOx>AZ1&kM2y94d`i2tCX1uWj1Ki!zi(twv^ZD207I2ryAzd z=?qR@ih4xHL1qA(=ywIcol!JI;R3``mWIC)c;C};ciiTHnc?B-*}K04O=IuG{wdCm(1!!KUGf=$=s2PCE@W)2=c`ugp=_wx%rQ%e$ypUJ=Lz^C~J zBLHYLKdd^Afu(3*i9!e(t#n=t3ec3Jk%^^Jbr@}R02X?s)!1}!w5QfQFOX6a2r$wc zL-LChq39Ry_oJ?RUgF&smToeBJo8nY(u#gxK>xhTUGACl zLL*)`7}4BHma+=fZYmgbd)3GAZc-qYL&igYTaxm2c3zc%lR(Pvjq|T;-(&>#oiqAC zQSfNk*;sIlR%#6~M%uHC()Qi0`#7V$>-dTIAHx^ky?k@!knXwQ&kJp3S=d{?SIXYK z8#*1uWqgOZxD@1>mUiFw+t21?thIv1pC%y5aL}8o8##|8UyF zZn}RIV%TZ_OYFPmb6Evh5o==Ki@dS%=Qsgh0vz--Sf;e*w9kiqF2y%XZml})VV_HN zO>JGh-9d>vY1X!^)u5Z+yVn-=JCOIg!dI*L5LWz2_#dVGRT%2!Y8`+4I%utmk1qzT z)$p&JyUB|%nC65yB!lxHMlIYr>CXq8X&{~wlAMBn0U;wH;P+(&YNR9Z3RcuogOe%7 zyh^9GF>Qje$e}7oeeD5W2Eic6_M3io(0$ZomkmlbFD@V-AaSjk}W{~!{M8`haBD677PA<_Qz@F$Mb z3gAdsaRW$@EY1l~bT|?QD8Q$U$B0HO1pNmgy88BIIU?0aN(K>mIRtdy66s^ajr z-F04`6Br7#nELPHU>Har>UYPa(y#LWdLy_q{rgrjS92ECLnQzowmwwyqWUVH}?@MzF4vL=2lbZIRWU(TI#$-F8Y-^AKGmCl{#-f_TLKu*O@lF zhB^ls6=RI1XbO9?2VO5L>*t05PH33GiVnv{)u)kTBq@Swj^^i2%n9@U*r4J%_OXAjKlC z!TWn$-2l(^RaCK`lZnoNEkq$g0L=!t3Sd-mTpR_2NDypRg`uPg0cewXiW}NH3IwBL zbuN~6{s$=eU)8dqJvfwpX%q@6FvDi`B_mx+MM73a);b*u3gp`6wmecukb#7hL%Vn& zZ5lgSJzR=*KD$#^0!M2*sZFP=ZFN^mM6vvPR?-g4wxJ9M*n)O$){vLSn}cA2khHE> zi}G|SQXFLgMk@+?IFV9{R1gw-D21dJpK<9^UJ!@s^Gs7I!5P^q&;Qz^?{a42q!*U= zI4pj4KN^VKoppnzp+QG-mxb;bGzBunMcu+l3`u}fyxq<{f7otQ=kU0Q`~vK)J~;BK zOC(xYuy%)&&Yn57@$aI3@gl|+*lMSYBJ^|0wyrDekpEGSiV%sd-UAF%CSmEoeZGnh z9IeQ}$a`1oe`XtgoPM=EUf6Vmh7$K8`^CL&758T=!IBvR`l^xvkMx6W{0%nOaM>sc z9RD)9RUoo0Bk1|>3c1#9P{D2ZjyU}l^7JrFvEYl%)HMCWVJ)!@Ao>uhFbN7*NUR`8 z97Qm1t|3n|BH+@3ATj?{JuLIUpYP98DwM=xF`U9NmJJ`r)5fAaOL zXZo9W@9o~se^@eG{P<hc*m2_Hs|5C z+gYtqWUbJp4;eDiF%r6k;cgm`|26U+ZB|)XZY+XK&hC8RFLJSA3Y zBZG_pMuu5DiYnI814U$CI+ONs7K`n~v*z$iNDL9pBP3!}l^f(Bq>2L3d1t!0>VT~u7}+&0Vp&!_ zvgO&bw^$*EbvpU^Wgn8;o8!I`X+azh&p*+-x=ec@LC3AnAMqoo%#8RGb!A5xd=`$Q z0Ydiqqk$ydNzY?JRo4SWf@R{bXE?7<)bBtE$-*XI?&7>L+1j^HFrM3-7YC$-y3iVt zH;6Q8wFgk50aDuw0Nj-DGVYLeK6iR-6yN%0ECfb)NG>*yMi1 z!){iK2E-UgCKPxX-3f-9rAFAs^1Ac+H(pb!V?V9K1>v50Sf9>5oOFDzBCr88%uyab zJ1=YCaAdzAC`GpvcE8wvS_&Y^8UD5Rx!a{maE^a$6j^@%xP$HLaTaXVfN!E!n7Bz@ z#p_4;W<|%l>nhdCqe$CH!{zw&cuiMoKNSls2+FJC1g+~Sf^T%o5dQacH07fUnI*(q_fA0Jh zEej_q0mqU5a1%vvM*}MKty!m5lGl9`4NUQ&Xduu+y#PB#S8d16R5k>H$%2{?Ysh7) z;UX$qHcL{12pZkAWUX14UG_)>2;$$#7QIJqh|LVj_R5G2z_EDG@@ZR(#W9xLXjo~; zc3nb%M`;TnOlSxt2DukVzTp1Ksx0>D{TZu{L9y8Gg?KO+Rr%r0T!Ot^X<@X<_M z|1%Mg?HZO~PKf;ga)t12HKa2^( zgB*$_I8RHfL6DUQhhaCEhAWYx-pfa(1O<(#ejitImC$v?@%UM*&X4cF192am0!u~} zzSsm0YbU0WHi04p(i>t>D`;VHnB;lXiw0`DxB;M0`m~Thdj>_(#B%Mw+t>B=CSvfG zbyFxjL~*H$vCmVy!>qdbyxMiVBgLq?l@=|8%EdA0%jWNI*)%vxJNDx&Us*`w5mn#>?m%)K_idGE#KK zk^4UvaS^}-N)lqZjF&`aikG=d{q^pPBCbtdKP1^Oy<5s(|Ln!$+3-S+tc;64_DIY< z;WI;we3BrWp;__R+mJ`P1bnX%4r}+C>(hB|V0p^24HwAcZ5gZ!^*%`rKBF23+45;K zMM6DfF4C}vy(am>Jw1qz0YR2!ep>ZO1D$4KDwHiI?m`TR{>Rr$#>B3-5z_(2@lHHA)Bx}KwDqQO0`;4z&OAnvEY^YRH=VUzc$-_ULHPE< zW=zbVjWSeF4kvr(zrNflz9l*?e@vcSie=X~M`>!T5ngw0K*T*->@x~$gqJy*EUd*2 z9Q{@auQNn=)Gh&IkSN%fm#j~^;N|U-lAV)F!v&Coz4Ktg>jWR*ibLLp2`^(9f_QV@ zKGft@-%>{)!yx4L8VK=eeOxLCI_gkx1O8;B1O_2>00tqWZ|0&J-p()3_!s|`W*HcC zPS>~`yyX|o^4G9~WKfpf?%CAuxU=1@9gQZ0UI}*1%`sWWAde`@(3)V_p}PhfSo4BzihhmUM}Pmb=+N_bmmy>@g3U2Rtg|1vgFr&$+jcG@k)XW- zfiwCnQ&p%HZ!-Jmsp6m3NB!Z;v)QzF*EJX&XxnD!aDAsp!cT!iwfG>%yw;I(FHqNW zHkVp~0P%Y-M~~eAxNI#xp{JDZ66?WilBnHTtGb5$(39MXp7%#FiF{I)lO^xpL0pGs zu1Md%)l>r!_&lC>_(}vlf5{x2p0+`{cZbdmK0Nf5?Jtu3Z`fgT;Q5*BuIx^^m*DkL z{Fy4EA<4!xIs#21Lf*3RZa2V-WI^eTR2x6KRrGKh8`MXqJapaT+z+_L?Y13{j_lpR zIKQI!y3P6;3eff+Q1hc%cDKdlNKX@DpCC@CHF6*bu3F}aVLsa%4!sbv#@&c=Dg86z z*a-q*5#9CGOUMefMaT*8qn@1{W6Evze-1tPg;TC`PKcV-}+$O zs|(+`t84v{1Ib3m&S0%S3dXsUF9=*;WnC@#{9xpp1+%Uh@U^q!ya{Jm-!h-S?Ao4I3nKp>GtJNe&!{$eI9IHE%* zE+g=EV~{@@{~p6(6&&Ti-j>L(l&l&_5oQxmP*%{nJ-(d-91w0_*O%4qNJcL;5ozP? z2@fZTj!ZJBi&1dLyqPCb{8wPT#VpCWt0~Y|`QnT1D(G1)b8WxrKUOS!c=PJv{i~J3hG0fQ zMNpQ-IxC^A&`cZE#x&T%-f1p>35dwTNX%(M0i!6t6+q2C?-g+p>9KxHPeMSSG zjCFR?z7G)QybKu^uBbUq*Aa3h0_UBYA2hbyqmv=96|>x>uukMQ`#!_}bcb1;q;+DU zzBTZ+c>PHbHt+p`YE&By+OaCBTH!!>*2!QSR64B@mcjO{4QuP-$UtCR(jBFP0PRI~ zoW2|;_qsX{h;(oZ4F)li`WmS9pvt4y#`kq@uX4^U0w39~Gqa>G*^( ziPhL#GdcIx64yiSYkv3f(|)*oyhMKfxuUHOFXrfiSpGkSXkRj+46TcTK;B0``UR|6 zl3VRJPm;~ahc1zpUuy+nad;AKgr41LBC-VmdtPXe%~tn)2LO$JV};`f^EIhZwuy)N ztb+TPAubL}5RffCX}Fz;MQty)cu_cwggvV=mMmgm(aI{iv+wSy6BJ1Xh z9cbEieIkWbRZr{mU>zxk>xLn6)4MSntpxzeSbgXG5<_!|OvlEAa|10Lj2*81yHd4Ew$$r+J)FAq{`$ESG~e-^ zco|&dNF`J1k)CNA@dj!Yq$#XFVb~lp;j`WBs+rX_?wvrtx8WqpV&b^JC>#2Zep6F- zf8bB;@)cANIS@|#GVzXMA+ovoW3VB6W-QEeUWE~^5a>on5c2sO^J#5PIgU~I0F7YEXEoR z3As`e+|O8)S?t%;2dwL!7Ce1%T0W-7(*D>fyt>w8b|#CdEB6{k`OXF0%Eyy}P#704 z0UsA_7^Y{kBHWpJ$Rg!G8Fq_WkpvMm2?ID&3Kl(Tb4r zEB?(Mu5I`<);$73M-dbuUBE2+B@X&N$V()^9~ISP1H#^G)iJ^r2Smui01K$(E0L@H zc$&>QDiH`lY_WhneRXycDpE+sBFStJ`5a&A>LB_s5-AP{WFD{&h&8~~@g~6)7>fc< z1f~xjM0@iCnz$yK#`Ox8#~ptFa9W&np*a|$X~mms3WC}%@FeWN@5i-5*C1M~tXUzv zfGDRkLE+CEJ`d})GghSV{qv-{fCD!q9XN@{l5%L0+kvlAN+^R*O%g&VXC>+VUb#mL zk;bZM_TVN%9ZV#z(y5*d561{})uksw(ib7-Km;F1XSZfQcN9DiG|nH0=Av)oBuGSV zgBMstJtAnw_&9LzNy$Vi32#=-p z8)Vb5YYT|hs^i}MA9ZA>C5-lPFKAGaWp)GJPJJU>zSe^hc9q8jILysf zGISicOJ5#4eI=Mj-Se13rXhVd)5c~~=ET!ucOL}zD<&D(N*n&5F`q`t)?!4N+5!c^ z-ufcK{n+0Ns1=f8#GOWxROv!2avW)+b@(OGr;EY~qNk`S-sh>hxw`FZTd1$q*z1xg zJh;>DWd@XW@UFOqiHBpkSnz3(*$QBw)_N@>}HDf1ajUtVGNg{O zPN{5JTWHnlK1ciK^Z8!i`}ezkzvI5{>+%P59397aKabb>dOe?yr&Yuc!sqYk1o^q@ zbN77aPB%?f&DAu&o1Htuh(OS3J5Y0GG+klY^17>v30cv-o47~Cb*jlO=dL=+>cu&@ z%$X%fM%7j~UZ!KBoF+7HFO40Z(Of5qSuYV&LMI;P{iYn}?4oOm#Pg!Q?cd6kZ4 zh%vgb;ge}I7QN2nJK?j9%ekYb+EU~$6N9no@_W|f+o?r(Mo-`+cI$9rc_B#{F^5=_tYiw#(j#FYZJ#6j*> z23hG&5}QF`CnnwOo=^16{@XfUHr~VGQ>WiQj(q?8{Szu$X>lZmFJN)hsXDodlZ!v7 z&g`{MO8CTTu>$vu!_oAnu~@ePjk=6OeIKS2`Lr}gb5EvfJ}nFr)tKqFVc&mf{UN12 zTW{9?EmYk6q-xFM#qnz!p1hwc*$N4gg+ZmLrX%!H{9srd@TOTEw!`Gr*2$a-Ta5p)hukp!+H(3Xb5ZUlLDyR-t><|0Z~oKe zwV(#D0g|EF-(=`l34zIJ!SC+w>hEhoueoKD_Y1v&o^6-7BRr}` znfSbfuz?+&UKS!4Vq)XckQ{ObrW17|1p!;1D*guNarc09JDQKmxE_ZCEkOMaVO>ex zeknW~aQj(TU(j}IdWsNwSW-%p6OWhV7tN}7*XmSN-lfa0&7jGCbQ0i5-*%_CBxlBq z4xrqBl@JtwlZW4R%&_m#i>%E*@j-j40DpVoSbr$_dEp8AYwCMfU_$>UH6`KeEm4E< zc0OZqqygK0*T zB@ktEe8&wy<-q*WOFbddR5W3`{>!zhS2Ugq6>$R6~Tf@5rfnH!Db&tZ|LQv&y*P#k5mOP>c0rawYUY$3SvRilhz1SFEVa^44MTXK4>JB}-YFxqSW>G|<|afa zxhgfLYSqPpa(Dn?zH9B27~P&EgzPboK~K9fT0CMuDg}x`V;%MR6*$b@sS`8&`EMlz z!B-dGaPd>a7QdAcG`{@$Y5v-p%>kKKTR%;|o;tJi_4E6eP^k)z4xo(~uvPw%Hx!e0%RnC+O4j*piE0cK-uNx~t_X?SyMG~W zq8n19U3S-fo3WJgE-z_Nv7gfxBz(2?lMMDR6z8i)mYay|goBaG&H-DpDYXh%s@RI-keEM8788m1n}%~}VLTGEh2OjGo0i`}k}lE!?ZquOoV1r!ww03CS3 z02)Xb##n^7s!NCi3_P*L8=vO%`Yu3vN&|^ph(#bi>P0);nMMJ0N+^KEk^Ws@mB-`q zVC*JO@+{fPV892z@;S1Ex463JktcbADhaGb_kKVgmuIZ2&|ennU)?+o7TP)!wuh(h zggq)afC!Wg6 zW4>B*MoDdb?Tj(MHjqr%a!@d(cx#AsWn~9bHmf^-%vcvSde~|vf+}vtH>a+1O*~Aa zy4F6ZUgQU?B!sr4_>b`;rTsLo8>fxpyico}j>X+=cwqg3kv-xwE}i?-CwgfW;8P~F zB|rEgYf8bZdvnK=FFnxe{V_n!mtCp%V%!!C;7^lksi3x!J)o<@u+xowS_{OK--g69l{`Nj4e)YRS_Zl_tsuiYF6X9 z62brK;ZEHC{=|?Yg?@*7Cs(7+aU+SRpHL{QQ4KNiH~lLUqs5gjq(tvl!o*&Qepx-& z@F`UT5=%y1b*q+zkne!WnN3xpK(b)P0b_tj;-@#CeVnGIWa-z~lHC*PRWIbn7_ zN~Lxi0;{^Cc^y86`#4Z6+P?$(5T<1A?&1&^f|>M-Y)T@>?u`A^Kipo`z4RBV>$PunzN4{JnJ4hQ*6d zW4FRk3=#BqzVQ!mi-SSLY_!;-<4?%rPjJh*W9W3W4kv0MLH1Jqr&UxaE2s$541S_r zv736A<&28dsK9L~qi*LOW7idZ4BrEXZDBieAblbEqaeRJ;NBmOX^Qt&FWPjMDhL<2 zz_L>aN{6cjLI2A<;4>GBgx(a3A^#zl;*eZIAOC+Xo%k<^gC7-}dp7nWW0?rQ08Ebj zNC-Y=(ViIHa&A3$yNj136*^*b!r1fGrcSt0FiLfONr{y*p01FN0l0!!saBHbwaPytoU(=^vIN^p<=1e>JQBH zTdH}0h}JE#kQpmI!$~1k1bvD$)N_6nnTBFz!4lK(VKqBFQ1GUiPmS}d0UX5%4@eY_ zUTehRjl+SnyBzQdjxh4#Sx*y4$h%73)q5LrC4>MA^XGhxS)~qA>}SvW>jCoBrmvqj z1ZrL8Ql4H(Sg48 zQ%f~;;<+@%N|r(zY-Tzv4++RZS8*bqn67k8AUqF3#N!9h{#0te&;g_Hr5%QX)PZXt zP@*R2Ci=L{)Mpuo%aVYBMaw-X1A#gbCMO;-PgB})$Lk?rOk)6;%3UB3s#CDn-J>wY z^{D%nE4&UuCOnKm!|fL*TXaQOIO7jHT`_Q_@lm$@?-o+YO1&StKU4%;n94nXmBq*z z3#7palG#osw~R&lFpQEQ&8K%9j_uZP0L-q~-*?*g{OzyKDJ%NjXkqI%7ZTG0hhL#A5KbSdrpd>we73Ri00XqKFd4naJ zHOPAcc?ZtzbJ!MesgiuU8IIf_40McMHz{_M<=Ybc^(+?%+SA0HP9fuq4B%`zS!`%$ zKxp@BVDrL>V7^mpTX;m6G{5CpFc!!c>uE8PcPLe4&8$4F6C_x#OjlB$z0p#FRXSza z^=fs0{2f7jjUIaZRK-K-1J~=iQMJ{#8}+nS-(~o*k{azEt_`^0biXxV(EX6dY|pOf zpE%sCqkADld~2s5%3Ui=LQ93TfYM^Wyg^!F^gY?*9=myW0KXlHT$5xA;c(HFLLa2W z)R%F%z{1lLIO4-}`qn+gfa-yH*O@FPB7S2)cZ8IP#g!%IpqsY%EWt=-!|yowe*%Sk zDm(jlNNDsGUW_sPg?n8fR;PSiE4?01CFEV<+7!$S47}Xn^5!&=D$G~=U6s%d?UgZr{SD8UGi^0ZCM2DVuSZEM$1!K#$GqV2_5q@&1_^DPOTSwk z5GEr9SnKH4^BgqC?1X`BCS4sUZ%mvcj1Q1~r-J~b^^Q?p5(gJhf|m!xF%+uq@#${M z1DpUV+znT*`->@03_lYe`7TT*Yhx0P2qN!#)`Zn*%n7CK}Lr~ z0yR0yh_J?tlSgz#5Eal+fH||gr^4v#1!sL!2d=K(Eub?Bz&ctVx^m6rJLe^ zcsIrW#S$*LBH=3%z9Qi(622ng|E6B(zw8taKoTzV`yxfEVjY0v#{#6HKMY!t6H+Ar z7~A^|8u8+-pW2&c`|@%_++Uy(kTU`zg)onY{{|ZI{~#bTDmF7aJ2mo?-Q@fHu_$UA zY#Yl5h6W%`gkvBM7vjykmXk;cB=6pFK!TJ`<7ylgpQmB6lOJfxzEPggg{M7~FtE7h z0+{%75e&L0h1AxZA%41XG-vtwr4W8m)wBT@SVF<3Qv2Lm7ZEs~Q}48Cj_Lp$Kqv+c z%mWBOo4&~}D2Ty`Vih^{aUsY<1}H96UgRc`JRHs+P$JpY0LO>=z zgOVG)w9P=C?dwgQTn$sBol>#`6v$_f;7XNTb5D)2dFFO3NFlw70`NigeCtT<)8IlU z5kE@DYkhQv_UyH?pD&*|qN5ppezPC>0Y@q)Xo}P{ z-p$y__0`odMquysl8Z3Q#y3#Bb8-FeR!Zeg+41D)TFICc;)qePERQVyB>vqsf8YG zp8RkR?;{_wxFBEOGkpZ27u6Z3d8nn|n9RXN(07Wl3*leC(^4YNG*|`PX!-4UDDtc} z+xt1{?5ovtZ-x3b&8I+! zjbh%}!cpg^&X0!^7slKu)hZLNs+?<|=Du$6zj#bx;YYjNo%nr7w$rb4CcxQ!&Gc`;NBS zwb^=p?-h2OBPpN$bb1SBs7GwLM{2|8Q8q-x?VV4u$~L!v$%hh5?c|%*7k6%aLFnmb zz;PC2e&ZURP-A$C;{7DK5YoYSR{3B3au^bV3klBkd`nzo>!dUn5-pO82<1i{@(v40 zrv@xAG^hp}lp-edwPfLl+KK>UU^qU8H-S)LmkLh+^o92b=Oi%D5&;HD zbEKPXX&h|UsSo)JMdQJ>9maHi2=cQ8BLv5}oi(Hy6+Whaqx`TCQzhId~C&m-Mk2t zniA6Up)8+C9Ban;^&{gWt(lBtr}x>ltzy8~>AJS#$}Z$qFn#;hOMI5^)*ppg=z>W@ z?^HKJ4cx%Rn;X`nS-{*>|9nvb`$ek_fjf3mq5ba_XW@olfzdrE&Zey;H*Fo zYDJS`;2jIE5{>ty{D&q}3a;CI}7Um_3Hi!zM)EK81x;{7XoTm`AXP0*h@Ps{PBIh!rC zd`@L0Tkp!R^a2<(^hg%eF6y@)f3gAxJmexaapD1K2_F-iH^z0e7+8~xocuN^f<0)h z+E6JcYH=zl9AP? zltHtj?IP?vf7s7@6^2`XZij6zm+nrtKoowdGVrcb&ZE7HZ(W~c3s{H-KMVW9P}5Er9uo#Bg)4#3@SAHP^M{?#Ofj{7(r1-04C z7)1`Wz%hg%0P(djAQt60-?9`3mR~n-7sQ311I?*(1?{^6bjT;$TNOZYrd=@b)%GAN z;JixrWIOuMvg#}QAK-x#bML-4UX){ahGm>VSRhoDy((tKZnnJfQ^iA$kZW7p#p~vN zGr%!r-i!|8>tdiZzk5W~QD>i;hkNbsegI52_|YgkfRa3Xt2F8I?&?4=|GqJH1F7BE zpPHg&2Z|g`?>QgksM(r7>1ufbr_BI}&6ZP}WDZ}QcQGug*B8VDb*wvvFv2d7zD`s|JZ_PU8Gs}vB;|U;gc=bUc8pD{FAq0M$kOOQrx(Z=- zGKk@q`(VRI<8Zlk=`8bbc8V^jgt^Q?0T9T6=oZv8?J9)|Cw{oMw22Y3@Z3Ee{5(jE z4Z>SyFZ?R^`7ZPJY^o>=7^+EPby)ZBGVj@}LQNcYQ_1P>_0W8M>{kc2LIZ-R6uR$5 zfCnlb&a6Zjxrotrp@tc!a-(ER!O}H{zDkn(O6Jvoh&2*~Zu`%;3&iS<{;WV4a0f`B zGD=vaV1BwA-_(`^Uy&egkySL^yHW44c_w_AR1)>g?eaY;^>^W)Zu^YM>!}F&8BdJ3 z+F6#%qLjBBPrQ=a&4gE-d3b5y(!YYj{eLg~mjm}by#s0gEmzZ*&u>3__Nwpg+jqT- zK_B10`SN+aF5lJ>MyNUNt5*5^(8ah`j=e5L$HT()-pGXF4)}Ci(n<1lx>kl#X1~* zVpUb2@7hrsdg^qz>E+h5vJJC0&z_-==*{`8epqMqq-T%9?*Z_uR5)u{=qrgwE$LVe zjpRkk(U@c*t!=FDswPab*6r*xDyz@z$`g=t5)i!YHBicv&Yq{aFn+J>Q(@fqsMHVV zb2O`%d2`dkcvq&v&c5ak&IkKGgrw9Ms=S>&JAPK1mGA7k=fk%n8a}^L{m@X5Zr*R= zFdgx(p2y*f;La;|UXLwA8`>{v7HC(0Xhhk=q6p43qc`OOQ2~*B`iF3>#XGt123`8G z7*{VBUcrFDrI8rX<2PLrtdot32&)Pz^pdWVJ&}idF2@%|%RAU2lT|#>5}fL*(8Lt^ zfI3Q=r-gyLpty*=JJkd`RGhI@4GMtM7WGPb(asv<;aWZdajDLlL(aJyJd7uEYzjV% z9Pl~1D4FKJ$;Bg|YnAcTai_h z`CC+~YYV*s4)Ev8wi58q=6`oSU-Au%8-H=3?wrV6cY1kngSTjRT&!@J)yOuDlc)3} zhb8jvIDeF(-3ws#B9VWynpps?02Va~1D!^IIcUONuu(i8L;+s>9|XyIe%oQ#&zEs}x&Vh||ei;c=7B@%S-Ahnck#Gxou zi;=sP4@jwp&Qk1Ym1Gq_HVxph$q_AZ8NoAd-_N+TEh??{t)lTRv-mff6Ai!77nqMv z^cNbr38Ik{Z!(G-hA21(v%VF_{z)TYANCBrSVj1*iQk9e2w)YM@ee-ti2p1^3YOos2+c#$Vn@~8H(bS3?>)81+9GgW#_d%M^=N+S z1#T~w7gLkw14i&xU1@>JsjOh=sr$;1Gg{y%yBQCyLw$%RdxEhp56z&YD>*);FUGS} z+dh7#EPwCpOga6QxOIi_^E$3S`h33`rWJ%E)Q(M@zT!xSI-3sS$vn-1nbHWWNpZlU z<7387$lp7FN8S*$!oYZo>`$+;@i=!{<@C+ht5=JuQ%u0f)JrZs=&d{!J@A<-)+F@2 z3xfUR&93Lnh=LFI+pkO3 z4+My7K?X?0a4UJEX&CedtfUFyR*gN%r8KD!X8NYLM!` z1|{SeBazBvM~ivR7!)#S7K?HLsZrw3ZLo;|eRSlaC=A1|b6Vjn?g(=3CLj?_2E?R2 zd3*UqAeviNFRVOyUT$CS>AOrYhxuzPe5uQzWE-qTc#T`mFZ}V;#ASl-^4r6`S zqO97kU2xM|F6drm> znJ`aL^o}3e{&lpt-(4z0uSHbx7K#$A^X9g=X*wsvdJCYrs$G`|7<0jiz3l?jeWCmE z41`MwWz++-`Ank?GX#Dr>zze1U(!BxPJl;ImU~M?K#3JhTl%#t{Sw_yAE|f2g;3O? zM*@_r!$$}uoxOK}GGUi_V1p&T+gl~cq;9)yG4DDW>~ZOGi#GGs-P&G-XTMjqB}Olj zR9N$ff{}`6?1f9XO7fmcNor~cm*vjNs;pkM!Kdp^>TLr_DNK>ar``+>)fs=a?amqj zYLujXOG;`!Iv12w_gqm3^pJV18L0F#)OGxQPZ7=hCP<;?fY)JIc&Q&L?;J~ct5FWC zTFI{Vy2V5ql0Z4UTTytL6PqvQIlP=uXepEKUrRkfX~@U=!NO}PK5MTr6AFWB_MsYK zw`g5}Dr2yN9PBE`Fy8R|&25YSD5gMrHyNSE-)ug;@W2LhNgQL<`lX{zVwHCzPw(-W zvBwnayzz%rt2+P2SSzrmDe;h{62`8~YE5f?87m`qCMFXSwL2SqF1Hrz|~ezK=u~_kdC5 zwf&$ZYm3=Mo;B^>7-fUSd6AD38#&z?X1<_@K?~q%>!A@Ruq$T&6|_Hv(c&)-6}Vff zu=agQ%Hev=;0stgh z?0@XdWMLRbk|>(`=ibaw3Xs){b%SY~Rw3Rx}lvl3l}wq+7LBVL+|O0bn}JHJ7-70-SqHy~SvhZKY3jq?#PU1PllG#z@-_N@Tp zUQev2ORI`_VYAvNQq#P%I!Bq^2whx-dbQ`rQALdX(gNcYUja*>vIQ5#!`S17|F@&c z#IA28J{)p)USO#VGxav^;g@GyFOSfa=fgxjMpsT&`O zfPE|+Uml5woB`Q|ad(}ZPekYTUTBoD&DC9;dVSs4g5IJN1;ZFocOIMw;RQrla7Gk$ zK$(6GSwU?eYM}7o1CTod4ss?_=}VNjVQW%58bYp&sh!6avH&u1^$c$@ZE*YH$Izo%=~2hMGf#6U zaV#k=I%zY7yIo;cG?)Z7eOnUIi%z9@kGX;X7FP#g5c!jL<3h*A$N(=U3ECa)lX;Oj$D0%naLC~#p2ZShT*+)&l^YL zAiB$s&!?rIpHHUg$95f*T_fykVpj&+_jrdP%1WD$5cl<7p-^!VjDE)Pfp<<{-1Q88 zMvdR|`%eym4_MhH4@BXuuT}^o&^w>Bd$43N)-O_GrY!&lT&fB?T&v@l&__ogr0=;< zu{XuL9}j-yDq)EIaKD^k*1zvlB6YAHF9r(PDZ)nUfmT6m=VwPi^B@)WS0^=gqc^B!2^dl;neigUp+5d5vJe;5+nFH9V)8U z#2)($_%c5Ltg3i6_bxX7?( z?wr;pP;YYLLq*}_&tLCrUXiodt1?G?K^j=^Db;V%{Q89IyG$CIpBldfE2`ETl%&A( zQZQ?lih63Rk4)u=3ra2)ZhL!?{Y3#HAo$N#(F>$ASJT%>1t23<`Fv4iQf9 zssj8*;A?1im(*i{Rf_3U1ZxI5aZ(j|kc0(f8Myd(5}rWiQ+`NC_fkQ2DWuE$Pb68C zEYEf>{Ei|*tLf48UCZYYv$C~6_hzdUaFrw$etPPy-ZHhu?i&FSuk%K5dQqDg z%tZm?=LTQy&aDM{s1u_v@0w*6oU{j@=+4AG{_30wiTLzohs`J9vL_U&&WGMGc*MdH zS0rgLLAx{(2+24l>V+OR!;*Vj69myw-m^rOKkF@f1k{t=OHFu9A|Yf<>HPglbOfW| zFqSL5%USheJ)q!ao#CbhSmUvlfbzRy&2}jUpaN&?nb71}3^fhfT9cWQx!vQ~h}CxQ zp;RPepWI}w`%&-7V_x5~{sO&b^n{F~`2kD?SB{#$Z3ihhX;v_z_fVtN7H}!zkccIMtCQe4BwqP2XC^ zKB8G!?^b|v%vUk9vgtZXA4?jmJ@*WTbU>DD6RtlS4%`jWQH+&8e=Zz5jXV+bkCLtM zviZ$D-fMZ)5mVqmdfeGO55G28DZq8>uAQKGi=ykpM7{v$~pv4YfE zztw{y(VQNG>@tZq+m(HJT@(6)s$m1tzQPe0iA~U5oK&$#uCW@w%7YI8w+jsM7wgo@ zuGPxqIbXSFpOW}E^);2XweR8rOKZwUJiH=6tL&~@TNpya-BKr-%`n51x2Xw4&%~4eZEm7*1H3lbI8i2j+fHs6rQ+o^^3*vG(#l~oA@=O?3zqbyozoEbVDmx?iJzV*@&^{)L@3 z_KhpUH(eDl@+`?C{4Rth5!lxhX+RE{CApbf0nyh>OdaT!ks z&u8!=wMI6}s1jiSeJ8AbbR+ioe9VtH?8W(5<>W$~=dU=d%65%Icee)9M;u|B%WKUo zYeRI>_>tSE&fU|n*h{3JUofdQxfh|GYE(V2?)$KP4LWzSc8vr0ve0FaL7cr9 zg1Fq55}(ebwAeF+{3z3l6q}Yrhm^bA4$^U|sn}^|OJF)A<5V-w+;b46C6KA&XLyJW z45+op_M3`(a@~Mhi-bLVl82s*FhqsYMkjwiQvbC!<=+q6e^>5bu&A#Q9rfkrOG#qd zBy|fC7{`ij{*pZ1LOs5Y<>F+GQGnXST{ET76Ck=+ z#~`;wn?1d9;CqKA1yU?po(quP=3*pCCG8xlzwEeNR&-|sPgQ4wwFy8&Syajw`RIaa zq>%6x3!M79L!uQYSZYZ<@1r1dlhHch{JOnk| z9^0OEso73(7sPCw6OE4`uQ6e@eho8|c|o0WQ~4qHSg>0+9GvV)-g|T#4W<@FwLT&! z%dkIr*$$w>bQOVCF`eNBiK5Hlcq(8Ol)YWU!%p9OO^PIQVz_O>#Yt79*}JWD|3Pt5&kZc;7(G_qH4(6cW^eUL zp8Rt<)tIzWMEFy8*Gw#@sM&%L1?0*oj3v zH~-tki7eZI4^P!szrarJ5H@Q^V}omA+tRR{b<+;k!?FPto}&v~U`%!F-fde8mR=E{ zlbe1oUw`#}Ud`9S)c&hG$>(l%c#f(97BYbyckQ)ycHF;Z`=q1OjBn|Od|Ufp#tV|7 zI|WegXSCMilFyT!&mTE-SXBFR?zzu3;Z$gKQk%Sl7bY?Msc$w5e3%5dY5^3&mLZNK z@*VdkVgjV8FBm3KIv5-%$T;O^TnmS3>%t`f>L;Ec;juP4*0!sa_wULb*3~BZao*^7 z<_`GAu;Kxpv=F>xHe$OBBH4I8)GE^n9*B+_c1a6MXrs(WG_;Nzmx%~*-+pzxJQcHR z1Vp9T<{~d0JK|-vvv%o*ty6c`?IYu0zawdwGdR?j^(&q`G2iAVe{$fr ztF!3lEWMV2k;osVA-A_e6>S4Z{%@rrww%Q8{a2pU-zA;d33?)cspthvCAT`3 zSpi}HPC$ZwSjYSfR7K_k>G@#_C;cLXq_Je`xfu_!xpUy=z&AM$;kq^as7W|DEc@}q z({fRvW0P~w{<#3_Kd0og!`)WynM)_lB5XeY>?>0?uh8Q_y%;baIadVt6u_+pEJDZ7 zTF`1`T0XsvE@9iPW3A8Hu6CY7D@bJfDbO*&O;UAKdE|vIizBnsih62ieAo0HL-${=FA6TRAOa6S{nkRMYUI+~x7C7_lvZA@wE!+17!c7D4c7gcXX(N;N>#gGt6 z3x+{79Qf3;+*{`Lhz(4*OH>{x+|fKT_`qx}p(>!ZWsLQDLh#a*xX=lJwKsV~mrE;5 z3tn+%>Ah9L=MTVBta4k){NB$?y(&~VJ3)g$Wka-;a-iS^`@g~ry z2<~lOOIy`Jv%7`yuK@-_mT&%+P~t~^lHM+KZ4(DVuN~H!u=e6@sCb1xLvm9f3z+1x z`9VEnO>FfxcSz0|2y4Kxp+x(x6oa%Lj#>9qn@8#>E_g>@bm~!;qZc0AxE!-Tg1lb& zOUb>bPJ~a+#IN3f+J5+C#A8U#wW4aR(*$J!2l|=savmk2gVB*JKEsrA&&ZB!&0zP~ z8HGOY0j8YS-azN2MjR$ST$Xc-?Cg@7i;$cfiVoPq>M^&hYpQX0F-(DfJppj%+Mnd9 zs#Z_Vf;CP1Dcl$&+k(xcu+{RSK~MAXwl_ZdD^w~)EYTFFzFnRFLCKc^ou+M5Cy`fG zrjs_v1KzFv?rE6SE*+TNLT-uJRvTQKv>+Y0i4i#ks-8L;%kR@wNUF0Cyw{dXlPx0k zJ;IWX=N?NaN`f)vy!u^e-gzB{Y+4y2M*68s4%S67^<6k`c4Z?^gxeAJSM^F5y-Z#y zj4qe;jiM3}d(#ZQOE)JnEmxmt;M?x(ozfjB=BJ;NBmzu6%;uWk$?NtiMbCOK%L|md zhZC)A)MRiYGH?I7P3;I{V@Sz!J!>wQ?~QK0Kw#xwptYT}6;~0LF!;*dQ8S?tj0TsBG_aZ$g4P!4u+VS0$3k->WIej;^PcpGMhz4E{Af(osF{A=V&Bny@m@U>+S7Gy zIs)NTP_dzOrdwA%RQdMXvBE0$>H`*s0&b6dj_SI$>EnWpX*LT;JXYg-IZlL*! zl^E>!?r1A@rydCuDSl!{42r9M79Vv93wRc-t%mnt!Bq$%a`jPZzHQHa37?_ZtOX!} zeF@(u%MhlPkvTAExbSp(Cu|XPH^VERj~uz^AQw{+em&^Vtq8cH$SaDxqR10V(pUf3ru<8#(f$ z)E$dDf1g<_d}hZ9)3Rmzopm4;s&kFo6CTejdfJwl3S9(a)6%jaI)QUwP99XQX5tc6 zR90S8;tK7N^9pL9i{O8vzVct%@77hl5uP||F{NL~F-hPq>swON*k7~uL$JMZwtgORi^(k+ zD^K&2I;CW~s)9b$#J=tnitv%~SDtSG)@lnDX zi=Jvejr3`cP(Oou$<`|eWW%06XxOK)&2&e&)L2={>%z-6`vULxNtdRYGg-ZZ^}bK~ zE?@dluGakBGc3B}+B@%8t%lReA3htt4HJ{{wJQ8_DE_TX=b^7ezI^2?9a3U;9jDI` zqY=E*mNDm58*fJAXJbYWI09u$V)0=JjBEVo6pS$M1(EP4hX@7N#^P6b+$`F+u5%HY ztQJ?{nreHetJr6A#;9AGZp3?MqWam!QCVa8V^31`3EmT#+l_`qL~WvSUDI|*b(JQw z9ULl-bu}P+9P?01gf4>j$aZ-~Ss2fJIh~=&kl;-V9tBmaJIXmFzRI;{L`HbYAQXwJc=hD$Mnpkr*k>TW@nBat2*RUb75Dh^R^zZQ=Ub8=A16f z3YP6WA}B$FF zLL{lP3BW&DWO6+&iGOP)JP0+OBpvo!ZY4}+=Khi~dx)XDIR((maU?($cJLy|xLUQ` zN>~lOoW$kaQ3G^K_lDY&7#*G0tf0(F*NvaGgbv?Z3Ga`Lh70xpur}1#?40MfR>B^h zXYbx`7vOuMPsKyUOPUMR78o)t3L%f^62Dy=&%$ToXh=^+u<{FL9q;1IM*$VdBT0jG zCFhR77F{s4H%m;tc4Jhb>Js7Z9QBmYAPk+1I1eoC!ET&%fnO~&`hQtVIQvv$B9&8W z|EVZWx0vK(4D0b;xoy|hj8#x2xYoY0tCDO@m$$m18lO2?S%Nt6QTr~BB5=0mfZdJb zAwrH9!<{ne#5OoQ-su#lftjOz@Wmlnw^i)a7#NTge=Fl&h{ksO1{3@9IYj@4`>>u4V+FpNl*LeKaU6b&uyT#i~Q)m?M>M|VxdkM5c*W_L{@v%4nr-*wlRtV{~sn03%0D9wBjFisX=I{v(Zdv+Z26{a}jj~{ulFr_a3fXlO z>EB(NZFR7_7wyh#xr*SNbj&PJFA{JhcvpR3-7SVwVc)a0U9Nl$^0WIb=p=DLM){b< zds?X6+QKRNxk%e7^)VIsBQCQaWaT!!-WW7}AgAZ{6Lny(JO`X8EIX@xF)KoT6MxO@ ztgm}##N?@wpFwoCTeB&s{%23)z|fDgDGoUN1<=M$@RS)!wevWOX`hX58S}V9F1fq5 z4cz+BZt(PZ1W5w&$$j@Q-OF;jm{5*N8uO<=5(t+du2xTdNV1H+(i9bz*N6wY)dv&CswYb%Zp?h|9gjQ4R=T+2Rzvd>ywHaTiTzI#g*w`;RlV*;Y6dnrbQv zJeH#d%)%Cb=oX$$g}ZYxeNMt|p7Fi6Zl4nsI(%p7A4|Fa-J`HPUQAaLkfrp+P@crOaa;Mj z@%H$Hm=Bw9GS%#EzIV}=urj_}Kigp0GUKIg| zVp>Qu>o-*tlHD;cEs#uyEpJoHkVo3Rxw(FIvjt@qFX zB7Ksmg~HPwfP81+OCVn_E`Ow4uhN*5T>01oCMYfq6d#57L+y4p`EIU*MFo$&Maxup}I-w)@ zD(r5n!;mi(aQ83!;Y&pbcioHMSH|?EzIjg*AX3($6k^MwNGDopK>*NAH*zVdtj)%K zOBQ5*?Us`l1eC`?I5iDoc+_U!t}MgRMKCyOTJIKsWPD2Y!a*+ynT1`8b8@SWr<;O= z*}c_rq73y)JURB`Gr63xePp@&|3RpLc^y}3n>;Rg?_p;iZ%rlXiR#E8SL|B#{*r17Il&mNZPW8wgo!xU zQ$+#_?QyS#O{i8uGx`;Hag$;5nvLG`4*9-i>irGGL1BRt#AdsGPj~~$x(~UEqm3vaD(vCbc0jc!dui4h#W7-N z4JW{?F#}ZF^}bXQx)>lxLcbmA7rbi<6}J5AP(hG>>bU5FRL-iNp_hA*^>+X*nV2{> zix3GcF!MR*Vj7Kj@LjOe6O@tGLvQ6U;-Z6E7GLS(FMUEl4}@w8s+D936h1vYrtFdCL;_x4;xZ)X7cC=XW46S&ECV#p= zS3E=i?L9-hfA##5)R+KBT#+6v(|L9obi~sh+M5!E|lxgD*fs*Cz0S0L2U z3IK-GdgPhxsV(Jqb`~h(!2oG3#{j$eavnna2QmN`_K8J<_>oe0szVC^toO0-=HUaV zGi5e7fP|p)lI<$X3X*2IM6{_S5KS5(r{?((%2QVBJ3VPrU zHRvpUp;I9cJ@kMd1p0bdqtHU(#Q7GC%s;YocZZp!UK*|1w(UYp_A8(6r+DWmxtF=S zF4D@5ziO2$7YcbGccwt*ePR$jy2yLFC9$X5t2NC83zV|#JYLy);^4*FijpeMKiYI{ zp?8`5`@39miLJQA2Ggf+pcS{J-%vvj7fYyf;)7_1fn>-kwvK5PQ%}9^rp2$#AgAdz zw2h&y??}33OqtQCt`;^wL6w?y?wqUlYHVeC!}4=qlnGV=NX+AywDJ46u3Q4SJsEy zfmut|I$ziN(60CU8Njor;=n^?vFFQ}Thmh?ttymC;Y)nBS$_7yX9ACRL>fsInr&yF ztG&E%xj1Ps^oXN%SCS0#0qUOvm+H{HdMLr|ZEK&+qoNE?)JNS{&MOlpx@<|!FUwQWAB9_@(j6hvED?VjHpzoWx83rw#_wRkr z=Biaca_2b66v{=lqN+eIQ-3;-FF=-Rl2(kXmf9y*?~xW~n1=gZbni;ErrRZE33K7^XWYnS=*qYz9$xV^ zzHe$9C8T_7{=^GhqT7HyQ8w%OSjne;sH!%-nbSecwHAY+kKa|%@6WWHY3Y0Tb>eJ` zXN$;6#$OjIJHol?_-$0KV#1A@U1(%)U5GfQNBBrBY5ir{eh;NUd=pSA%q=dXhNGf} zMZ`bby)hL4`Ki&ci;`2g8)O7_###wuhE zlr2;QmEmEN{^N@`6u$&hS#h2?7E*31A;w+Mc$K{5c+Roi55})-`#;P%-P(9k z-N4B95n;@1K$)9L;b8_9T1BMJXe|zP(ocg@CkM7myQpAH6dN@+PTD)TrNp_%X9bu@s)F!#=p%+DuY$bQym&YATCA)Z=c50lm zE?D^(rR8mK_|XVXM$KS+)MvSr5bSNaREo&U0>-EMg4b_L4p82^ulO1CgyQO(Yg3iNC^-JRG0PVI2EuMduJTV8|E$K`WRUt{9Fe5S?I z>b!pBb66jbS-kxO^Msf?xolhor}rc2dsZ9CQY?NVdb|9Udxp?nh~dj$WNfB|om&6suFQ>mihBfB@+Jte(!z+0Fn(9;4+xfJ4MEIP+3p~>FX5n!W zBVc-L_ymla^8?B!pIl$u?wp;>^rdORr1IP6p>7+j`j`qO(0_5^aML` zLX53m_J4l%ma2&>^lj*sCi}xvK^6d&)x`hUWh8!yi;^n72 zLVa_p2LdXIZmY~fflTR1ZGyI$cf3qejbWx2gkG6K%+^c?P54YYDu-=}ZzAt}yPITJ zIrt3wtZi`EFLB*-mUXsP+KP>XEgol@UM|hyIPZAfk$Iqxpj0JUF+Rvh=p7iTDkM-5 zbjnI98W&VNm)u(12EMy~pz~VuJR@s(>DIj~nROZOwb?RFL+L}ePbHmv!S2`gc&7cH zh-a~zvxj93thU|jsfVvryPxskOU9;-Q#&&=(`mCcBkQBCBVTyJI?s)Y?Z%|%v5mcW zH(w)PuwrMpIv%xfG{^QXT(*^RrUzV#c?SpgC(O`R|vvg0j`kM zlo1ke3>;{3ZCiT2l(tlStTWt>E++o3mM1NY@l~%e=1Ns*LsFmoC!*sE(NFY_>&@n! zSCv<5L$j&n=BdS+O3egCYej=GB24ey?;3qj3NMdJKeIW6`1<`do-r@GnKmx_JW@I& zUQFE5=-tlyNAIm8Hc@j4XW&{`Q|vgZC_y>@FU)gIyK|C`?a|`Q*JG}Kc)D(vUolbf ztYV?m@@{{$6UUTnvClAW`QCV8w6gMBpDPQ0o$Xa+8OySDkv*dXI#f2%~?FKW9=2o z9E+JPx1^+vG_EX73Te1A7fz6$(w&@WCk7VJ4kvkx;WQrii;a2i&k^I0ky}qOskekK zHdMYrzMXuyb!GY8r0POcpYXOR_l0WR8BjKi0Ap#ANc9X)8FV4R|h2q$c$1#3=O!$jz)(OMbD{ z&a%!{3=;V~9KRYgMQgECt?P}Sy))0-sSy^mxx1%ZZsajXk8lA6~*-`ougr%6LvZmEKZ@B+u z>0^UoNnf5*u2af+UWJ;?)Pt>W48(-a`PJ-CS;>l$NW;D1d18F*QtU<1<^}{(AdoO* zvRl}Z&322>xV>KTxlzO`X4)Pp>_4(;vuu`EB|qw0=P(mH8?>dg7`PaQI-i%7$q}fO zwU@XuZL&Dgncw-%g1rF}5VP&qC|XjY)0(injXWx5jBnQjjn09Blc_-zSwk1@8rZ(w zzyGA8Wbep6Ls3W6-qHOBjt)mS-K0USc^x(_uO3x?K8RdlRuVs(v#{{M;O4~R1!qCC z8$R^hYisNEQb&V(KyR2$EG!nA8yZB1EMIzq5E=P$4+KxpWwE@v(5e2}rWZ~49EO(5 z4wCuEVU`p9hGKVM|Ac^Mbgp7S*lFPVBhuO!Zlk3I5(MaDpd)0gprZgq1~eryw%@cG z*#!{Ak9l$sD9jFY?u&8ASz%)4>X?~^1r1iAUPC&Xn+U0d>smh z1O4^8?$*}M9taoDn$8AuVB)yzH4_gIh>4ff$l&_mCBS~4-3?<;V=Ya&yDm;5mR2rz ztVMjBTuF973O;fG>16F`$?45_z)!VmfX#`&}2Ax-1| zqvj#bKQ#?Jtlgn5PQa3$N`Kt-YuUrhUy2H%q&Gjb6F;~4$1JeVO4JIXzkQ}e-4U!o zawEGPOy>sB13^Z*$kKqH3qN}jEg{aFkU|83=!)R5%Qt)m=ZBHeV_udYmyJs1*K3jq ztp*owTiBZBJJieU7-6e)WlD-L9Ox`h7U&k>PVcQTpr&X!=ia**MNMWdliZB z@hZ{OD8u*;%L-t1+PPWPN-ts2lhdQ%Me#_)SzH3#FTK+?5bi7kjC$uDWOd%tR-tM%f!M+S0Q#PQ#CCrk?{|_6dy6eOL|eOV(E>NC`}j*wW#<-i^RTE*2z~ zDPS2X>1bZeWl8J?+;c{G0Rv%SM}_S98MwB17y{wsG){E%MX}+ySWVd!xE_n^wpPRY znLNTnsg<%P@MX39vTbM4xskRBLLwJY0VEnFnZQD^njj!LbOmz5-3*nKknj+5nx=Q| zyY{Euw;&kQ;RFiIf6^4-uH8miJ?4Tz4R&n6SUzZYXsd{ZdngMG>b06M>_~~%C7fVL z$eO=*nkFew0{g)Ruu|89)UiMvXVcn})ijBxjjacHs%gwe?#in8ic9UFE$3A>KPJZ>5*kln*LO`eWH2oCH%wQC}1Zk5Rb z%7PokmkwU5No$ZE?Z(>hxz%P~tR#jc=YPkKUOAAD70-S@9ThNq5>1DD(jenC(<9py z#)dLSFaXbXV+b{YX|G2hfzPwh@lMkv1wL%_NRyZR^D6TphL=0pdFa5V^HEs^jnOvXo)_0Z=3ooO(N z-tM7PT&)3cKfsx! zbT;{Xb>xTp0^v2qBNpm}-F>hmm1mH1o~J3sGg_9p<)mUHb&--(V>`T71= z<%fIgz+=wlC8jl=)2jf>9ND!>2!u0Le7Fh3MBUqtJtIckV$bS0YP7jZLJ4JMZ8qE+ z36|Yi=E5)4ipgLH_0JdsS1FqJEM8EhOgsQMKP8z6m)_#8e>dV0V5bAkG zhLB^r{I)JC;LgZsV0y!7x|Mq);;fl`&1^|~u@k^@bJ>fnL4g?-h0vDK!q-q4U24i- zUVDv(j14O+Q>j9tE2$E|ZG+Rvp$*bgso-hZ@m33Fyx8Oh?ixzV>7Ef(*=&6&G$eMH ziV^4VxeRQZdKq#b&!$eCrb)>vUkXjCJ;}{#YLaOMCpsFTz%Ki-sxW90 zq9GB}C=-D|oWqsE-G|%3SdQIyeCS+j2zISD$h1{dIUbC3V!(IFr3s9ca42O?#7F~& zTV6GCYwQ_6yvV5QR`q)OiEQxz~eJU6>vIbm=8#CQTX}I5aBfO^(3&jw9cYN*?M^F;=)6W{4Z!(3TW%Cihjsvd1?~3FRt{3%YZMX>oa2hu` zmLRl5A^~JW1K14!uKi47lw8G3`D$l1ph>6ID?fv@MP+~i8O+yn!hU8jk2O^fvsKSl zx^uWea6d#R`{`17>QyL&#)Sc=H@=7pu-p>s3WQzt_#PXJzzCVu-<^s=%i0eoO_YSv z&hn)7E2Z`C4e8apQK^*+VmbC}+_rKML9}@GZ^|cnLh!p~7qNv@8Uoe_%s*uBQo=kc z9;bsBE%w*OtLiVJiHb0fdvrMbo6o(XqaUZ*h5p6_6FhxDR1mP$Cz zrhvyqwp3*=PH!iXUM=~S&s_HT-8e-jpSx%}nnQOCLawD8E&^;4g5zhQ)I|492FYvy z>7x7J47D$(6?KbxH#jaS=@<3hGgF1s)f|Fg8##FKZLaQLh(M}&;riXH2sYrJNG@Y zT9OEXN-=vrBhhp5taw_d^#BirM|wYh;{{f^&UJt@l45n4MIh~N!dm-tWO20uJhlW` z=39mMuJEURbAOLOc`MWvu8BTH-hb@VmR#mCG6yCm0YV-L&xL_1&O!fEt;+;Ld)QQ6 z;M)wC1&I_WNX+I)v8n`Qy!or2C+fGZfQ=I4^L%T9MzX=oGW(w+!Tk}T9p(P5OREH+ zIE-P(l|o~h%o2u&Uhk`+PN+huOb2|FhI$h+^wnZ9E?Z*!SdW-lei$^&bjU}^ENi0K zD8dH&7Vqd_t}MVR*Yo)}HabMVL#yT$5Xd){h~T*75k;(tDg>Q-|5tUW0nopz!ze)N zS9M5|L7f{}=ZG`8$cop+-v?ulKLA35Q85*4VhDti;Ghf-Tsi{KxMzZrj6}eo)64}vr(fl~LM@H$X;2wqhSRlBPf$z_>- z){SzaX&r&gElod=cAGV;Fj&Ty3j}26p-leq`2RZQVFi5n^FE8On1JC`(i#3;VSVTz`*Y;{IiwC1 z+yB#g?El5tMf#}wN8$Ko)7Jlm;gD`?LsGv**XHAN3$btnf&>uXB-Vh?mJ^7EPYPL^W)@#LS=FL#-rbGqW`LIs3ZiojAij6^n92KqQnl@X^gz*k z-HlrV-DCCn^6>Kz(i`E9mY>?CH{Eakabj!qH9jS@CqaK4&;qq3nQ36+8`h}sP<4Ew z#A{-%fsS)|7~%;#&9994l}1`rKAyW<%NjURto(l;FRrk`|8#zcp?cxTCxakxl!=2n zVW$AQ-ZU44e^aEoT7A;r>bCRh_5`yk1 z;y&-Xju?XrL4V^#dM3D$9$?@i%rpcF%rn8`i=qXID?nlYoFtFyUZ33uSHE7SG6Gj? zL&R44A^!sR6g+C(G6AUx6V=V@4CoM}m_OJK{CS}UIeY{A{x_ll#BHm;v3DL}UZUe3 zfyjAr2*ZLAw;l5c)a>Lx(!@1ibv#P`5(FIrmE1zovVrjv_EY|(KXl-#+OXm)x9|TK DTPOj6 From 4a339aa7acaded1e5bc6f40218fc3bf19f44ef6d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 22:11:38 -0800 Subject: [PATCH 69/77] Update typing gif --- docs/_static/typing.gif | Bin 65734 -> 221787 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/_static/typing.gif b/docs/_static/typing.gif index 86a4e389780552621d2b17cc49d4c124e14a5b79..d38f09041eee4b443d3e08714185c995b58591f7 100644 GIT binary patch literal 221787 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⋙U0nm4?P}O7$sQK5ef!Y*MHWEWU0#&q=xhKb<7sf1+SR z`Ya+;j*{qIV`Ay0h+NI}`A$tSR(*$`hX0RO*pFPFe{aRrtGgkdh zKaETMj8z|M24|E&{^=D4mG}lj6=H8bW$NYEO5(7bC&Nsc0YoIhVwl1c4I)=ZI~;H& zWot*S&i0FiJ0kIt!ZAfx)FXMM;?gVXw?u|Yh?F-pD@Cpb2;aQ3F{OHSxVPPtjYLGT z3?fAOMx?4(V)BJX>Ee;Rok9=C#wMm;jf2U#Xh$R47~heKIyyQ8I1D32Vj3R>!-XGE@Z8E|^iW z&DIoKm>*wL`f%{)VZtdI>4dRelW=DA z&TNqXzQ-r09792Sytt(LWv);>anK2KZ>oXrBN0Rzj)C9uves4kX82g?QllfmUrLj{ zFC>o9D5i_^HnJ6`q!uN{LG8Zc7bb~hbhl(H1!=)M5|Scj*VaH=-Nr$lZlEnN7_anlY6rEyhVS+R#C}8fI%uziC z$G2dNHjjEscQ1b|!wM+C-9v)41YZ)4mXz%;;hdW3&#jM~d0Fa~s5f&ia#qkyFU~90 z10>r&>Mc5rJL(e<nBqe568_DY`-6ZOgr^o&%L)MScoh zptR>wo}vt{zDN9$FZSNQhy>ow2yOF2w%q;dL+CY%2^yy!Wl^~rqS8Tt=DBF`r_?aU z1<)m68&AL(9oMZ5(_4!Kw)_}s!%Bj0JI<_j9@I6cduwZK6XA{mVHj zQ>wdZf^fCnL-6$oWi0(1cy#q0s}is{cVt-u^mMiFimmQvL;^Dh3Hw2j!0Mrz+NwGx zxf|5PU!S)uKBa~yfId8TlaCV?!QR-jQocEatvE)==L#MvhNWPwy+iq0sSzr~7Myi| zD91P*wSzw^lw$)a<>Y;K4RVMn_Z4NiMc9S8s4V^^5c1@xncn8QltdMiEiuHzHhU-% z_^`j{76I*DLE+_+_p5{7F_|z$*X0tm{E=~%^e)X}=@nJwlj0q*wb7vzhiYO$tbGo} zssG4^YYGbq)nC%)xqYG09B|S^EiM5IGRymIX_*Run@&m8 zyECPu$ITKz=D6L=zHPFj*iezkAG5gcsm@wP+v+TzhR7}qG!GfwJFKj%+}zwEA|g^! zQi_U-78Vwe@5RT*2PzGN%68VwfW^hdwY9Z3Z{CE+v!_p=Ld4964#v5jONQOj;lCZvuQg(N(k57rCFTt;@|^>-TNF!BpM3ALwWXs#IH$%g`75{QvBcv zd`=NltRDH10`5U>B%xCsV^JUi>mD^?M5dq&f(;5(Kkp(zd5jfaE?bp32cDjJJgKG^ zcK-vGhJ=dlX)q32d(zpOruIiLli5PC{bL-4k5Z}nqkFu+Og{unOA#_t{!;jU=L?|U z5c(Q5)uhj6FhxscRVzo8WMP%aUZ5&py(Jy+lQxiqqM61F(+S{8(O+nF zzg>pfaFC-(0oeEl0;C%-B})D`1w$E<4KEHfCc_HRLCX|!;S`n1-B?4#WBuSm67 zZ04VOaG-x_!1>b}2Yvd`r}d}Wtu*0{&+00&r3KxFz!en1KEZ+`w72H?;dX9^>SL5!`OuqqQJ zBJvEoFCsW9A<>JMl|wk`_cwfQ-X&^&L1EF4+AWRWAN5s@UeNC63-b+cYISa2UHwjR z-R`w$UfuleJuqz!lo0lP_>9!NP4d@{=PxvxuSB9;FbaS+nWwL)>oZ$cH8A> zy`wUIf%;SEk*J zj`@59O&dUe!`&` zC2{gYyi!6Yp};{$+%NP~99r@Yet%>$kZx7kowv*kV_7U{&lT{ZB?+<^^+T`u{^b^Y zcq_InC5^WXhSU)(n<>drjO0q%A!t`rs+X!ES;pA@5(?NhkWOzDoRX`k{Tz6yumX

O5EVi`JF9c%8C-c8s3 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%0D9wBjFisX=I{v(Zdv+Z26{a}jj~{ulFr_a3fXlO z>EB(NZFR7_7wyh#xr*SNbj&PJFA{JhcvpR3-7SVwVc)a0U9Nl$^0WIb=p=DLM){b< zds?X6+QKRNxk%e7^)VIsBQCQaWaT!!-WW7}AgAZ{6Lny(JO`X8EIX@xF)KoT6MxO@ ztgm}##N?@wpFwoCTeB&s{%23)z|fDgDGoUN1<=M$@RS)!wevWOX`hX58S}V9F1fq5 z4cz+BZt(PZ1W5w&$$j@Q-OF;jm{5*N8uO<=5(t+du2xTdNV1H+(i9bz*N6wY)dv&CswYb%Zp?h|9gjQ4R=T+2Rzvd>ywHaTiTzI#g*w`;RlV*;Y6dnrbQv zJeH#d%)%Cb=oX$$g}ZYxeNMt|p7Fi6Zl4nsI(%p7A4|Fa-J`HPUQAaLkfrp+P@crOaa;Mj z@%H$Hm=Bw9GS%#EzIV}=urj_}Kigp0GUKIg| zVp>Qu>o-*tlHD;cEs#uyEpJoHkVo3Rxw(FIvjt@qFX zB7Ksmg~HPwfP81+OCVn_E`Ow4uhN*5T>01oCMYfq6d#57L+y4p`EIU*MFo$&Maxup}I-w)@ zD(r5n!;mi(aQ83!;Y&pbcioHMSH|?EzIjg*AX3($6k^MwNGDopK>*NAH*zVdtj)%K zOBQ5*?Us`l1eC`?I5iDoc+_U!t}MgRMKCyOTJIKsWPD2Y!a*+ynT1`8b8@SWr<;O= z*}c_rq73y)JURB`Gr63xePp@&|3RpLc^y}3n>;Rg?_p;iZ%rlXiR#E8SL|B#{*r17Il&mNZPW8wgo!xU zQ$+#_?QyS#O{i8uGx`;Hag$;5nvLG`4*9-i>irGGL1BRt#AdsGPj~~$x(~UEqm3vaD(vCbc0jc!dui4h#W7-N z4JW{?F#}ZF^}bXQx)>lxLcbmA7rbi<6}J5AP(hG>>bU5FRL-iNp_hA*^>+X*nV2{> zix3GcF!MR*Vj7Kj@LjOe6O@tGLvQ6U;-Z6E7GLS(FMUEl4}@w8s+D936h1vYrtFdCL;_x4;xZ)X7cC=XW46S&ECV#p= zS3E=i?L9-hfA##5)R+KBT#+6v(|L9obi~sh+M5!E|lxgD*fs*Cz0S0L2U z3IK-GdgPhxsV(Jqb`~h(!2oG3#{j$eavnna2QmN`_K8J<_>oe0szVC^toO0-=HUaV zGi5e7fP|p)lI<$X3X*2IM6{_S5KS5(r{?((%2QVBJ3VPrU zHRvpUp;I9cJ@kMd1p0bdqtHU(#Q7GC%s;YocZZp!UK*|1w(UYp_A8(6r+DWmxtF=S zF4D@5ziO2$7YcbGccwt*ePR$jy2yLFC9$X5t2NC83zV|#JYLy);^4*FijpeMKiYI{ zp?8`5`@39miLJQA2Ggf+pcS{J-%vvj7fYyf;)7_1fn>-kwvK5PQ%}9^rp2$#AgAdz zw2h&y??}33OqtQCt`;^wL6w?y?wqUlYHVeC!}4=qlnGV=NX+AywDJ46u3Q4SJsEy zfmut|I$ziN(60CU8Njor;=n^?vFFQ}Thmh?ttymC;Y)nBS$_7yX9ACRL>fsInr&yF ztG&E%xj1Ps^ Date: Tue, 27 Dec 2022 22:42:29 -0800 Subject: [PATCH 70/77] Backslash escapes don't seem to be working :shrug: --- tox.ini | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 257dca07..f7883730 100644 --- a/tox.ini +++ b/tox.ini @@ -49,10 +49,7 @@ deps=rstcheck [testenv:uploaddocs] allowlist_externals=rsync changedir=docs -commands= - rsync --rsync-path 'sudo -u herokuish rsync' -azP --stats --delete \ - _build/html/ \ - grantjenks:/srv/www/grantjenks.com/public/docs/freegames/ +commands=rsync --rsync-path 'sudo -u herokuish rsync' -avzP --stats --delete _build/html/ grantjenks.com:/srv/www/grantjenks.com/public/docs/freegames/ [isort] multi_line_output = 3 From d4f6a9d1785c709d2af5c5a59f3546af7ee4c97e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 22:43:56 -0800 Subject: [PATCH 71/77] Bump version 2.5.2 --- src/freegames/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freegames/__init__.py b/src/freegames/__init__.py index 8f0b20aa..a5029a89 100644 --- a/src/freegames/__init__.py +++ b/src/freegames/__init__.py @@ -62,7 +62,7 @@ __all__ = ['floor', 'line', 'path', 'square', 'vector'] __title__ = 'freegames' -__version__ = '2.5.1' +__version__ = '2.5.2' __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = '2017-2023, Grant Jenks' From 5e142cd95882ffdf821ce83aae9b919fcbe627b9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:06:23 -0800 Subject: [PATCH 72/77] Add "freegames" entry-point and "play" subcommand --- pyproject.toml | 3 ++ src/freegames/__main__.py | 88 ++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 10672a84..f9fe6efe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,9 @@ classifiers = [ ] dynamic = ["version"] +[project.scripts] +freegames = "freegames.__main__:main" + [project.urls] Documentation = "https://grantjenks.com/docs/freegames/" Funding = "https://gum.co/freegames" diff --git a/src/freegames/__main__.py b/src/freegames/__main__.py index fee826a2..f09f3aba 100644 --- a/src/freegames/__main__.py +++ b/src/freegames/__main__.py @@ -3,9 +3,7 @@ import argparse import os - -directory = os.path.dirname(os.path.realpath(__file__)) -contents = os.listdir(directory) +import runpy def game_file(name): @@ -17,49 +15,61 @@ def game_file(name): ) -games = sorted(name[:-3] for name in contents if game_file(name)) +def main(): + directory = os.path.dirname(os.path.realpath(__file__)) + contents = os.listdir(directory) + games = sorted(name[:-3] for name in contents if game_file(name)) -parser = argparse.ArgumentParser( - prog='freegames', - description='Free Python Games', - epilog='Copyright 2023 Grant Jenks', -) -subparsers = parser.add_subparsers(dest='command', help='sub-command help') + parser = argparse.ArgumentParser( + prog='freegames', + description='Free Python Games', + epilog='Copyright 2023 Grant Jenks', + ) + subparsers = parser.add_subparsers(dest='command', help='sub-command help') + subparsers.required = True -parser_list = subparsers.add_parser('list', help='list games') + parser_list = subparsers.add_parser('list', help='list games') -parser_copy = subparsers.add_parser('copy', help='copy game source code') -parser_copy.add_argument('game', choices=games, help='game name') -parser_copy.add_argument( - '--force', - action='store_true', - help='overwrite existing file', -) + parser_play = subparsers.add_parser('play', help='play free Python games') + parser_play.add_argument('game', choices=games, help='game name') -parser_show = subparsers.add_parser('show', help='show game source code') -parser_show.add_argument('game', choices=games, help='game name') + parser_show = subparsers.add_parser('show', help='show game source code') + parser_show.add_argument('game', choices=games, help='game name') -args = parser.parse_args() + parser_copy = subparsers.add_parser('copy', help='copy game source code') + parser_copy.add_argument('game', choices=games, help='game name') + parser_copy.add_argument( + '--force', + action='store_true', + help='overwrite existing file', + ) -if args.command == 'list': - for game in games: - print(game) -elif args.command == 'copy': - filename = args.game + '.py' + args = parser.parse_args() - with open(os.path.join(directory, filename)) as reader: - text = reader.read() + if args.command == 'list': + for game in games: + print(game) + elif args.command == 'play': + runpy.run_module('freegames.' + args.game) + elif args.command == 'show': + with open(os.path.join(directory, args.game + '.py')) as reader: + print(reader.read()) + else: + assert args.command == 'copy' + filename = args.game + '.py' - cwd = os.getcwd() - path = os.path.join(cwd, filename) + with open(os.path.join(directory, filename)) as reader: + text = reader.read() + + cwd = os.getcwd() + path = os.path.join(cwd, filename) + + if args.force or not os.path.exists(path): + with open(path, 'w') as writer: + writer.write(text) + else: + print('ERROR: File already exists. Specify --force to overwrite.') - if args.force or not os.path.exists(path): - with open(path, 'w') as writer: - writer.write(text) - else: - print('ERROR: File already exists. Specify --force to overwrite.') -else: - assert args.command == 'show' - with open(os.path.join(directory, args.game + '.py')) as reader: - print(reader.read()) +if __name__ == '__main__': + main() From 43004792fca2574b55279f1ac08ffcc17d551b56 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:09:52 -0800 Subject: [PATCH 73/77] Add note about pipx support in quickstart --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index f4e6ccea..ca6b1cbd 100644 --- a/README.rst +++ b/README.rst @@ -123,6 +123,12 @@ help function:: >>> import freegames >>> help(freegames) +Free Python Games also provides an entry-point script for compatibility with +`pipx `_:: + + $ pipx install freegames + $ freegames play life + Free Games ---------- From a63c62cf068cbb5222e2447706b7c89a847dc5e3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:11:03 -0800 Subject: [PATCH 74/77] Bump version to 2.5.3 --- src/freegames/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freegames/__init__.py b/src/freegames/__init__.py index a5029a89..84e1d1ac 100644 --- a/src/freegames/__init__.py +++ b/src/freegames/__init__.py @@ -62,7 +62,7 @@ __all__ = ['floor', 'line', 'path', 'square', 'vector'] __title__ = 'freegames' -__version__ = '2.5.2' +__version__ = '2.5.3' __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = '2017-2023, Grant Jenks' From 6a12ec5c446af36c4ab965d5424112531fd21cf2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:12:19 -0800 Subject: [PATCH 75/77] flake8 fix --- src/freegames/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freegames/__main__.py b/src/freegames/__main__.py index f09f3aba..3631d0ef 100644 --- a/src/freegames/__main__.py +++ b/src/freegames/__main__.py @@ -28,7 +28,7 @@ def main(): subparsers = parser.add_subparsers(dest='command', help='sub-command help') subparsers.required = True - parser_list = subparsers.add_parser('list', help='list games') + subparsers.add_parser('list', help='list games') parser_play = subparsers.add_parser('play', help='play free Python games') parser_play.add_argument('game', choices=games, help='game name') From b88cf6ed20d0b380ae60339cb01ce43c50d93218 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:27:31 -0800 Subject: [PATCH 76/77] Add tests for entry-point changes --- src/freegames/__main__.py | 2 +- tests/test_main.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/freegames/__main__.py b/src/freegames/__main__.py index 3631d0ef..73ed8a6a 100644 --- a/src/freegames/__main__.py +++ b/src/freegames/__main__.py @@ -72,4 +72,4 @@ def main(): if __name__ == '__main__': - main() + main() # pragma: no cover diff --git a/tests/test_main.py b/tests/test_main.py index f85436cd..85b4e137 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -4,12 +4,14 @@ import runpy import unittest.mock as mock +from freegames import __main__ + def test_main_list(): random.seed(0) with mock.patch('sys.argv', ['__main__.py', 'list']): - runpy.run_module('freegames.__main__') + __main__.main() def test_main_copy(): @@ -19,7 +21,7 @@ def test_main_copy(): with mock.patch('sys.argv', ['__main__.py', 'copy', 'guess']): with mock.patch('builtins.open', mock_open): - runpy.run_module('freegames.__main__') + __main__.main() def test_main_copy_error(): @@ -31,7 +33,7 @@ def test_main_copy_error(): try: with mock.patch('sys.argv', ['__main__.py', 'copy', 'guess']): - runpy.run_module('freegames.__main__') + __main__.main() finally: os.remove('guess.py') @@ -40,4 +42,15 @@ def test_main_show(): random.seed(0) with mock.patch('sys.argv', ['__main__.py', 'show', 'guess']): - runpy.run_module('freegames.__main__') + __main__.main() + + +def test_main_play(): + random.seed(0) + mock_input = mock.Mock() + mock_input.side_effect = [20, 70, 50] + mocks = {'print': lambda *args: None, 'input': mock_input} + + with mock.patch.multiple('builtins', **mocks): + with mock.patch('sys.argv', ['__main__.py', 'play', 'guess']): + __main__.main() From cfa331f3d2d20f3fb742c9cd5504cea53504b0ef Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 27 Dec 2022 23:31:31 -0800 Subject: [PATCH 77/77] flake8 it --- tests/test_main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 85b4e137..50906473 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,7 +1,6 @@ import io import os import random -import runpy import unittest.mock as mock from freegames import __main__